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#[derive(Debug, Clone)]
11pub struct SharedState {
12 pub keys: PublicKeysResponse,
13 pub expiry: OffsetDateTime,
14}
15
16
17#[derive(Debug, Deserialize, Serialize)]
19pub struct FirebaseTokenHeader {
20 pub alg: String,
22 pub kid: String,
24}
25
26#[derive(Debug, Deserialize, Serialize)]
28pub struct FirebaseTokenPayload {
29 pub exp: i64,
31 pub iat: i64,
33 pub aud: String,
35 pub iss: String,
37 pub sub: String,
39 pub auth_time: i64,
41}
42
43#[derive(Debug, Deserialize, Clone)]
45pub struct PublicKeysResponse {
46 #[serde(flatten)]
47 pub keys: HashMap<String, String>,
48}
49
50#[derive(Debug, Clone)]
52pub struct FirebaseAuthConfig {
53 pub project_id: String,
55 pub public_keys_url: String,
57}
58
59#[derive(Debug, Clone)]
61pub struct FirebaseAuthUser {
62 pub uid: String,
64 pub issued_at: OffsetDateTime,
66 pub expires_at: OffsetDateTime,
68 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#[derive(Debug)]
98pub struct FirebaseAuth {
99 pub config: FirebaseAuthConfig,
101 pub cached_public_keys: Arc<RwLock<Option<SharedState>>>,
103}
104
105#[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
119impl 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
136impl Error for FirebaseAuthError {
138 fn source(&self) -> Option<&(dyn Error + 'static)> {
139 None
140 }
141}
142
143pub type FirebaseAuthResult<T> = Result<T, FirebaseAuthError>;
145
146pub 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 if self.exp <= current_time.unix_timestamp() {
156 return Err(FirebaseAuthError::TokenExpired);
157 }
158
159 if self.iat >= current_time.unix_timestamp() {
161 return Err(FirebaseAuthError::InvalidTokenFormat);
162 }
163
164 if self.auth_time >= current_time.unix_timestamp() {
166 return Err(FirebaseAuthError::InvalidAuthTime);
167 }
168
169 if self.aud != project_id {
171 return Err(FirebaseAuthError::InvalidAudience);
172 }
173
174 let expected_issuer = format!("https://securetoken.google.com/{}", project_id);
176 if self.iss != expected_issuer {
177 return Err(FirebaseAuthError::InvalidIssuer);
178 }
179
180 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 if let Some(exp) = self.exp {
205 if (exp as i64) <= current_time.unix_timestamp() {
206 return Err(FirebaseAuthError::TokenExpired);
207 }
208 }
209
210 if let Some(iat) = self.iat {
212 if (iat as i64) >= current_time.unix_timestamp() {
213 return Err(FirebaseAuthError::InvalidTokenFormat);
214 }
215 }
216
217 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 if let Some(aud) = &self.aud {
226 if aud != project_id {
227 return Err(FirebaseAuthError::InvalidAudience);
228 }
229 }
230
231 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 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}