fire_auth_token/
structs.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::error::Error;
4use std::fmt;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8/// Shared state for caching public keys
9#[derive(Debug, Clone)]
10pub struct SharedState {
11    pub keys: PublicKeysResponse,
12    pub expiry: i64,  // Unix timestamp
13}
14
15/// Firebase normal user authentication payload
16#[derive(Debug, Serialize, Deserialize)]
17pub struct FirebaseAuthUser {
18    pub exp: i64,
19    pub iat: i64,
20    pub aud: String,
21    pub iss: String,
22    pub sub: String,
23    pub auth_time: i64,
24}
25
26/// Firebase Google user authentication payload
27#[derive(Debug, Serialize, Deserialize)]
28pub struct FirebaseAuthGoogleUser {
29    pub name: Option<String>,
30    pub picture: Option<String>,
31    pub iss: Option<String>,
32    pub aud: Option<String>,
33    pub auth_time: Option<i64>,
34    pub user_id: Option<String>,
35    pub sub: Option<String>,
36    pub iat: Option<i64>,
37    pub exp: Option<i64>,
38    pub email: Option<String>,
39    pub email_verified: Option<bool>,
40    pub firebase: Option<FirebaseGoogleUserData>,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
44pub struct FirebaseGoogleUserData {
45    pub identities: Option<HashMap<String, Vec<String>>>,
46    pub sign_in_provider: Option<String>,
47}
48
49/// Represents the header of a Firebase ID token as specified in the documentation
50#[derive(Debug, Deserialize, Serialize)]
51pub struct FirebaseTokenHeader {
52    /// Algorithm used for the token signature (must be "RS256")
53    pub alg: String,
54    /// Key ID corresponding to the public key used for signature verification
55    pub kid: String,
56}
57
58/// Response from Google's public key endpoint
59#[derive(Debug, Deserialize, Clone)]
60pub struct PublicKeysResponse {
61    #[serde(flatten)]
62    pub keys: HashMap<String, String>,
63}
64
65/// Configuration for Firebase Authentication
66#[derive(Debug, Clone)]
67pub struct FirebaseAuthConfig {
68    /// Firebase project ID
69    pub project_id: String,
70    /// Base URL for public key metadata
71    pub public_keys_url: String,
72}
73
74/// Main struct for Firebase Authentication operations
75#[derive(Debug)]
76pub struct FirebaseAuth {
77    /// Configuration for Firebase Authentication
78    pub config: FirebaseAuthConfig,
79    /// Cached public keys with their expiration time
80    pub cached_public_keys: Arc<RwLock<Option<SharedState>>>,
81}
82
83/// Custom error types for Firebase Authentication
84#[derive(Debug)]
85pub enum FirebaseAuthError {
86    InvalidTokenFormat,
87    TokenExpired,
88    InvalidSignature,
89    InvalidIssuer,
90    InvalidAudience,
91    InvalidSubject,
92    InvalidAuthTime,
93    HttpError(String),
94    JwtError(String),
95}
96
97// Implement Display trait for FirebaseAuthError
98impl fmt::Display for FirebaseAuthError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            FirebaseAuthError::InvalidTokenFormat => write!(f, "Invalid token format"),
102            FirebaseAuthError::TokenExpired => write!(f, "Token expired"),
103            FirebaseAuthError::InvalidSignature => write!(f, "Invalid signature"),
104            FirebaseAuthError::InvalidIssuer => write!(f, "Invalid issuer"),
105            FirebaseAuthError::InvalidAudience => write!(f, "Invalid audience"),
106            FirebaseAuthError::InvalidSubject => write!(f, "Invalid subject"),
107            FirebaseAuthError::InvalidAuthTime => write!(f, "Invalid authentication time"),
108            FirebaseAuthError::HttpError(msg) => write!(f, "HTTP request failed: {}", msg),
109            FirebaseAuthError::JwtError(msg) => write!(f, "JWT error: {}", msg),
110        }
111    }
112}
113
114// Implement Error trait for FirebaseAuthError
115impl Error for FirebaseAuthError {
116    fn source(&self) -> Option<&(dyn Error + 'static)> {
117        None
118    }
119}
120
121// Type alias for Result with FirebaseAuthError
122pub type FirebaseAuthResult<T> = Result<T, FirebaseAuthError>;
123
124/// Trait for token verification
125pub trait TokenVerifier {
126    fn verify(&self, project_id: &str, current_time: i64) -> FirebaseAuthResult<()>;
127}
128
129impl TokenVerifier for FirebaseAuthUser {
130    fn verify(&self, project_id: &str, current_time: i64) -> FirebaseAuthResult<()> {
131        if self.exp <= current_time {
132            return Err(FirebaseAuthError::TokenExpired);
133        }
134
135        if self.iat >= current_time {
136            return Err(FirebaseAuthError::InvalidTokenFormat);
137        }
138
139        if self.auth_time >= current_time {
140            return Err(FirebaseAuthError::InvalidAuthTime);
141        }
142
143        if self.aud != project_id {
144            return Err(FirebaseAuthError::InvalidAudience);
145        }
146
147        let expected_issuer = format!("https://securetoken.google.com/{}", project_id);
148        if self.iss != expected_issuer {
149            return Err(FirebaseAuthError::InvalidIssuer);
150        }
151
152        if self.sub.is_empty() {
153            return Err(FirebaseAuthError::InvalidSubject);
154        }
155
156        Ok(())
157    }
158}
159
160impl TokenVerifier for FirebaseAuthGoogleUser {
161    fn verify(&self, project_id: &str, current_time: i64) -> FirebaseAuthResult<()> {
162        if let Some(exp) = self.exp {
163            if exp <= current_time {
164                return Err(FirebaseAuthError::TokenExpired);
165            }
166        }
167
168        if let Some(iat) = self.iat {
169            if iat >= current_time {
170                return Err(FirebaseAuthError::InvalidTokenFormat);
171            }
172        }
173
174        if let Some(auth_time) = self.auth_time {
175            if auth_time >= current_time {
176                return Err(FirebaseAuthError::InvalidAuthTime);
177            }
178        }
179
180        if let Some(aud) = &self.aud {
181            if aud != project_id {
182                return Err(FirebaseAuthError::InvalidAudience);
183            }
184        }
185
186        if let Some(iss) = &self.iss {
187            let expected_issuer = format!("https://securetoken.google.com/{}", project_id);
188            if iss != &expected_issuer {
189                return Err(FirebaseAuthError::InvalidIssuer);
190            }
191        }
192
193        if let Some(sub) = &self.sub {
194            if sub.is_empty() {
195                return Err(FirebaseAuthError::InvalidSubject);
196            }
197        } else {
198            return Err(FirebaseAuthError::InvalidSubject);
199        }
200
201        Ok(())
202    }
203}