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 time::OffsetDateTime;
7use tokio::sync::RwLock;
8
9/// Update FirebaseAuth to use Arc and RwLock for shared state
10#[derive(Debug, Clone)]
11pub struct SharedState {
12    pub keys: PublicKeysResponse,
13    pub expiry: OffsetDateTime,
14}
15
16
17/// Represents the header of a Firebase ID token as specified in the documentation
18#[derive(Debug, Deserialize, Serialize)]
19pub struct FirebaseTokenHeader {
20    /// Algorithm used for the token signature (must be "RS256")
21    pub alg: String,
22    /// Key ID corresponding to the public key used for signature verification
23    pub kid: String,
24}
25
26/// Represents the payload of a Firebase ID token as specified in the documentation
27#[derive(Debug, Deserialize, Serialize)]
28pub struct FirebaseTokenPayload {
29    /// Expiration time (in seconds since UNIX epoch)
30    pub exp: i64,
31    /// Issued at time (in seconds since UNIX epoch)
32    pub iat: i64,
33    /// Audience (must be your Firebase project ID)
34    pub aud: String,
35    /// Issuer (must be "https://securetoken.google.com/<projectId>")
36    pub iss: String,
37    /// Subject (must be the uid of the user or device)
38    pub sub: String,
39    /// Authentication time (must be in the past)
40    pub auth_time: i64,
41}
42
43/// Response from Google's public key endpoint
44#[derive(Debug, Deserialize, Clone)]
45pub struct PublicKeysResponse {
46    #[serde(flatten)]
47    pub keys: HashMap<String, String>,
48}
49
50/// Configuration for Firebase Authentication
51#[derive(Debug, Clone)]
52pub struct FirebaseAuthConfig {
53    /// Firebase project ID
54    pub project_id: String,
55    /// Base URL for public key metadata
56    pub public_keys_url: String,
57}
58
59/// Represents a verified Firebase user
60#[derive(Debug, Clone)]
61pub struct FirebaseAuthUser {
62    /// User's unique ID (from sub claim)
63    pub uid: String,
64    /// Time when the token was issued
65    pub issued_at: OffsetDateTime,
66    /// Time when the token expires
67    pub expires_at: OffsetDateTime,
68    /// Time when the user was authenticated
69    pub auth_time: OffsetDateTime,
70}
71
72
73#[derive(Debug, Serialize, Deserialize)]
74pub struct FirebaseGoogleTokenPayload {
75    pub name: Option<String>,
76    pub picture: Option<String>,
77    pub iss: Option<String>,
78    pub aud: Option<String>,
79    pub auth_time: Option<u64>,
80    pub user_id: Option<String>,
81    pub sub: Option<String>,
82    pub iat: Option<u64>,
83    pub exp: Option<u64>,
84    pub email: Option<String>,
85    pub email_verified: Option<bool>,
86    pub firebase: Option<FirebaseGoogleUserData>,
87}
88
89#[derive(Debug, Serialize, Deserialize)]
90pub struct FirebaseGoogleUserData {
91    pub identities: Option<HashMap<String, Vec<String>>>,
92    pub sign_in_provider: Option<String>,
93}
94
95
96/// Main struct for Firebase Authentication operations
97#[derive(Debug)]
98pub struct FirebaseAuth {
99    /// Configuration for Firebase Authentication
100    pub config: FirebaseAuthConfig,
101    /// Cached public keys with their expiration time
102    pub cached_public_keys: Arc<RwLock<Option<SharedState>>>,
103}
104
105/// Custom error types for Firebase Authentication
106#[derive(Debug)]
107pub enum FirebaseAuthError {
108    InvalidTokenFormat,
109    TokenExpired,
110    InvalidSignature,
111    InvalidIssuer,
112    InvalidAudience,
113    InvalidSubject,
114    InvalidAuthTime,
115    HttpError(String),
116    JwtError(String),
117}
118
119// Implement Display trait for FirebaseAuthError
120impl fmt::Display for FirebaseAuthError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            FirebaseAuthError::InvalidTokenFormat => write!(f, "Invalid token format"),
124            FirebaseAuthError::TokenExpired => write!(f, "Token expired"),
125            FirebaseAuthError::InvalidSignature => write!(f, "Invalid signature"),
126            FirebaseAuthError::InvalidIssuer => write!(f, "Invalid issuer"),
127            FirebaseAuthError::InvalidAudience => write!(f, "Invalid audience"),
128            FirebaseAuthError::InvalidSubject => write!(f, "Invalid subject"),
129            FirebaseAuthError::InvalidAuthTime => write!(f, "Invalid authentication time"),
130            FirebaseAuthError::HttpError(msg) => write!(f, "HTTP request failed: {}", msg),
131            FirebaseAuthError::JwtError(msg) => write!(f, "JWT error: {}", msg),
132        }
133    }
134}
135
136// Implement Error trait for FirebaseAuthError
137impl Error for FirebaseAuthError {
138    fn source(&self) -> Option<&(dyn Error + 'static)> {
139        None
140    }
141}
142
143// Type alias for Result with FirebaseAuthError
144pub type FirebaseAuthResult<T> = Result<T, FirebaseAuthError>;
145
146/// Trait for token verification
147pub trait TokenVerifier {
148    fn verify(&self, project_id: &str, current_time: OffsetDateTime) -> FirebaseAuthResult<()>;
149    fn to_auth_user(&self) -> FirebaseAuthUser;
150}
151
152impl TokenVerifier for FirebaseTokenPayload {
153    fn verify(&self, project_id: &str, current_time: OffsetDateTime) -> FirebaseAuthResult<()> {
154        // Verify expiration time
155        if self.exp <= current_time.unix_timestamp() {
156            return Err(FirebaseAuthError::TokenExpired);
157        }
158
159        // Verify issued at time
160        if self.iat >= current_time.unix_timestamp() {
161            return Err(FirebaseAuthError::InvalidTokenFormat);
162        }
163
164        // Verify authentication time
165        if self.auth_time >= current_time.unix_timestamp() {
166            return Err(FirebaseAuthError::InvalidAuthTime);
167        }
168
169        // Verify audience
170        if self.aud != project_id {
171            return Err(FirebaseAuthError::InvalidAudience);
172        }
173
174        // Verify issuer
175        let expected_issuer = format!("https://securetoken.google.com/{}", project_id);
176        if self.iss != expected_issuer {
177            return Err(FirebaseAuthError::InvalidIssuer);
178        }
179
180        // Verify subject
181        if self.sub.is_empty() {
182            return Err(FirebaseAuthError::InvalidSubject);
183        }
184
185        Ok(())
186    }
187
188    fn to_auth_user(&self) -> FirebaseAuthUser {
189        FirebaseAuthUser {
190            uid: self.sub.clone(),
191            issued_at: OffsetDateTime::from_unix_timestamp(self.iat)
192                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
193            expires_at: OffsetDateTime::from_unix_timestamp(self.exp)
194                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
195            auth_time: OffsetDateTime::from_unix_timestamp(self.auth_time)
196                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
197        }
198    }
199}
200
201impl TokenVerifier for FirebaseGoogleTokenPayload {
202    fn verify(&self, project_id: &str, current_time: OffsetDateTime) -> FirebaseAuthResult<()> {
203        // Verify expiration time
204        if let Some(exp) = self.exp {
205            if (exp as i64) <= current_time.unix_timestamp() {
206                return Err(FirebaseAuthError::TokenExpired);
207            }
208        }
209
210        // Verify issued at time
211        if let Some(iat) = self.iat {
212            if (iat as i64) >= current_time.unix_timestamp() {
213                return Err(FirebaseAuthError::InvalidTokenFormat);
214            }
215        }
216
217        // Verify authentication time
218        if let Some(auth_time) = self.auth_time {
219            if (auth_time as i64) >= current_time.unix_timestamp() {
220                return Err(FirebaseAuthError::InvalidAuthTime);
221            }
222        }
223
224        // Verify audience
225        if let Some(aud) = &self.aud {
226            if aud != project_id {
227                return Err(FirebaseAuthError::InvalidAudience);
228            }
229        }
230
231        // Verify issuer
232        if let Some(iss) = &self.iss {
233            let expected_issuer = format!("https://securetoken.google.com/{}", project_id);
234            if iss != &expected_issuer {
235                return Err(FirebaseAuthError::InvalidIssuer);
236            }
237        }
238
239        // Verify subject
240        if let Some(sub) = &self.sub {
241            if sub.is_empty() {
242                return Err(FirebaseAuthError::InvalidSubject);
243            }
244        } else {
245            return Err(FirebaseAuthError::InvalidSubject);
246        }
247
248        Ok(())
249    }
250
251    fn to_auth_user(&self) -> FirebaseAuthUser {
252        FirebaseAuthUser {
253            uid: self.sub.clone().unwrap_or_default(),
254            issued_at: OffsetDateTime::from_unix_timestamp(self.iat.unwrap_or(0) as i64)
255                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
256            expires_at: OffsetDateTime::from_unix_timestamp(self.exp.unwrap_or(0) as i64)
257                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
258            auth_time: OffsetDateTime::from_unix_timestamp(self.auth_time.unwrap_or(0) as i64)
259                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
260        }
261    }
262}