diff --git a/README.md b/README.md index 691f5b8..3fbba94 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ use rs_firebase_admin_sdk::{ }; // Supply the Firebase project ID directly instead of reading GOOGLE_CLOUD_PROJECT -let live_app = App::live_with_project_id("my-firebase-project-id").await.unwrap(); +let live_app = App::live_with_project_id("my-firebase-project-id").unwrap(); ``` For more examples please see https://github.com/expl/rs-firebase-admin-sdk/tree/main/examples diff --git a/examples/clear_emulator/Cargo.toml b/examples/clear_emulator/Cargo.toml index 42c726c..dbacd44 100644 --- a/examples/clear_emulator/Cargo.toml +++ b/examples/clear_emulator/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" [dependencies] rs-firebase-admin-sdk = { path = "../../lib" } -tokio = { version = "1.51", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.52", features = ["macros", "rt-multi-thread"] } diff --git a/examples/clear_emulator/src/main.rs b/examples/clear_emulator/src/main.rs index 074176f..b4f97e3 100644 --- a/examples/clear_emulator/src/main.rs +++ b/examples/clear_emulator/src/main.rs @@ -12,8 +12,8 @@ where #[tokio::main] async fn main() { - let emulator_app = App::emulated(); - let emulator_admin = emulator_app.auth("http://localhost:9099".into()); + let emulator_app = App::emulated("http://localhost:9099".into()); + let emulator_admin = emulator_app.auth(); clear_emulator(&emulator_admin).await; } diff --git a/examples/cookies/Cargo.toml b/examples/cookies/Cargo.toml index 9307ec0..017be8b 100644 --- a/examples/cookies/Cargo.toml +++ b/examples/cookies/Cargo.toml @@ -6,4 +6,4 @@ edition = "2024" [dependencies] rs-firebase-admin-sdk = { path = "../../lib" } time = { version = "0.3" } -tokio = { version = "1.51", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.52", features = ["macros", "rt-multi-thread"] } diff --git a/examples/cookies/src/main.rs b/examples/cookies/src/main.rs index 560a868..15071b6 100644 --- a/examples/cookies/src/main.rs +++ b/examples/cookies/src/main.rs @@ -1,4 +1,4 @@ -use rs_firebase_admin_sdk::{App, auth::FirebaseAuthService, jwt::TokenValidator}; +use rs_firebase_admin_sdk::{App, auth::FirebaseAuthService}; use time::Duration; #[tokio::main] @@ -10,8 +10,7 @@ async fn main() { .create_session_cookie(oidc_token, Duration::seconds(60 * 60)) .await .unwrap(); + let live_cookie_validator = live_app.cookie_validator().unwrap(); - let live_cookie_validator = live_app.cookie_token_verifier().unwrap(); - - live_cookie_validator.validate(&cookie).await.unwrap(); + live_cookie_validator.validate(cookie).await.unwrap(); } diff --git a/examples/get_users/Cargo.toml b/examples/get_users/Cargo.toml index 4a00ae5..c356f99 100644 --- a/examples/get_users/Cargo.toml +++ b/examples/get_users/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" [dependencies] rs-firebase-admin-sdk = { path = "../../lib" } -tokio = { version = "1.51", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.52", features = ["macros", "rt-multi-thread"] } diff --git a/examples/get_users/src/main.rs b/examples/get_users/src/main.rs index 09c41ad..d643e80 100644 --- a/examples/get_users/src/main.rs +++ b/examples/get_users/src/main.rs @@ -32,8 +32,8 @@ async fn main() { print_all_users(&live_auth_admin).await; // Emulator Firebase App - let emulator_app = App::emulated(); - let emulator_auth_admin = emulator_app.auth("http://localhost:9099".into()); + let emulator_app = App::emulated("http://localhost:9099".into()); + let emulator_auth_admin = emulator_app.auth(); print_all_users(&emulator_auth_admin).await; } diff --git a/examples/verify_token/Cargo.toml b/examples/verify_token/Cargo.toml index 6603e61..7698713 100644 --- a/examples/verify_token/Cargo.toml +++ b/examples/verify_token/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" [dependencies] rs-firebase-admin-sdk = { path = "../../lib", features = ["tokens"] } -tokio = { version = "1.51", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.52", features = ["macros", "rt-multi-thread"] } diff --git a/examples/verify_token/src/main.rs b/examples/verify_token/src/main.rs index ba19002..664d875 100644 --- a/examples/verify_token/src/main.rs +++ b/examples/verify_token/src/main.rs @@ -1,6 +1,7 @@ use rs_firebase_admin_sdk::{App, jwt::TokenValidator}; +use std::sync::Arc; -async fn verify_token(token: &str, validator: &T) { +async fn verify_token(token: String, validator: Arc) { match validator.validate(token).await { Ok(token) => { let user_id = token.get("sub").unwrap().as_str().unwrap(); @@ -17,11 +18,11 @@ async fn main() { // Live let oidc_token = std::env::var("ID_TOKEN").unwrap(); let live_app = App::live().await.unwrap(); - let live_token_validator = live_app.id_token_verifier().unwrap(); - verify_token(&oidc_token, &live_token_validator).await; + let live_token_validator = live_app.token_validator().unwrap(); + verify_token(oidc_token.clone(), live_token_validator.clone()).await; // Emulator - let emulator_app = App::emulated(); - let emulator_token_validator = emulator_app.id_token_verifier(); - verify_token(&oidc_token, &emulator_token_validator).await; + let emulator_app = App::emulated("http://emulator:9099".into()); + let emulator_token_validator = emulator_app.token_validator().unwrap(); + verify_token(oidc_token, emulator_token_validator).await; } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 58f9609..31f14a0 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rs-firebase-admin-sdk" -version = "4.3.0" +version = "5.0.0" rust-version = "1.88" edition = "2024" authors = ["Kostas Petrikas"] @@ -18,7 +18,7 @@ default = ["tokens"] tokens = ["dep:jsonwebtoken", "dep:jsonwebtoken-jwks-cache"] [dependencies] -tokio = { version = "1.51", features = ["sync"], default-features = false } +tokio = { version = "1.52", features = ["sync"], default-features = false } error-stack = "0.7" thiserror = "2.0" serde = { version = "1.0", features = ["derive"] } @@ -28,12 +28,12 @@ headers = "0.4" reqwest = { version = "0.13", features = ["charset", "json", "hickory-dns", "rustls", "brotli"], default-features = false } urlencoding = "2.1" bytes = "1" -google-cloud-auth = "1.8" +google-cloud-auth = "1.13" time = { version = "0.3", features = ["serde"] } base64 = "0.22" jsonwebtoken = { version = "10", optional = true } jsonwebtoken-jwks-cache = { version = "0.3", optional = true } [dev-dependencies] -tokio = { version = "1.51", features = ["macros", "rt-multi-thread"] } -serial_test = "3.4" +tokio = { version = "1.52", features = ["macros", "rt-multi-thread"] } +serial_test = "3.5" diff --git a/lib/src/auth/mod.rs b/lib/src/auth/mod.rs index ab7c148..7a83ebb 100644 --- a/lib/src/auth/mod.rs +++ b/lib/src/auth/mod.rs @@ -12,6 +12,7 @@ use crate::client::ApiHttpClient; use crate::client::error::ApiClientError; use crate::util::{I128EpochMs, StrEpochMs, StrEpochSec}; pub use claims::Claims; +use core::pin::Pin; use error_stack::Report; use http::Method; pub use import::{UserImportRecord, UserImportRecords}; @@ -349,13 +350,13 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn create_user( &self, user: NewUser, - ) -> impl Future>> + Send { + ) -> Pin>> + Send>> { let client = self.get_client(); let uri = self .get_auth_uri_builder() .build(FirebaseAuthRestApi::CreateUser); - client.send_request_body(uri, Method::POST, user) + Box::pin(client.send_request_body(uri, Method::POST, user)) } /// Get first user that matches given identifier filter @@ -371,14 +372,14 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn get_user( &self, indentifiers: UserIdentifiers, - ) -> impl Future, Report>> + Send { - async move { + ) -> Pin, Report>> + Send>> { + Box::pin(async move { if let Some(users) = self.get_users(indentifiers).await? { return Ok(users.into_iter().next()); } Ok(None) - } + }) } /// Get all users that match a given identifier filter @@ -395,8 +396,9 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn get_users( &self, indentifiers: UserIdentifiers, - ) -> impl Future>, Report>> + Send { - async move { + ) -> Pin>, Report>> + Send>> + { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -409,7 +411,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { .await?; Ok(users.users) - } + }) } /// Fetch all users in batches of `users_per_page`, to progress pass previous page into the method's `prev`. @@ -432,8 +434,9 @@ pub trait FirebaseAuthService: Send + Sync + 'static { &self, users_per_page: usize, prev: Option, - ) -> impl Future, Report>> + Send { - async move { + ) -> Pin, Report>> + Send>> + { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); let mut params = vec![("maxResults".to_string(), users_per_page.clone().to_string())]; @@ -455,15 +458,15 @@ pub trait FirebaseAuthService: Send + Sync + 'static { .await?; Ok(Some(users)) - } + }) } /// Delete user with given ID fn delete_user( &self, uid: String, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -474,7 +477,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { UserId { uid }, ) .await - } + }) } /// Delete all users with given list of IDs @@ -482,8 +485,8 @@ pub trait FirebaseAuthService: Send + Sync + 'static { &self, uids: Vec, force: bool, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -494,7 +497,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { UserIds { uids, force }, ) .await - } + }) } /// Update user with given changes @@ -510,8 +513,8 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn update_user( &self, update: UserUpdate, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -522,7 +525,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { update, ) .await - } + }) } /// Create users in bulk @@ -539,8 +542,8 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn import_users( &self, users: Vec, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -553,7 +556,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { .await?; Ok(()) - } + }) } /// Send email with OOB code action @@ -569,8 +572,8 @@ pub trait FirebaseAuthService: Send + Sync + 'static { fn generate_email_action_link( &self, oob_action: OobCodeAction, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -583,7 +586,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { .await?; Ok(oob_link.oob_link) - } + }) } /// Create session cookie @@ -592,8 +595,8 @@ pub trait FirebaseAuthService: Send + Sync + 'static { &self, id_token: String, expires_in: Duration, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_client(); let uri_builder = self.get_auth_uri_builder(); @@ -611,7 +614,7 @@ pub trait FirebaseAuthService: Send + Sync + 'static { .await?; Ok(session_cookie.session_cookie) - } + }) } } @@ -664,8 +667,10 @@ where fn get_emulator_auth_uri_builder(&self) -> &ApiUriBuilder; /// Delete all users within emulator - fn clear_all_users(&self) -> impl Future>> + Send { - async move { + fn clear_all_users( + &self, + ) -> Pin>> + Send>> { + Box::pin(async move { let client = self.get_emulator_client(); let uri_builder = self.get_emulator_auth_uri_builder(); @@ -677,14 +682,15 @@ where .await?; Ok(()) - } + }) } /// Get current emulator configuration fn get_emulator_configuration( &self, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> + { + Box::pin(async move { let client = self.get_emulator_client(); let uri_builder = self.get_emulator_auth_uri_builder(); @@ -694,15 +700,16 @@ where Method::GET, ) .await - } + }) } /// Update emulator configuration fn patch_emulator_configuration( &self, configuration: EmulatorConfiguration, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> + { + Box::pin(async move { let client = self.get_emulator_client(); let uri_builder = self.get_emulator_auth_uri_builder(); @@ -713,14 +720,14 @@ where configuration, ) .await - } + }) } /// Fetch all OOB codes within emulator fn get_oob_codes( &self, - ) -> impl Future, Report>> + Send { - async move { + ) -> Pin, Report>> + Send>> { + Box::pin(async move { let client = self.get_emulator_client(); let uri_builder = self.get_emulator_auth_uri_builder(); @@ -732,14 +739,15 @@ where .await?; Ok(oob_codes.oob_codes) - } + }) } /// Fetch all SMS codes within emulator fn get_sms_verification_codes( &self, - ) -> impl Future>> + Send { - async move { + ) -> Pin>> + Send>> + { + Box::pin(async move { let client = self.get_emulator_client(); let uri_builder = self.get_emulator_auth_uri_builder(); @@ -749,7 +757,7 @@ where Method::GET, ) .await - } + }) } } diff --git a/lib/src/auth/test.rs b/lib/src/auth/test.rs index 91f5887..89c51ad 100644 --- a/lib/src/auth/test.rs +++ b/lib/src/auth/test.rs @@ -11,13 +11,14 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use serial_test::serial; use std::collections::BTreeMap; +use std::sync::Arc; #[cfg(feature = "tokens")] use time::Duration; use tokio; fn get_auth_service() -> FirebaseAuth { - App::emulated().auth("http://emulator:9099".parse().unwrap()) + App::emulated("http://emulator:9099".into()).auth() } #[derive(Serialize)] @@ -543,8 +544,9 @@ async fn test_create_session_cookie() { .create_session_cookie(id_token, Duration::hours(1)) .await .unwrap(); + let validator = Arc::new(EmulatorValidator); - let claims = EmulatorValidator.validate(&cookie).await.unwrap(); + let claims = validator.validate(cookie).await.unwrap(); let email = claims.get("email").unwrap().as_str().unwrap(); assert_eq!(email, "test@example.com"); diff --git a/lib/src/jwt/mod.rs b/lib/src/jwt/mod.rs index be1d310..655c03d 100644 --- a/lib/src/jwt/mod.rs +++ b/lib/src/jwt/mod.rs @@ -1,10 +1,12 @@ use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use core::future::Future; +use core::pin::Pin; use error_stack::{Report, ResultExt}; use jsonwebtoken::{DecodingKey, Validation, decode, decode_header}; use jsonwebtoken_jwks_cache::{CachedJWKS, TimeoutSpec}; use serde_json::{Value, from_slice}; use std::collections::HashMap; +use std::sync::Arc; use std::time::Duration; use thiserror::Error; @@ -25,14 +27,16 @@ pub enum TokenVerificationError { Internal, } +pub type ClaimsResult = Pin< + Box, Report>> + Send>, +>; + pub trait TokenValidator { /// Validate JWT returning all claims on success - fn validate( - &self, - token: &str, - ) -> impl Future, Report>> + Send + Sync; + fn validate(self: Arc, token: String) -> ClaimsResult; } +#[derive(Clone)] pub struct LiveValidator { project_id: String, issuer: String, @@ -68,30 +72,30 @@ impl LiveValidator { } impl TokenValidator for LiveValidator { - async fn validate( - &self, - token: &str, - ) -> Result, Report> { - let jwks = self - .jwks - .get() - .await - .change_context(TokenVerificationError::Internal)?; - let jwt_header = decode_header(token).change_context(TokenVerificationError::Invalid)?; + fn validate(self: Arc, token: String) -> ClaimsResult { + Box::pin(async move { + let jwks = self + .jwks + .get() + .await + .change_context(TokenVerificationError::Internal)?; + let jwt_header = + decode_header(&token).change_context(TokenVerificationError::Invalid)?; - let jwk: DecodingKey = jwks - .find(&jwt_header.kid.ok_or(TokenVerificationError::MissingKey)?) - .ok_or(TokenVerificationError::MissingKey)? - .try_into() - .change_context(TokenVerificationError::Internal)?; + let jwk: DecodingKey = jwks + .find(&jwt_header.kid.ok_or(TokenVerificationError::MissingKey)?) + .ok_or(TokenVerificationError::MissingKey)? + .try_into() + .change_context(TokenVerificationError::Internal)?; - let mut validator = Validation::new(jwt_header.alg); - validator.set_audience(&[&self.project_id]); - validator.set_issuer(&[&self.issuer]); + let mut validator = Validation::new(jwt_header.alg); + validator.set_audience(&[&self.project_id]); + validator.set_issuer(&[&self.issuer]); - decode::>(token, &jwk, &validator) - .change_context(TokenVerificationError::Invalid) - .map(|t| t.claims) + decode::>(&token, &jwk, &validator) + .change_context(TokenVerificationError::Invalid) + .map(|t| t.claims) + }) } } @@ -99,19 +103,18 @@ impl TokenValidator for LiveValidator { pub struct EmulatorValidator; impl TokenValidator for EmulatorValidator { - async fn validate( - &self, - token: &str, - ) -> Result, Report> { - let header = token - .split(".") - .nth(1) - .ok_or(TokenVerificationError::Invalid)?; + fn validate(self: Arc, token: String) -> ClaimsResult { + Box::pin(async move { + let header = token + .split(".") + .nth(1) + .ok_or(TokenVerificationError::Invalid)?; - let header = URL_SAFE_NO_PAD - .decode(header) - .change_context(TokenVerificationError::Invalid)?; + let header = URL_SAFE_NO_PAD + .decode(header) + .change_context(TokenVerificationError::Invalid)?; - from_slice(&header).change_context(TokenVerificationError::Invalid) + from_slice(&header).change_context(TokenVerificationError::Invalid) + }) } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index aabf7af..8a1ceb6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -8,107 +8,122 @@ pub mod util; use auth::FirebaseAuth; use client::ReqwestApiClient; -use core::marker::PhantomData; use credentials::{GCPCredentialsError, emulator::EmulatorCredentials, get_project_id}; use error_stack::{Report, ResultExt}; -use google_cloud_auth::credentials::{AccessTokenCredentials, Builder}; +use google_cloud_auth::credentials::Builder; pub use google_cloud_auth::credentials::{Credentials, CredentialsProvider}; +use std::sync::Arc; const FIREBASE_AUTH_SCOPES: [&str; 2] = [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", ]; -/// Base privileged manager for Firebase -pub struct App { - credentials: Credentials, - project_id: String, - _credentials_provider: PhantomData, +#[derive(Debug, Clone, thiserror::Error)] +#[error("Failed to create Firebase token validator")] +pub struct FirebaseAppValidatorError; + +pub enum App { + Live { + credentials: Credentials, + project_id: String, + }, + Emulated { + credentials: EmulatorCredentials, + url: String, + }, } -impl App { - /// Firebase app backend by emulator - pub fn emulated() -> Self { - let credentials = EmulatorCredentials::default(); - Self { - project_id: credentials.project_id.clone(), - credentials: credentials.into(), - _credentials_provider: PhantomData, - } - } - - /// Firebase authentication manager for emulator - pub fn auth(&self, emulator_url: String) -> FirebaseAuth { - let client = ReqwestApiClient::new(reqwest::Client::new(), self.credentials.clone()); - - FirebaseAuth::emulated(emulator_url, &self.project_id, client) - } - - /// OIDC token verifier for emulator - #[cfg(feature = "tokens")] - pub fn id_token_verifier(&self) -> impl jwt::TokenValidator { - jwt::EmulatorValidator - } -} - -impl App { - /// Create instance of Firebase app for live project with an explicit project ID, - /// bypassing environment variable and credential header resolution. - pub fn live_with_project_id(project_id: &str) -> Result> { +impl App { + pub async fn live() -> Result> { let credentials: Credentials = Builder::default() .with_scopes(FIREBASE_AUTH_SCOPES) .build_access_token_credentials() .change_context(GCPCredentialsError)? .into(); - Ok(Self { + let project_id = get_project_id(&credentials) + .await + .change_context(GCPCredentialsError)?; + + Ok(Self::Live { credentials, - project_id: project_id.to_string(), - _credentials_provider: PhantomData, + project_id, }) } - /// Create instance of Firebase app for live project - pub async fn live() -> Result> { + pub fn live_with_project_id(project_id: String) -> Result> { let credentials: Credentials = Builder::default() .with_scopes(FIREBASE_AUTH_SCOPES) .build_access_token_credentials() .change_context(GCPCredentialsError)? .into(); - let project_id = get_project_id(&credentials) - .await - .change_context(GCPCredentialsError)?; - - Ok(Self { + Ok(Self::Live { credentials, project_id, - _credentials_provider: PhantomData, }) } - /// Create Firebase authentication manager - pub fn auth(&self) -> FirebaseAuth { - let client = ReqwestApiClient::new(reqwest::Client::new(), self.credentials.clone()); + pub fn emulated(url: String) -> Self { + Self::Emulated { + credentials: EmulatorCredentials::default(), + url, + } + } - FirebaseAuth::live(&self.project_id, client) + pub fn auth(&self) -> FirebaseAuth { + match self { + Self::Live { + credentials, + project_id, + } => { + let client = ReqwestApiClient::new(reqwest::Client::new(), credentials.clone()); + + FirebaseAuth::live(project_id, client) + } + Self::Emulated { credentials, url } => { + let client = + ReqwestApiClient::new(reqwest::Client::new(), credentials.clone().into()); + + FirebaseAuth::emulated(url.clone(), &credentials.project_id, client) + } + } } - /// Create OIDC token verifier #[cfg(feature = "tokens")] - pub fn id_token_verifier( + pub fn token_validator( &self, - ) -> Result> { - jwt::LiveValidator::new_jwt_validator(self.project_id.clone()) - .change_context(credentials::GCPCredentialsError) + ) -> Result, Report> { + match self { + Self::Live { + credentials: _, + project_id, + } => jwt::LiveValidator::new_jwt_validator(project_id.clone()) + .change_context(FirebaseAppValidatorError) + .map(|s| Arc::new(s) as Arc), + Self::Emulated { + credentials: _, + url: _, + } => Ok(Arc::new(jwt::EmulatorValidator) as Arc), + } } - /// Create cookie token verifier #[cfg(feature = "tokens")] - pub fn cookie_token_verifier( + pub fn cookie_validator( &self, - ) -> Result> { - jwt::LiveValidator::new_cookie_validator(self.project_id.clone()) - .change_context(credentials::GCPCredentialsError) + ) -> Result, Report> { + match self { + Self::Live { + credentials: _, + project_id, + } => jwt::LiveValidator::new_cookie_validator(project_id.clone()) + .change_context(FirebaseAppValidatorError) + .map(|s| Arc::new(s) as Arc), + Self::Emulated { + credentials: _, + url: _, + } => Ok(Arc::new(jwt::EmulatorValidator) as Arc), + } } }