firebase_wasm/
auth.rs

1mod user;
2
3use crate::FirebaseError;
4use std::{error::Error, fmt};
5pub use user::*;
6use wasm_bindgen::{prelude::*, JsCast};
7
8#[derive(Clone, Debug, derive_more::Deref)]
9pub struct AuthError {
10    pub kind: AuthErrorKind,
11    #[deref]
12    pub source: FirebaseError,
13}
14
15impl fmt::Display for AuthError {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        self.source.fmt(f)
18    }
19}
20
21impl Error for AuthError {
22    fn source(&self) -> Option<&(dyn Error + 'static)> {
23        Some(&self.source)
24    }
25}
26
27impl From<FirebaseError> for AuthError {
28    fn from(err: FirebaseError) -> Self {
29        Self {
30            kind: err.code().parse().unwrap(),
31            source: err,
32        }
33    }
34}
35
36#[derive(Clone, Debug, strum_macros::EnumString)]
37#[non_exhaustive]
38pub enum AuthErrorKind {
39    #[strum(serialize = "auth/app-deleted")]
40    AppDeleted,
41    #[strum(serialize = "auth/app-not-authorized")]
42    AppNotAuthorized,
43    #[strum(serialize = "auth/argument-error")]
44    ArgumentError,
45    #[strum(serialize = "auth/invalid-api-key")]
46    InvalidApiKey,
47    #[strum(serialize = "auth/invalid-user-token")]
48    InvalidUserToken,
49    #[strum(serialize = "auth/invalid-tenant-id")]
50    InvalidTenantId,
51    #[strum(serialize = "auth/network-request-failed")]
52    NetworkRequestFailed,
53    #[strum(serialize = "auth/operation-not-allowed")]
54    OperationNotAllowed,
55    #[strum(serialize = "auth/requires-recent-login")]
56    RequiresRecentLogin,
57    #[strum(serialize = "auth/too-many-requests")]
58    TooManyRequests,
59    #[strum(serialize = "auth/unauthorized-domain")]
60    UnauthorizedDomain,
61    #[strum(serialize = "auth/user-disabled")]
62    UserDisabled,
63    #[strum(serialize = "auth/user-token-expired")]
64    UserTokenExpired,
65    #[strum(serialize = "auth/web-storage-unsupported")]
66    WebStorageUnsupported,
67    #[strum(serialize = "auth/invalid-email")]
68    InvalidEmail,
69    #[strum(serialize = "auth/user-not-found")]
70    UserNotFound,
71    #[strum(serialize = "auth/wrong-password")]
72    WrongPassword,
73    #[strum(serialize = "auth/email-already-in-use")]
74    EmailAlreadyInUse,
75    #[strum(serialize = "auth/weak-password")]
76    WeakPassword,
77    #[strum(serialize = "auth/missing-android-pkg-name")]
78    MissingAndroidPackageName,
79    #[strum(serialize = "auth/missing-continue-uri")]
80    MissingContinueUri,
81    #[strum(serialize = "auth/missing-ios-bundle-id")]
82    MissingIOSBundleId,
83    #[strum(serialize = "auth/invalid-continue-uri")]
84    InvalidContinueUri,
85    #[strum(serialize = "auth/unauthorized-continue-uri")]
86    UnauthorizedContinueUri,
87    #[strum(serialize = "auth/expired-action-code")]
88    ExpiredActionCode,
89    #[strum(default)]
90    Other(String),
91}
92
93#[derive(Debug, Clone, TypedBuilder, serde::Serialize)]
94#[builder(field_defaults(default))]
95pub struct ActionCodeSettings {
96    pub android: Option<AndroidActionCodeSettings>,
97    #[builder(setter(strip_option))]
98    pub handle_code_in_app: Option<bool>,
99    pub ios: Option<IOSActionCodeSettings>,
100    #[builder(!default)]
101    pub url: String,
102    pub dynamic_link_domain: Option<String>,
103}
104
105#[serde_with::skip_serializing_none]
106#[derive(Debug, Clone, PartialEq, Eq, TypedBuilder, serde::Serialize)]
107#[serde(rename_all = "camelCase")]
108pub struct AndroidActionCodeSettings {
109    #[builder(default, setter(strip_option))]
110    pub install_app: Option<bool>,
111    #[builder(default)]
112    pub minimum_version: Option<String>,
113    pub package_name: String,
114}
115
116#[serde_with::skip_serializing_none]
117#[derive(Debug, Clone, PartialEq, Eq, TypedBuilder, serde::Serialize)]
118#[serde(rename_all = "camelCase")]
119pub struct IOSActionCodeSettings {
120    pub bundle_id: String,
121}
122
123pub async fn create_user_with_email_and_password(
124    auth: Auth,
125    email: String,
126    password: String,
127) -> Result<UserCredential, AuthError> {
128    create_user_with_email_and_password_js(auth, email, password)
129        .await
130        .map(|cred| cred.unchecked_into::<UserCredential>())
131        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
132}
133
134pub async fn sign_in_with_email_and_password(
135    auth: Auth,
136    email: String,
137    password: String,
138) -> Result<UserCredential, AuthError> {
139    sign_in_with_email_and_password_js(auth, email, password)
140        .await
141        .map(|cred| cred.unchecked_into::<UserCredential>())
142        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
143}
144
145pub async fn send_sign_in_link_to_email(
146    auth: Auth,
147    email: String,
148    action_code_settings: ActionCodeSettings,
149) -> Result<(), AuthError> {
150    let action_code_settings = serde_wasm_bindgen::to_value(&action_code_settings).unwrap();
151
152    send_sign_in_link_to_email_js(auth, email, action_code_settings)
153        .await
154        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
155}
156
157pub async fn sign_in_with_email_link(
158    auth: Auth,
159    email: String,
160    email_link: String,
161) -> Result<UserCredential, AuthError> {
162    sign_in_with_email_link_js(auth, email, email_link)
163        .await
164        .map(|u| u.unchecked_into::<UserCredential>())
165        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
166}
167
168pub async fn send_password_reset_email(
169    auth: Auth,
170    email: String,
171    action_code_settings: Option<ActionCodeSettings>,
172) -> Result<(), AuthError> {
173    let action_code_settings = serde_wasm_bindgen::to_value(&action_code_settings).unwrap();
174
175    send_password_reset_email_js(auth, email, action_code_settings)
176        .await
177        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
178}
179
180pub async fn verify_password_reset_code(auth: Auth, code: String) -> Result<String, AuthError> {
181    verify_password_reset_code_js(auth, code)
182        .await
183        .map(|res| res.unchecked_into::<js_sys::JsString>())
184        .map(|s| ToString::to_string(&s))
185        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
186}
187
188pub async fn confirm_password_reset(
189    auth: Auth,
190    code: String,
191    new_password: String,
192) -> Result<(), JsValue> {
193    confirm_password_reset_js(auth, code, new_password)
194        .await
195        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
196}
197
198#[wasm_bindgen(module = "firebase/auth")]
199extern "C" {
200    #[derive(Clone, Debug)]
201    pub type Auth;
202    #[derive(Clone, Debug)]
203    pub type UserCredential;
204
205    #[wasm_bindgen(js_name = getAuth)]
206    pub fn get_auth() -> Auth;
207
208    #[wasm_bindgen(js_name = onAuthStateChanged)]
209    pub fn on_auth_state_changed(auth: Auth, callback: &Closure<dyn FnMut(Option<User>)>);
210
211    #[wasm_bindgen(js_name = createUserWithEmailAndPassword, catch)]
212    async fn create_user_with_email_and_password_js(
213        auth: Auth,
214        email: String,
215        password: String,
216    ) -> Result<JsValue, JsValue>;
217
218    #[wasm_bindgen(js_name = signInWithEmailAndPassword, catch)]
219    async fn sign_in_with_email_and_password_js(
220        auth: Auth,
221        email: String,
222        password: String,
223    ) -> Result<JsValue, JsValue>;
224
225    #[wasm_bindgen(js_name = signInWithEmailLink, catch)]
226    async fn sign_in_with_email_link_js(
227        auth: Auth,
228        email: String,
229        email_link: String,
230    ) -> Result<JsValue, JsValue>;
231
232    #[wasm_bindgen(js_name = isSignInWithEmailLink, )]
233    pub fn is_sign_in_with_email_link(auth: Auth, email_link: &str) -> bool;
234
235    #[wasm_bindgen(js_name = sendSignInLinkToEmail, catch)]
236    async fn send_sign_in_link_to_email_js(
237        auth: Auth,
238        email: String,
239        action_code_settings: JsValue,
240    ) -> Result<(), JsValue>;
241
242    #[wasm_bindgen(js_name = signOut)]
243    pub async fn sign_out(auth: Auth);
244
245    #[wasm_bindgen(js_name = sendPasswordResetEmail, catch)]
246    async fn send_password_reset_email_js(
247        auth: Auth,
248        email: String,
249        action_code_settings: JsValue,
250    ) -> Result<(), JsValue>;
251
252    #[wasm_bindgen(js_name = verifyPasswordResetCode, catch)]
253    async fn verify_password_reset_code_js(auth: Auth, code: String) -> Result<JsValue, JsValue>;
254
255    #[wasm_bindgen(js_name = confirmPasswordReset, catch)]
256    async fn confirm_password_reset_js(
257        auth: Auth,
258        code: String,
259        new_password: String,
260    ) -> Result<(), JsValue>;
261
262    // =======================================================
263    //                  UserCredential
264    // =======================================================
265
266    #[wasm_bindgen(method, getter)]
267    pub fn user(this: &UserCredential) -> user::User;
268
269    #[wasm_bindgen(method, getter, js_name = providerId)]
270    pub fn provider_id(this: &UserCredential) -> String;
271
272    #[wasm_bindgen(method, getter, js_name = operationType)]
273    pub fn operation_type(this: &UserCredential) -> String;
274}