firebase-admin-auth-rs 0.1.0

A firebase ID token validator written in Rust.
Documentation
use crate::jwk::{Fetcher, JwkFetcher};
use crate::verifier::{Claims, JwkVerifier};
use jsonwebtoken::TokenData;
use log::info;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::task::JoinHandle;
use tokio::time::sleep;

const ISSUER_URL: &'static str = "https://securetoken.google.com/";
const DEFAULT_PUBKEY_URL: &'static str =
    "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";

pub struct JwkAuth {
    verifier: Arc<Mutex<JwkVerifier>>,
    fetcher: Arc<JwkFetcher>,
    task_handler: Arc<Mutex<Box<JoinHandle<()>>>>,
}

impl Drop for JwkAuth {
    fn drop(&mut self) {
        let handler = self.task_handler.lock().unwrap();
        handler.abort();
    }
}

impl JwkAuth {
    pub async fn new(project_id: String) -> JwkAuth {
        let pubkey_url = DEFAULT_PUBKEY_URL.to_string();
        Self::_new(project_id, pubkey_url).await
    }
    pub async fn _new(project_id: String, pubkey_url: String) -> JwkAuth {
        let issuer = format!("{}{}", ISSUER_URL, project_id.clone());
        let audience = project_id;
        let fetcher = JwkFetcher::new(pubkey_url);
        let jwk_key_result = fetcher.fetch_keys().await;
        let jwk_keys = match jwk_key_result {
            Ok(keys) => keys,
            Err(_) => {
                panic!("Unable to fetch jwk keys!")
            }
        };
        let mut instance = JwkAuth {
            verifier: Arc::new(Mutex::new(JwkVerifier::new(
                jwk_keys.keys,
                audience,
                issuer,
            ))),
            fetcher: Arc::new(fetcher),
            task_handler: Arc::new(Mutex::new(Box::new(tokio::spawn(async {})))),
        };
        instance.start_periodic_key_update();
        instance
    }
    pub fn verify(&self, token: &String) -> Option<TokenData<Claims>> {
        let verifier = self.verifier.lock().unwrap();
        verifier.verify(token)
    }
    fn start_periodic_key_update(&mut self) {
        let verifier_ref = Arc::clone(&self.verifier);
        let fetcher_ref = Arc::clone(&self.fetcher);
        let task = tokio::spawn(async move {
            loop {
                let fetch_result = fetcher_ref.fetch_keys().await;
                let delay = match fetch_result {
                    Ok(jwk_keys) => {
                        {
                            let mut verifier = verifier_ref.lock().unwrap();
                            verifier.set_keys(jwk_keys.keys);
                        }
                        info!(
                            "Updated JWK Keys. Next refresh will be in {:?}",
                            jwk_keys.validity
                        );
                        jwk_keys.validity
                    }
                    Err(_) => Duration::from_secs(60),
                };
                sleep(delay).await;
            }
        });
        let mut handler = self.task_handler.lock().unwrap();
        *handler = Box::new(task);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tests::*;
    use crate::verifier::JwkConfig;

    #[tokio::test]
    async fn test_jwk_auth_new() {
        let keys = get_test_keys();
        let mock_server = get_mock_server().await;
        let project_id = "pj".to_string();

        let jwk_auth = JwkAuth::_new(project_id.clone(), get_mock_url(&mock_server)).await;
        let verifier = jwk_auth.verifier.lock().unwrap();

        assert_eq!(verifier.get_key("kid-0"), Some(&keys[0]));
        assert_eq!(verifier.get_key("kid-1"), Some(&keys[1]));
        assert_eq!(
            verifier.get_config(),
            Some(&JwkConfig {
                audience: project_id.clone(),
                issuer: format!("{}{}", ISSUER_URL, project_id.clone())
            })
        );
    }
}