firebase_rs_sdk/auth/
model.rs

1use crate::app::FirebaseApp;
2use crate::auth::error::{AuthError, AuthResult};
3use crate::auth::token_manager::{TokenManager, TokenUpdate};
4use crate::auth::types::MultiFactorInfo;
5use crate::util::PartialObserver;
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10
11#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
12pub struct UserInfo {
13    pub uid: String,
14    pub display_name: Option<String>,
15    pub email: Option<String>,
16    pub phone_number: Option<String>,
17    pub photo_url: Option<String>,
18    pub provider_id: String,
19}
20
21#[derive(Clone, Debug)]
22pub struct User {
23    app: FirebaseApp,
24    info: UserInfo,
25    email_verified: bool,
26    is_anonymous: bool,
27    token_manager: TokenManager,
28    mfa_factors: Arc<Mutex<Vec<MultiFactorInfo>>>,
29}
30
31impl User {
32    /// Creates a new user bound to the given app with profile information.
33    pub fn new(app: FirebaseApp, info: UserInfo) -> Self {
34        Self {
35            app,
36            info,
37            email_verified: false,
38            is_anonymous: false,
39            token_manager: TokenManager::default(),
40            mfa_factors: Arc::new(Mutex::new(Vec::new())),
41        }
42    }
43
44    /// Returns the owning `FirebaseApp` for the user.
45    pub fn app(&self) -> &FirebaseApp {
46        &self.app
47    }
48
49    /// Indicates whether the user signed in anonymously.
50    pub fn is_anonymous(&self) -> bool {
51        self.is_anonymous
52    }
53
54    /// Flags the user as anonymous or regular.
55    pub fn set_anonymous(&mut self, anonymous: bool) {
56        self.is_anonymous = anonymous;
57    }
58
59    /// Returns the stable Firebase UID for the user.
60    pub fn uid(&self) -> &str {
61        &self.info.uid
62    }
63
64    /// Indicates whether the user's email has been verified.
65    pub fn email_verified(&self) -> bool {
66        self.email_verified
67    }
68
69    /// Returns the refresh token issued for this user, if present.
70    pub fn refresh_token(&self) -> Option<String> {
71        self.token_manager.refresh_token()
72    }
73
74    /// Returns the cached ID token or an error if none is available.
75    pub fn get_id_token(&self, _force_refresh: bool) -> AuthResult<String> {
76        self.token_manager
77            .access_token()
78            .ok_or_else(|| AuthError::InvalidCredential("Missing ID token".into()))
79    }
80
81    /// Exposes the underlying token manager.
82    pub fn token_manager(&self) -> &TokenManager {
83        &self.token_manager
84    }
85
86    /// Updates the cached tokens with fresh credentials from the backend.
87    pub fn update_tokens(
88        &self,
89        access_token: Option<String>,
90        refresh_token: Option<String>,
91        expires_in: Option<Duration>,
92    ) {
93        let update = TokenUpdate::new(access_token, refresh_token, expires_in);
94        self.token_manager.update(update);
95    }
96
97    /// Returns the immutable `UserInfo` profile snapshot.
98    pub fn info(&self) -> &UserInfo {
99        &self.info
100    }
101
102    pub(crate) fn set_email_verified(&mut self, value: bool) {
103        self.email_verified = value;
104    }
105
106    pub(crate) fn set_mfa_info(&self, factors: Vec<MultiFactorInfo>) {
107        *self.mfa_factors.lock().unwrap() = factors;
108    }
109
110    pub fn mfa_info(&self) -> Vec<MultiFactorInfo> {
111        self.mfa_factors.lock().unwrap().clone()
112    }
113}
114
115#[derive(Clone, Debug)]
116pub struct UserCredential {
117    pub user: Arc<User>,
118    pub provider_id: Option<String>,
119    pub operation_type: Option<String>,
120}
121
122#[derive(Debug, Clone)]
123pub struct AuthCredential {
124    pub provider_id: String,
125    pub sign_in_method: String,
126    pub token_response: serde_json::Value,
127}
128
129impl AuthCredential {
130    /// Serializes the credential into a JSON value matching the Firebase JS SDK shape.
131    pub fn to_json(&self) -> Value {
132        json!({
133            "providerId": self.provider_id,
134            "signInMethod": self.sign_in_method,
135            "tokenResponse": self.token_response,
136        })
137    }
138
139    /// Serializes the credential into a JSON string.
140    pub fn to_json_string(&self) -> AuthResult<String> {
141        serde_json::to_string(&self.to_json())
142            .map_err(|err| AuthError::InvalidCredential(err.to_string()))
143    }
144
145    /// Reconstructs a credential from a JSON value previously produced via [`to_json`].
146    pub fn from_json(value: Value) -> AuthResult<Self> {
147        let provider_id = value
148            .get("providerId")
149            .and_then(Value::as_str)
150            .ok_or_else(|| {
151                AuthError::InvalidCredential("Credential JSON missing providerId".into())
152            })?
153            .to_string();
154
155        let sign_in_method = value
156            .get("signInMethod")
157            .and_then(Value::as_str)
158            .ok_or_else(|| {
159                AuthError::InvalidCredential("Credential JSON missing signInMethod".into())
160            })?
161            .to_string();
162
163        let token_response = value
164            .get("tokenResponse")
165            .cloned()
166            .unwrap_or_else(|| json!({}));
167
168        Ok(Self {
169            provider_id,
170            sign_in_method,
171            token_response,
172        })
173    }
174
175    /// Reconstructs a credential from its JSON string representation.
176    pub fn from_json_str(data: &str) -> AuthResult<Self> {
177        let value: Value = serde_json::from_str(data)
178            .map_err(|err| AuthError::InvalidCredential(err.to_string()))?;
179        Self::from_json(value)
180    }
181}
182
183#[derive(Debug, Clone, Default)]
184pub struct AuthConfig {
185    pub api_key: Option<String>,
186    pub identity_toolkit_endpoint: Option<String>,
187    pub secure_token_endpoint: Option<String>,
188}
189
190#[derive(Clone)]
191pub struct EmailAuthProvider;
192
193impl EmailAuthProvider {
194    pub const PROVIDER_ID: &'static str = "password";
195
196    /// Builds an auth credential suitable for email/password sign-in flows.
197    pub fn credential(email: &str, password: &str) -> AuthCredential {
198        AuthCredential {
199            provider_id: Self::PROVIDER_ID.to_string(),
200            sign_in_method: Self::PROVIDER_ID.to_string(),
201            token_response: json!({
202                "email": email,
203                "password": password,
204                "returnSecureToken": true,
205            }),
206        }
207    }
208}
209
210#[derive(Default)]
211pub struct AuthStateListeners {
212    observers: Mutex<Vec<PartialObserver<Arc<User>>>>,
213}
214
215impl AuthStateListeners {
216    /// Registers a new observer to receive auth state changes.
217    pub fn add_observer(&self, observer: PartialObserver<Arc<User>>) {
218        self.observers.lock().unwrap().push(observer);
219    }
220
221    /// Notifies all observers with the provided user snapshot.
222    pub fn notify(&self, user: Arc<User>) {
223        for observer in self.observers.lock().unwrap().iter() {
224            if let Some(next) = observer.next.clone() {
225                next(&user);
226            }
227        }
228    }
229}
230
231#[derive(Debug, Serialize, Clone)]
232pub struct SignInWithPasswordRequest {
233    pub email: String,
234    pub password: String,
235    #[serde(rename = "returnSecureToken")]
236    pub return_secure_token: bool,
237}
238
239#[derive(Debug, Deserialize, Clone)]
240pub struct SignInWithPasswordResponse {
241    #[serde(rename = "idToken")]
242    pub id_token: Option<String>,
243    #[serde(rename = "refreshToken")]
244    pub refresh_token: Option<String>,
245    #[serde(rename = "localId")]
246    pub local_id: String,
247    pub email: String,
248    #[serde(rename = "expiresIn")]
249    pub expires_in: Option<String>,
250    #[serde(rename = "mfaPendingCredential")]
251    pub mfa_pending_credential: Option<String>,
252    #[serde(rename = "mfaInfo")]
253    pub mfa_info: Option<Vec<MfaEnrollmentInfo>>,
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn auth_credential_json_roundtrip() {
262        let credential = AuthCredential {
263            provider_id: "custom-provider".into(),
264            sign_in_method: "custom-provider".into(),
265            token_response: json!({ "idToken": "abc" }),
266        };
267
268        let json_value = credential.to_json();
269        let restored = AuthCredential::from_json(json_value.clone()).unwrap();
270        assert_eq!(restored.provider_id, "custom-provider");
271        assert_eq!(restored.sign_in_method, "custom-provider");
272        assert_eq!(restored.token_response, json_value["tokenResponse"]);
273
274        let json_string = credential.to_json_string().unwrap();
275        let restored_from_str = AuthCredential::from_json_str(&json_string).unwrap();
276        assert_eq!(restored_from_str.provider_id, "custom-provider");
277        assert_eq!(restored_from_str.sign_in_method, "custom-provider");
278    }
279}
280
281#[derive(Debug, Serialize, Clone, Default)]
282pub struct SignUpRequest {
283    #[serde(rename = "idToken", skip_serializing_if = "Option::is_none")]
284    pub id_token: Option<String>,
285    #[serde(rename = "returnSecureToken", skip_serializing_if = "Option::is_none")]
286    pub return_secure_token: Option<bool>,
287    #[serde(rename = "email", skip_serializing_if = "Option::is_none")]
288    pub email: Option<String>,
289    #[serde(rename = "password", skip_serializing_if = "Option::is_none")]
290    pub password: Option<String>,
291    #[serde(rename = "tenantId", skip_serializing_if = "Option::is_none")]
292    pub tenant_id: Option<String>,
293    #[serde(rename = "captchaResponse", skip_serializing_if = "Option::is_none")]
294    pub captcha_response: Option<String>,
295    #[serde(rename = "clientType", skip_serializing_if = "Option::is_none")]
296    pub client_type: Option<String>,
297    #[serde(rename = "recaptchaVersion", skip_serializing_if = "Option::is_none")]
298    pub recaptcha_version: Option<String>,
299}
300
301#[derive(Debug, Deserialize, Clone)]
302pub struct SignUpResponse {
303    #[serde(rename = "idToken")]
304    pub id_token: Option<String>,
305    #[serde(rename = "refreshToken")]
306    pub refresh_token: Option<String>,
307    #[serde(rename = "localId")]
308    pub local_id: Option<String>,
309    #[serde(rename = "email")]
310    pub email: Option<String>,
311    #[serde(rename = "displayName")]
312    pub display_name: Option<String>,
313    #[serde(rename = "expiresIn")]
314    pub expires_in: Option<String>,
315    #[serde(rename = "isNewUser")]
316    pub is_new_user: Option<bool>,
317    #[serde(rename = "mfaPendingCredential")]
318    pub mfa_pending_credential: Option<String>,
319    #[serde(rename = "mfaInfo")]
320    pub mfa_info: Option<Vec<MfaEnrollmentInfo>>,
321}
322
323#[derive(Debug, Serialize, Clone)]
324pub struct SignInWithCustomTokenRequest {
325    #[serde(rename = "token")]
326    pub token: String,
327    #[serde(rename = "returnSecureToken")]
328    pub return_secure_token: bool,
329}
330
331#[derive(Debug, Deserialize, Clone)]
332pub struct SignInWithCustomTokenResponse {
333    #[serde(rename = "idToken")]
334    pub id_token: Option<String>,
335    #[serde(rename = "refreshToken")]
336    pub refresh_token: Option<String>,
337    #[serde(rename = "localId")]
338    pub local_id: Option<String>,
339    #[serde(rename = "email")]
340    pub email: Option<String>,
341    #[serde(rename = "expiresIn")]
342    pub expires_in: Option<String>,
343    #[serde(rename = "isNewUser")]
344    pub is_new_user: Option<bool>,
345    #[serde(rename = "mfaPendingCredential")]
346    pub mfa_pending_credential: Option<String>,
347    #[serde(rename = "mfaInfo")]
348    pub mfa_info: Option<Vec<MfaEnrollmentInfo>>,
349}
350
351#[derive(Debug, Serialize, Clone)]
352pub struct SignInWithEmailLinkRequest {
353    #[serde(rename = "email")]
354    pub email: String,
355    #[serde(rename = "oobCode")]
356    pub oob_code: String,
357    #[serde(rename = "returnSecureToken")]
358    pub return_secure_token: bool,
359    #[serde(rename = "tenantId", skip_serializing_if = "Option::is_none")]
360    pub tenant_id: Option<String>,
361    #[serde(rename = "idToken", skip_serializing_if = "Option::is_none")]
362    pub id_token: Option<String>,
363}
364
365#[derive(Debug, Deserialize, Clone)]
366pub struct SignInWithEmailLinkResponse {
367    #[serde(rename = "idToken")]
368    pub id_token: Option<String>,
369    #[serde(rename = "refreshToken")]
370    pub refresh_token: Option<String>,
371    #[serde(rename = "localId")]
372    pub local_id: Option<String>,
373    #[serde(rename = "email")]
374    pub email: Option<String>,
375    #[serde(rename = "expiresIn")]
376    pub expires_in: Option<String>,
377    #[serde(rename = "isNewUser")]
378    pub is_new_user: Option<bool>,
379    #[serde(rename = "mfaPendingCredential")]
380    pub mfa_pending_credential: Option<String>,
381    #[serde(rename = "mfaInfo")]
382    pub mfa_info: Option<Vec<MfaEnrollmentInfo>>,
383}
384
385#[derive(Debug, Clone, Deserialize)]
386pub struct ProviderUserInfo {
387    #[serde(rename = "providerId")]
388    pub provider_id: Option<String>,
389    #[serde(rename = "rawId")]
390    pub raw_id: Option<String>,
391    #[serde(rename = "email")]
392    pub email: Option<String>,
393    #[serde(rename = "displayName")]
394    pub display_name: Option<String>,
395    #[serde(rename = "photoUrl")]
396    pub photo_url: Option<String>,
397    #[serde(rename = "phoneNumber")]
398    pub phone_number: Option<String>,
399}
400
401#[derive(Debug, Clone, Deserialize, Default)]
402pub struct MfaEnrollmentInfo {
403    #[serde(rename = "mfaEnrollmentId")]
404    pub mfa_enrollment_id: Option<String>,
405    #[serde(rename = "displayName")]
406    pub display_name: Option<String>,
407    #[serde(rename = "phoneInfo")]
408    pub phone_info: Option<String>,
409    #[serde(rename = "totpInfo")]
410    pub totp_info: Option<Value>,
411    #[serde(rename = "webauthnInfo")]
412    pub webauthn_info: Option<Value>,
413    #[serde(rename = "enrolledAt")]
414    pub enrolled_at: Option<Value>,
415    #[serde(rename = "factorId")]
416    pub factor_id: Option<String>,
417}
418
419#[derive(Debug, Clone, Deserialize)]
420pub struct AccountInfoUser {
421    #[serde(rename = "localId")]
422    pub local_id: Option<String>,
423    #[serde(rename = "displayName")]
424    pub display_name: Option<String>,
425    #[serde(rename = "photoUrl")]
426    pub photo_url: Option<String>,
427    #[serde(rename = "email")]
428    pub email: Option<String>,
429    #[serde(rename = "emailVerified")]
430    pub email_verified: Option<bool>,
431    #[serde(rename = "phoneNumber")]
432    pub phone_number: Option<String>,
433    #[serde(rename = "providerUserInfo")]
434    pub provider_user_info: Option<Vec<ProviderUserInfo>>,
435    #[serde(rename = "mfaInfo")]
436    pub mfa_info: Option<Vec<MfaEnrollmentInfo>>,
437}
438
439#[derive(Debug, Clone, Deserialize)]
440pub struct GetAccountInfoResponse {
441    pub users: Vec<AccountInfoUser>,
442}