From ad13cf19f9908d0e87f4f27104627ef19458d82d Mon Sep 17 00:00:00 2001 From: chodak166 Date: Tue, 20 Jan 2026 22:15:21 +0100 Subject: [PATCH] WIP: basic API --- config.toml | 4 +- lib/Cargo.toml | 1 + lib/src/auth/infrastructure/jwt.rs | 88 +++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/config.toml b/config.toml index 13e61f8..347ac25 100644 --- a/config.toml +++ b/config.toml @@ -1,9 +1,9 @@ +log_level = "trace" + [listen] host = "0.0.0.0" port = 3000 -log_level = "trace" - [auth] firebase_project_id = "test-project" firebase_emulator_url = "http://192.168.1.23:9099" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 27487b7..a765b95 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -21,6 +21,7 @@ sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "chrono", "mi futures = "0.3.31" jsonwebtoken = "9.3" reqwest = { version = "0.12", features = ["json"] } +base64 = "0.22" [dev-dependencies] mockall = "0.14.0" diff --git a/lib/src/auth/infrastructure/jwt.rs b/lib/src/auth/infrastructure/jwt.rs index c70488c..e0e7674 100644 --- a/lib/src/auth/infrastructure/jwt.rs +++ b/lib/src/auth/infrastructure/jwt.rs @@ -1,9 +1,8 @@ use crate::auth::domain::AuthClaims; use crate::auth::traits::Authenticator; use crate::common::errors::AuthError; -use jsonwebtoken::{ - Algorithm, DecodingKey, Validation, dangerous_insecure_decode, decode, decode_header, -}; +use base64::Engine; +use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; @@ -90,6 +89,71 @@ impl JwtAuthenticator { } } + fn decode_emulator_token(&self, token: &str) -> Result { + let parts: Vec<&str> = token.split('.').collect(); + if parts.len() != 3 { + return Err(AuthError::InvalidToken); + } + + let header_json = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[0]) + .map_err(|e| { + eprintln!("Failed to decode emulated header: {}", e); + AuthError::InvalidToken + })?; + let header: serde_json::Value = + serde_json::from_slice(&header_json).map_err(|_| AuthError::InvalidToken)?; + + if header.get("alg").and_then(|v| v.as_str()) != Some("none") { + return Err(AuthError::InvalidToken); + } + + let payload_json = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[1]) + .map_err(|e| { + eprintln!("Failed to decode emulated payload: {}", e); + AuthError::InvalidToken + })?; + let claims: FirebaseClaims = + serde_json::from_slice(&payload_json).map_err(|_| AuthError::InvalidToken)?; + + let now = chrono::Utc::now().timestamp(); + + if claims.exp < now { + return Err(AuthError::AuthenticationFailed("Token expired".to_string())); + } + + if claims.aud != self.audience { + return Err(AuthError::AuthenticationFailed( + "Invalid audience".to_string(), + )); + } + + if claims.iss != self.issuer { + return Err(AuthError::AuthenticationFailed( + "Invalid issuer".to_string(), + )); + } + + let mut auth_claims = AuthClaims::new(claims.user_id) + .with_expiration(claims.exp) + .with_iat(claims.iat); + + if let Some(email) = claims.email { + auth_claims = auth_claims.with_email(email); + } + + if let Some(ref firebase) = claims.firebase { + if let Some(ref provider) = firebase.sign_in_provider { + auth_claims = auth_claims.with_role(format!("auth:{}", provider)); + } + } + + auth_claims = auth_claims.with_role("authenticated"); + + Ok(auth_claims) + } + async fn get_public_keys(&self) -> Result, AuthError> { let cache = self.key_cache.read().await; if let Some(ref cached) = *cache { @@ -160,10 +224,20 @@ impl Authenticator for JwtAuthenticator { )); } - let header = decode_header(token).map_err(|e| { - tracing::debug!("Failed to decode header: {}", e.to_string()); - AuthError::InvalidToken - })?; + let header_result = decode_header(token); + + let header = match header_result { + Ok(h) => h, + Err(e) => { + if self.emulator_url.is_some() { + if let Ok(claims) = self.decode_emulator_token(token) { + return Ok(claims); + } + } + tracing::debug!("Failed to decode header: {}", e.to_string()); + return Err(AuthError::InvalidToken); + } + }; let kid = header .kid