1#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}