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