rs_firebase_admin_sdk/auth/
mod.rs

1//! Firebase Identity Provider management interface
2
3#[cfg(test)]
4mod test;
5
6pub mod claims;
7pub mod import;
8pub mod oob_code;
9
10use crate::api_uri::{ApiUriBuilder, FirebaseAuthEmulatorRestApi, FirebaseAuthRestApi};
11use crate::client::ApiHttpClient;
12use crate::client::error::ApiClientError;
13use crate::util::{I128EpochMs, StrEpochMs, StrEpochSec};
14pub use claims::Claims;
15use error_stack::Report;
16use http::Method;
17pub use import::{UserImportRecord, UserImportRecords};
18use oob_code::{OobCodeAction, OobCodeActionLink, OobCodeActionType};
19use serde::{Deserialize, Serialize};
20use std::collections::BTreeMap;
21use std::future::Future;
22use std::vec;
23use time::{Duration, OffsetDateTime};
24
25const FIREBASE_AUTH_REST_AUTHORITY: &str = "identitytoolkit.googleapis.com";
26
27#[derive(Serialize, Debug, Clone, Default)]
28#[serde(rename_all = "camelCase")]
29pub struct NewUser {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    #[serde(rename = "localId")]
32    pub uid: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub email: Option<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub password: Option<String>,
37}
38
39impl NewUser {
40    pub fn email_and_password(email: String, password: String) -> Self {
41        Self {
42            uid: None,
43            email: Some(email),
44            password: Some(password),
45        }
46    }
47}
48
49#[derive(Deserialize, Debug, Clone)]
50#[serde(rename_all = "camelCase")]
51pub struct ProviderUserInfo {
52    pub provider_id: String,
53    pub email: Option<String>,
54    pub phone_number: Option<String>,
55    pub federated_id: Option<String>,
56    pub raw_id: String,
57}
58
59#[derive(Deserialize, Debug, Clone)]
60#[serde(rename_all = "camelCase")]
61pub struct User {
62    #[serde(rename = "localId")]
63    pub uid: String,
64    pub email: Option<String>,
65    pub display_name: Option<String>,
66    pub photo_url: Option<String>,
67    pub phone_number: Option<String>,
68    pub last_login_at: Option<StrEpochMs>,
69    pub email_verified: Option<bool>,
70    pub password_updated_at: Option<I128EpochMs>,
71    pub valid_since: Option<StrEpochSec>,
72    pub created_at: Option<StrEpochMs>,
73    pub salt: Option<String>,
74    pub password_hash: Option<String>,
75    pub provider_user_info: Option<Vec<ProviderUserInfo>>,
76    #[serde(rename = "customAttributes")]
77    pub custom_claims: Option<Claims>,
78    pub disabled: Option<bool>,
79}
80
81#[derive(Deserialize, Debug, Clone)]
82#[serde(rename_all = "camelCase")]
83pub struct Users {
84    pub users: Option<Vec<User>>,
85}
86
87#[derive(Deserialize, Debug, Clone)]
88#[serde(rename_all = "camelCase")]
89pub struct UserList {
90    #[serde(default)]
91    pub users: Vec<User>,
92    pub next_page_token: Option<String>,
93}
94
95#[derive(Serialize, Debug, Clone)]
96#[serde(rename_all = "camelCase")]
97pub struct CreateSessionCookie {
98    pub id_token: String,
99    pub valid_duration: i64,
100}
101
102#[derive(Deserialize, Debug, Clone)]
103#[serde(rename_all = "camelCase")]
104pub struct SessionCookie {
105    pub session_cookie: String,
106}
107
108#[derive(Serialize, Debug, Clone)]
109#[serde(rename_all = "camelCase")]
110pub struct FederatedUserId {
111    pub provider_id: String,
112    pub raw_id: String,
113}
114
115#[derive(Serialize, Debug, Clone, Default)]
116#[serde(rename_all = "camelCase")]
117pub struct UserIdentifiers {
118    #[serde(skip_serializing_if = "Option::is_none")]
119    #[serde(rename = "localId")]
120    pub uid: Option<Vec<String>>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub email: Option<Vec<String>>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub phone_number: Option<Vec<String>>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub federated_user_id: Option<FederatedUserId>,
127}
128
129impl UserIdentifiers {
130    pub fn builder() -> UserIdentifiersBuilder {
131        UserIdentifiersBuilder::default()
132    }
133}
134
135#[derive(Clone, Default)]
136pub struct UserIdentifiersBuilder {
137    ids: UserIdentifiers,
138}
139
140impl UserIdentifiersBuilder {
141    pub fn with_email(mut self, email: String) -> Self {
142        match &mut self.ids.email {
143            Some(email_vec) => email_vec.push(email),
144            None => self.ids.email = Some(vec![email]),
145        };
146
147        self
148    }
149
150    pub fn with_uid(mut self, uid: String) -> Self {
151        match &mut self.ids.uid {
152            Some(uid_vec) => uid_vec.push(uid),
153            None => self.ids.uid = Some(vec![uid]),
154        };
155
156        self
157    }
158
159    pub fn with_phone_number(mut self, pnumber: String) -> Self {
160        match &mut self.ids.phone_number {
161            Some(pnumber_vec) => pnumber_vec.push(pnumber),
162            None => self.ids.phone_number = Some(vec![pnumber]),
163        };
164
165        self
166    }
167
168    pub fn build(self) -> UserIdentifiers {
169        self.ids
170    }
171}
172
173#[derive(Serialize, Debug, Clone)]
174pub enum DeleteAttribute {
175    #[serde(rename = "DISPLAY_NAME")]
176    DisplayName,
177    #[serde(rename = "PHOTO_URL")]
178    PhotoUrl,
179}
180
181#[derive(Serialize, Debug, Clone)]
182#[serde(rename_all = "camelCase")]
183pub enum DeleteProvider {
184    Phone,
185}
186
187#[derive(Serialize, Debug, Clone, Default)]
188#[serde(rename_all = "camelCase")]
189pub struct UserUpdate {
190    #[serde(rename = "localId")]
191    pub uid: String,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub email: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub password: Option<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub valid_since: Option<OffsetDateTime>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub email_verified: Option<bool>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub disable_user: Option<bool>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub display_name: Option<String>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub photo_url: Option<String>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub phone_number: Option<String>,
208    #[serde(rename = "customAttributes")]
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub custom_claims: Option<Claims>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub delete_attribute: Option<Vec<DeleteAttribute>>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub delete_provider: Option<Vec<DeleteProvider>>,
215}
216
217impl UserUpdate {
218    pub fn builder(uid: String) -> UserUpdateBuilder {
219        UserUpdateBuilder::new(uid)
220    }
221}
222
223pub struct UserUpdateBuilder {
224    update: UserUpdate,
225}
226
227pub enum AttributeOp<T> {
228    Change(T),
229    Delete,
230}
231
232impl UserUpdateBuilder {
233    pub fn new(uid: String) -> Self {
234        Self {
235            update: UserUpdate {
236                uid,
237                ..Default::default()
238            },
239        }
240    }
241
242    pub fn display_name(mut self, value: AttributeOp<String>) -> Self {
243        match value {
244            AttributeOp::Change(new_display_name) => {
245                self.update.display_name = Some(new_display_name)
246            }
247            AttributeOp::Delete => self
248                .update
249                .delete_attribute
250                .get_or_insert(Vec::new())
251                .push(DeleteAttribute::DisplayName),
252        };
253
254        self
255    }
256
257    pub fn photo_url(mut self, value: AttributeOp<String>) -> Self {
258        match value {
259            AttributeOp::Change(new_photo_url) => self.update.photo_url = Some(new_photo_url),
260            AttributeOp::Delete => self
261                .update
262                .delete_attribute
263                .get_or_insert(Vec::new())
264                .push(DeleteAttribute::PhotoUrl),
265        };
266
267        self
268    }
269
270    pub fn phone_number(mut self, value: AttributeOp<String>) -> Self {
271        match value {
272            AttributeOp::Change(new_phone_number) => {
273                self.update.phone_number = Some(new_phone_number)
274            }
275            AttributeOp::Delete => self
276                .update
277                .delete_provider
278                .get_or_insert(Vec::new())
279                .push(DeleteProvider::Phone),
280        };
281
282        self
283    }
284
285    pub fn custom_claims(mut self, value: Claims) -> Self {
286        self.update.custom_claims = Some(value);
287
288        self
289    }
290
291    pub fn email(mut self, value: String) -> Self {
292        self.update.email = Some(value);
293
294        self
295    }
296
297    pub fn password(mut self, value: String) -> Self {
298        self.update.password = Some(value);
299
300        self
301    }
302
303    pub fn email_verified(mut self, value: bool) -> Self {
304        self.update.email_verified = Some(value);
305
306        self
307    }
308
309    pub fn disabled(mut self, is_disabled: bool) -> Self {
310        self.update.disable_user = Some(is_disabled);
311
312        self
313    }
314
315    pub fn build(self) -> UserUpdate {
316        self.update
317    }
318}
319
320#[derive(Serialize, Debug, Clone, Default)]
321#[serde(rename_all = "camelCase")]
322struct UserId {
323    #[serde(rename = "localId")]
324    pub uid: String,
325}
326
327#[derive(Serialize, Debug, Clone, Default)]
328#[serde(rename_all = "camelCase")]
329struct UserIds {
330    #[serde(rename = "localIds")]
331    pub uids: Vec<String>,
332    pub force: bool,
333}
334
335pub trait FirebaseAuthService<C: ApiHttpClient>: Send + Sync + 'static {
336    fn get_client(&self) -> &C;
337    fn get_auth_uri_builder(&self) -> &ApiUriBuilder;
338
339    /// Creates a new user account with the specified properties.
340    /// # Example
341    /// ```rust
342    /// let new_user = auth.create_user(
343    ///     NewUser::email_and_password(
344    ///        "test@example.com".into(),
345    ///        "123ABC".into(),
346    ///     )
347    /// ).await.unwrap();
348    /// ```
349    fn create_user(
350        &self,
351        user: NewUser,
352    ) -> impl Future<Output = Result<User, Report<ApiClientError>>> + Send {
353        let client = self.get_client();
354        let uri = self
355            .get_auth_uri_builder()
356            .build(FirebaseAuthRestApi::CreateUser);
357
358        client.send_request_body(uri, Method::POST, user)
359    }
360
361    /// Get first user that matches given identifier filter
362    /// # Example
363    /// ```rust
364    /// let user = auth.get_user(
365    ///     UserIdentifiers {
366    ///         email: Some(vec!["me@example.com".into()]),
367    ///         ..Default::default()
368    ///     }
369    /// ).await.unwrap();
370    /// ```
371    fn get_user(
372        &self,
373        indentifiers: UserIdentifiers,
374    ) -> impl Future<Output = Result<Option<User>, Report<ApiClientError>>> + Send {
375        async move {
376            if let Some(users) = self.get_users(indentifiers).await? {
377                return Ok(users.into_iter().next());
378            }
379
380            Ok(None)
381        }
382    }
383
384    /// Get all users that match a given identifier filter
385    /// # Example
386    /// ```rust
387    /// let users = auth.get_users(
388    ///     UserIdentifiers {
389    ///         email: Some(vec!["me@example.com".into()]),
390    ///         uid: Some(vec!["A123456".into()]),
391    ///         ..Default::default()
392    ///     }
393    /// ).await.unwrap().unwrap();
394    /// ```
395    fn get_users(
396        &self,
397        indentifiers: UserIdentifiers,
398    ) -> impl Future<Output = Result<Option<Vec<User>>, Report<ApiClientError>>> + Send {
399        async move {
400            let client = self.get_client();
401            let uri_builder = self.get_auth_uri_builder();
402
403            let users: Users = client
404                .send_request_body(
405                    uri_builder.build(FirebaseAuthRestApi::GetUsers),
406                    Method::POST,
407                    indentifiers,
408                )
409                .await?;
410
411            Ok(users.users)
412        }
413    }
414
415    /// Fetch all users in batches of `users_per_page`, to progress pass previous page into the method's `prev`.
416    /// # Example
417    /// ```rust
418    /// let mut user_page: Option<UserList> = None;
419    /// loop {
420    ///     user_page = auth.list_users(10, user_page).await.unwrap();
421    ///
422    ///     if let Some(user_page) = &user_page {
423    ///         for user in &user_page.users {
424    ///             println!("User: {user:?}");
425    ///         }
426    ///     } else {
427    ///         break;
428    ///     }
429    /// }
430    /// ```
431    fn list_users(
432        &self,
433        users_per_page: usize,
434        prev: Option<UserList>,
435    ) -> impl Future<Output = Result<Option<UserList>, Report<ApiClientError>>> + Send {
436        async move {
437            let client = self.get_client();
438            let uri_builder = self.get_auth_uri_builder();
439            let mut params = vec![("maxResults".to_string(), users_per_page.clone().to_string())];
440
441            if let Some(prev) = prev {
442                if let Some(next_page_token) = prev.next_page_token {
443                    params.push(("nextPageToken".to_string(), next_page_token));
444                } else {
445                    return Ok(None);
446                }
447            }
448
449            let users: UserList = client
450                .send_request_with_params(
451                    uri_builder.build(FirebaseAuthRestApi::ListUsers),
452                    params.into_iter(),
453                    Method::GET,
454                )
455                .await?;
456
457            Ok(Some(users))
458        }
459    }
460
461    /// Delete user with given ID
462    fn delete_user(
463        &self,
464        uid: String,
465    ) -> impl Future<Output = Result<(), Report<ApiClientError>>> + Send {
466        async move {
467            let client = self.get_client();
468            let uri_builder = self.get_auth_uri_builder();
469
470            client
471                .send_request_body_empty_response(
472                    uri_builder.build(FirebaseAuthRestApi::DeleteUser),
473                    Method::POST,
474                    UserId { uid },
475                )
476                .await
477        }
478    }
479
480    /// Delete all users with given list of IDs
481    fn delete_users(
482        &self,
483        uids: Vec<String>,
484        force: bool,
485    ) -> impl Future<Output = Result<(), Report<ApiClientError>>> + Send {
486        async move {
487            let client = self.get_client();
488            let uri_builder = self.get_auth_uri_builder();
489
490            client
491                .send_request_body_empty_response(
492                    uri_builder.build(FirebaseAuthRestApi::DeleteUsers),
493                    Method::POST,
494                    UserIds { uids, force },
495                )
496                .await
497        }
498    }
499
500    /// Update user with given changes
501    /// # Example
502    /// ```rust
503    /// let update = UserUpdate::builder("ID123".into())
504    ///     .display_name(AttributeOp::Change("My new name".into()))
505    ///     .phone_number(AttributeOp::Delete)
506    ///     .email("new@example.com".into())
507    ///     .build();
508    /// auth.update_user(update).await.unwrap();
509    /// ```
510    fn update_user(
511        &self,
512        update: UserUpdate,
513    ) -> impl Future<Output = Result<User, Report<ApiClientError>>> + Send {
514        async move {
515            let client = self.get_client();
516            let uri_builder = self.get_auth_uri_builder();
517
518            client
519                .send_request_body(
520                    uri_builder.build(FirebaseAuthRestApi::UpdateUser),
521                    Method::POST,
522                    update,
523                )
524                .await
525        }
526    }
527
528    /// Create users in bulk
529    /// # Example
530    /// ```rust
531    /// let records = vec![
532    ///     UserImportRecord::builder()
533    ///         .with_email("me@example.com".into(), true)
534    ///         .with_display_name("My Name".into())
535    ///         .build()
536    /// ];
537    /// auth.import_users(records).await.unwrap();
538    /// ```
539    fn import_users(
540        &self,
541        users: Vec<UserImportRecord>,
542    ) -> impl Future<Output = Result<(), Report<ApiClientError>>> + Send {
543        async move {
544            let client = self.get_client();
545            let uri_builder = self.get_auth_uri_builder();
546
547            client
548                .send_request_body_empty_response(
549                    uri_builder.build(FirebaseAuthRestApi::ImportUsers),
550                    Method::POST,
551                    UserImportRecords { users },
552                )
553                .await?;
554
555            Ok(())
556        }
557    }
558
559    /// Send email with OOB code action
560    /// # Example
561    /// ```rust
562    /// let oob_action = OobCodeAction::builder(
563    ///     OobCodeActionType::PasswordReset,
564    ///     "me@example.com".into()
565    /// ).build();
566    ///
567    /// let link = auth.generate_email_action_link(oob_action).await.unwrap();
568    /// ```
569    fn generate_email_action_link(
570        &self,
571        oob_action: OobCodeAction,
572    ) -> impl Future<Output = Result<String, Report<ApiClientError>>> + Send {
573        async move {
574            let client = self.get_client();
575            let uri_builder = self.get_auth_uri_builder();
576
577            let oob_link: OobCodeActionLink = client
578                .send_request_body(
579                    uri_builder.build(FirebaseAuthRestApi::SendOobCode),
580                    Method::POST,
581                    oob_action,
582                )
583                .await?;
584
585            Ok(oob_link.oob_link)
586        }
587    }
588
589    /// Create session cookie
590    /// that then can be verified and parsed with `App::live().cookie_token_verifier()`
591    fn create_session_cookie(
592        &self,
593        id_token: String,
594        expires_in: Duration,
595    ) -> impl Future<Output = Result<String, Report<ApiClientError>>> + Send {
596        async move {
597            let client = self.get_client();
598            let uri_builder = self.get_auth_uri_builder();
599
600            let create_cookie = CreateSessionCookie {
601                id_token,
602                valid_duration: expires_in.whole_seconds(),
603            };
604
605            let session_cookie: SessionCookie = client
606                .send_request_body(
607                    uri_builder.build(FirebaseAuthRestApi::CreateSessionCookie),
608                    Method::POST,
609                    create_cookie,
610                )
611                .await?;
612
613            Ok(session_cookie.session_cookie)
614        }
615    }
616}
617
618#[derive(Serialize, Deserialize, Debug, Clone)]
619#[serde(rename_all = "camelCase")]
620pub struct EmulatorConfigurationSignIn {
621    allow_duplicate_emails: bool,
622}
623
624#[derive(Serialize, Deserialize, Debug, Clone)]
625#[serde(rename_all = "camelCase")]
626pub struct EmulatorConfiguration {
627    sign_in: EmulatorConfigurationSignIn,
628}
629
630#[derive(Deserialize, Debug, Clone)]
631#[serde(rename_all = "camelCase")]
632pub struct OobCode {
633    pub email: String,
634    pub oob_code: String,
635    pub oob_link: String,
636    pub request_type: OobCodeActionType,
637}
638
639#[derive(Deserialize, Debug, Clone)]
640#[serde(rename_all = "camelCase")]
641pub struct OobCodes {
642    pub oob_codes: Vec<OobCode>,
643}
644
645#[derive(Deserialize, Debug, Clone)]
646#[serde(rename_all = "camelCase")]
647pub struct SmsVerificationCode {
648    pub phone_number: String,
649    pub session_code: String,
650}
651
652#[derive(Deserialize, Debug, Clone)]
653#[serde(rename_all = "camelCase")]
654pub struct SmsVerificationCodes {
655    pub verification_codes: Vec<SmsVerificationCode>,
656}
657
658pub trait FirebaseEmulatorAuthService<ApiHttpClientT>
659where
660    Self: Send + Sync,
661    ApiHttpClientT: ApiHttpClient + Send + Sync,
662{
663    fn get_emulator_client(&self) -> &ApiHttpClientT;
664    fn get_emulator_auth_uri_builder(&self) -> &ApiUriBuilder;
665
666    /// Delete all users within emulator
667    fn clear_all_users(&self) -> impl Future<Output = Result<(), Report<ApiClientError>>> + Send {
668        async move {
669            let client = self.get_emulator_client();
670            let uri_builder = self.get_emulator_auth_uri_builder();
671
672            let _result: BTreeMap<String, String> = client
673                .send_request(
674                    uri_builder.build(FirebaseAuthEmulatorRestApi::ClearUserAccounts),
675                    Method::DELETE,
676                )
677                .await?;
678
679            Ok(())
680        }
681    }
682
683    /// Get current emulator configuration
684    fn get_emulator_configuration(
685        &self,
686    ) -> impl Future<Output = Result<EmulatorConfiguration, Report<ApiClientError>>> + Send {
687        async move {
688            let client = self.get_emulator_client();
689            let uri_builder = self.get_emulator_auth_uri_builder();
690
691            client
692                .send_request(
693                    uri_builder.build(FirebaseAuthEmulatorRestApi::Configuration),
694                    Method::GET,
695                )
696                .await
697        }
698    }
699
700    /// Update emulator configuration
701    fn patch_emulator_configuration(
702        &self,
703        configuration: EmulatorConfiguration,
704    ) -> impl Future<Output = Result<EmulatorConfiguration, Report<ApiClientError>>> + Send {
705        async move {
706            let client = self.get_emulator_client();
707            let uri_builder = self.get_emulator_auth_uri_builder();
708
709            client
710                .send_request_body(
711                    uri_builder.build(FirebaseAuthEmulatorRestApi::Configuration),
712                    Method::PATCH,
713                    configuration,
714                )
715                .await
716        }
717    }
718
719    /// Fetch all OOB codes within emulator
720    fn get_oob_codes(
721        &self,
722    ) -> impl Future<Output = Result<Vec<OobCode>, Report<ApiClientError>>> + Send {
723        async move {
724            let client = self.get_emulator_client();
725            let uri_builder = self.get_emulator_auth_uri_builder();
726
727            let oob_codes: OobCodes = client
728                .send_request(
729                    uri_builder.build(FirebaseAuthEmulatorRestApi::OobCodes),
730                    Method::GET,
731                )
732                .await?;
733
734            Ok(oob_codes.oob_codes)
735        }
736    }
737
738    /// Fetch all SMS codes within emulator
739    fn get_sms_verification_codes(
740        &self,
741    ) -> impl Future<Output = Result<SmsVerificationCodes, Report<ApiClientError>>> + Send {
742        async move {
743            let client = self.get_emulator_client();
744            let uri_builder = self.get_emulator_auth_uri_builder();
745
746            client
747                .send_request(
748                    uri_builder.build(FirebaseAuthEmulatorRestApi::SmsVerificationCodes),
749                    Method::GET,
750                )
751                .await
752        }
753    }
754}
755
756pub struct FirebaseAuth<ApiHttpClientT> {
757    client: ApiHttpClientT,
758    auth_uri_builder: ApiUriBuilder,
759    emulator_auth_uri_builder: Option<ApiUriBuilder>,
760}
761
762impl<ApiHttpClientT> FirebaseAuth<ApiHttpClientT>
763where
764    ApiHttpClientT: ApiHttpClient + Send + Sync,
765{
766    /// Create Firebase Authentication manager for emulator
767    pub fn emulated(emulator_url: String, project_id: &str, client: ApiHttpClientT) -> Self {
768        let fb_auth_root = emulator_url.clone()
769            + &format!("/{FIREBASE_AUTH_REST_AUTHORITY}/v1/projects/{project_id}");
770        let fb_emu_root = emulator_url + &format!("/emulator/v1/projects/{project_id}");
771
772        Self {
773            client,
774            auth_uri_builder: ApiUriBuilder::new(fb_auth_root),
775            emulator_auth_uri_builder: Some(ApiUriBuilder::new(fb_emu_root)),
776        }
777    }
778
779    /// Create Firebase Authentication manager for live project
780    pub fn live(project_id: &str, client: ApiHttpClientT) -> Self {
781        let fb_auth_root = "https://".to_string()
782            + FIREBASE_AUTH_REST_AUTHORITY
783            + &format!("/v1/projects/{project_id}");
784
785        Self {
786            client,
787            auth_uri_builder: ApiUriBuilder::new(fb_auth_root),
788            emulator_auth_uri_builder: None,
789        }
790    }
791}
792
793impl<ApiHttpClientT> FirebaseAuthService<ApiHttpClientT> for FirebaseAuth<ApiHttpClientT>
794where
795    ApiHttpClientT: ApiHttpClient + Send + Sync,
796{
797    fn get_client(&self) -> &ApiHttpClientT {
798        &self.client
799    }
800
801    fn get_auth_uri_builder(&self) -> &ApiUriBuilder {
802        &self.auth_uri_builder
803    }
804}
805
806impl<ApiHttpClientT> FirebaseEmulatorAuthService<ApiHttpClientT> for FirebaseAuth<ApiHttpClientT>
807where
808    ApiHttpClientT: ApiHttpClient + Send + Sync,
809{
810    fn get_emulator_client(&self) -> &ApiHttpClientT {
811        &self.client
812    }
813
814    fn get_emulator_auth_uri_builder(&self) -> &ApiUriBuilder {
815        self.emulator_auth_uri_builder
816            .as_ref()
817            .expect("Auth emulator URI builder is unset")
818    }
819}