1use chrono::Duration;
2use iso8601_timestamp::Timestamp;
3
4use crate::{
5 config::EmailVerificationConfig,
6 models::{
7 totp::Totp, Account, DeletionInfo, EmailVerification, MFAMethod, MFAResponse, MFATicket,
8 PasswordReset, Session,
9 },
10 util::{hash_password, normalise_email},
11 Authifier, AuthifierEvent, Error, Result, Success,
12};
13
14impl Account {
15 pub async fn save(&self, authifier: &Authifier) -> Success {
17 authifier.database.save_account(self).await
18 }
19
20 pub async fn new(
22 authifier: &Authifier,
23 email: String,
24 plaintext_password: String,
25 verify_email: bool,
26 ) -> Result<Account> {
27 let password = hash_password(plaintext_password)?;
29
30 let email_normalised = normalise_email(email.clone());
32
33 if let Some(mut account) = authifier
35 .database
36 .find_account_by_normalised_email(&email_normalised)
37 .await?
38 {
39 if let EmailVerification::Pending { .. } = &account.verification {
41 account.start_email_verification(authifier).await?;
42 } else {
43 account.start_password_reset(authifier, true).await?;
44 }
45
46 Ok(account)
47 } else {
48 let mut account = Account {
50 id: ulid::Ulid::new().to_string(),
51
52 email,
53 email_normalised,
54 password,
55
56 disabled: false,
57 verification: EmailVerification::Verified,
58 password_reset: None,
59 deletion: None,
60 lockout: None,
61
62 mfa: Default::default(),
63 };
64
65 if verify_email {
67 account.start_email_verification(authifier).await?;
68 } else {
69 account.save(authifier).await?;
70 }
71
72 authifier
74 .publish_event(AuthifierEvent::CreateAccount {
75 account: account.clone(),
76 })
77 .await;
78
79 Ok(account)
80 }
81 }
82
83 pub async fn create_session(&self, authifier: &Authifier, name: String) -> Result<Session> {
85 let session = Session {
86 id: ulid::Ulid::new().to_string(),
87 token: nanoid!(64),
88
89 user_id: self.id.clone(),
90 name,
91
92 last_seen: Timestamp::now_utc().format().to_string(),
93
94 origin: None,
95 subscription: None,
96 };
97
98 authifier.database.save_session(&session).await?;
100
101 authifier
103 .publish_event(AuthifierEvent::CreateSession {
104 session: session.clone(),
105 })
106 .await;
107
108 Ok(session)
109 }
110
111 pub async fn start_email_verification(&mut self, authifier: &Authifier) -> Success {
113 if let EmailVerificationConfig::Enabled {
114 templates,
115 expiry,
116 smtp,
117 } = &authifier.config.email_verification
118 {
119 let token = nanoid!(32);
120 let url = format!("{}{}", templates.verify.url, token);
121
122 smtp.send_email(
123 self.email.clone(),
124 &templates.verify,
125 json!({
126 "email": self.email.clone(),
127 "url": url
128 }),
129 )?;
130
131 self.verification = EmailVerification::Pending {
132 token,
133 expiry: Timestamp::UNIX_EPOCH
134 + iso8601_timestamp::Duration::milliseconds(
135 (chrono::Utc::now() + Duration::seconds(expiry.expire_verification))
136 .timestamp_millis(),
137 ),
138 };
139 } else {
140 self.verification = EmailVerification::Verified;
141 }
142
143 self.save(authifier).await
144 }
145
146 pub async fn start_email_move(&mut self, authifier: &Authifier, new_email: String) -> Success {
148 if let EmailVerification::Pending { .. } = self.verification {
151 return Err(Error::UnverifiedAccount);
152 }
153
154 if let EmailVerificationConfig::Enabled {
155 templates,
156 expiry,
157 smtp,
158 } = &authifier.config.email_verification
159 {
160 let token = nanoid!(32);
161 let url = format!("{}{}", templates.verify.url, token);
162
163 smtp.send_email(
164 new_email.clone(),
165 &templates.verify,
166 json!({
167 "email": self.email.clone(),
168 "url": url
169 }),
170 )?;
171
172 self.verification = EmailVerification::Moving {
173 new_email,
174 token,
175 expiry: Timestamp::UNIX_EPOCH
176 + iso8601_timestamp::Duration::milliseconds(
177 (chrono::Utc::now() + Duration::seconds(expiry.expire_verification))
178 .timestamp_millis(),
179 ),
180 };
181 } else {
182 self.email_normalised = normalise_email(new_email.clone());
183 self.email = new_email;
184 }
185
186 self.save(authifier).await
187 }
188
189 pub async fn start_password_reset(
191 &mut self,
192 authifier: &Authifier,
193 existing_account: bool,
194 ) -> Success {
195 if let EmailVerificationConfig::Enabled {
196 templates,
197 expiry,
198 smtp,
199 } = &authifier.config.email_verification
200 {
201 let template = if existing_account {
202 &templates.reset_existing
203 } else {
204 &templates.reset
205 };
206
207 let token = nanoid!(32);
208 let url = format!("{}{}", template.url, token);
209
210 smtp.send_email(
211 self.email.clone(),
212 &template,
213 json!({
214 "email": self.email.clone(),
215 "url": url
216 }),
217 )?;
218
219 self.password_reset = Some(PasswordReset {
220 token,
221 expiry: Timestamp::UNIX_EPOCH
222 + iso8601_timestamp::Duration::milliseconds(
223 (chrono::Utc::now() + Duration::seconds(expiry.expire_password_reset))
224 .timestamp_millis(),
225 ),
226 });
227 } else {
228 return Err(Error::OperationFailed);
229 }
230
231 self.save(authifier).await
232 }
233
234 pub async fn start_account_deletion(&mut self, authifier: &Authifier) -> Success {
238 if let EmailVerificationConfig::Enabled {
239 templates,
240 expiry,
241 smtp,
242 } = &authifier.config.email_verification
243 {
244 let token = nanoid!(32);
245 let url = format!("{}{}", templates.deletion.url, token);
246
247 smtp.send_email(
248 self.email.clone(),
249 &templates.deletion,
250 json!({
251 "email": self.email.clone(),
252 "url": url
253 }),
254 )?;
255
256 self.deletion = Some(DeletionInfo::WaitingForVerification {
257 token,
258 expiry: Timestamp::UNIX_EPOCH
259 + iso8601_timestamp::Duration::milliseconds(
260 (chrono::Utc::now() + Duration::seconds(expiry.expire_password_reset))
261 .timestamp_millis(),
262 ),
263 });
264
265 self.save(authifier).await
266 } else {
267 self.schedule_deletion(authifier).await
268 }
269 }
270
271 pub fn verify_password(&self, plaintext_password: &str) -> Success {
273 argon2::verify_encoded(&self.password, plaintext_password.as_bytes())
274 .map(|v| {
275 if v {
276 Ok(())
277 } else {
278 Err(Error::InvalidCredentials)
279 }
280 })
281 .map_err(|_| Error::InvalidCredentials)?
284 }
285
286 pub async fn consume_mfa_response(
288 &mut self,
289 authifier: &Authifier,
290 response: MFAResponse,
291 ticket: Option<MFATicket>,
292 ) -> Success {
293 let allowed_methods = self.mfa.get_methods();
294
295 match response {
296 MFAResponse::Password { password } => {
297 if allowed_methods.contains(&MFAMethod::Password) {
298 self.verify_password(&password)
299 } else {
300 Err(Error::DisallowedMFAMethod)
301 }
302 }
303 MFAResponse::Totp { totp_code } => {
304 if allowed_methods.contains(&MFAMethod::Totp) {
305 if let Totp::Enabled { .. } = &self.mfa.totp_token {
306 if let Some(ticket) = ticket {
308 if let Some(code) = ticket.last_totp_code {
309 if code == totp_code {
310 return Ok(());
311 }
312 }
313 }
314
315 if self.mfa.totp_token.generate_code()? == totp_code {
317 Ok(())
318 } else {
319 Err(Error::InvalidToken)
320 }
321 } else {
322 unreachable!()
323 }
324 } else {
325 Err(Error::DisallowedMFAMethod)
326 }
327 }
328 MFAResponse::Recovery { recovery_code } => {
329 if allowed_methods.contains(&MFAMethod::Recovery) {
330 if let Some(index) = self
331 .mfa
332 .recovery_codes
333 .iter()
334 .position(|x| x == &recovery_code)
335 {
336 self.mfa.recovery_codes.remove(index);
337 self.save(authifier).await
338 } else {
339 Err(Error::InvalidToken)
340 }
341 } else {
342 Err(Error::DisallowedMFAMethod)
343 }
344 }
345 }
346 }
347
348 pub async fn delete_all_sessions(
350 &self,
351 authifier: &Authifier,
352 exclude_session_id: Option<String>,
353 ) -> Success {
354 authifier
355 .database
356 .delete_all_sessions(&self.id, exclude_session_id.clone())
357 .await?;
358
359 authifier
361 .publish_event(AuthifierEvent::DeleteAllSessions {
362 user_id: self.id.to_string(),
363 exclude_session_id,
364 })
365 .await;
366
367 Ok(())
368 }
369
370 pub async fn disable(&mut self, authifier: &Authifier) -> Success {
372 self.disabled = true;
373 self.delete_all_sessions(authifier, None).await?;
374 self.save(authifier).await
375 }
376
377 pub async fn schedule_deletion(&mut self, authifier: &Authifier) -> Success {
379 self.deletion = Some(DeletionInfo::Scheduled {
380 after: Timestamp::UNIX_EPOCH
381 + iso8601_timestamp::Duration::milliseconds(
382 (chrono::Utc::now() + Duration::weeks(1)).timestamp_millis(),
383 ),
384 });
385
386 self.disable(authifier).await
387 }
388}