pray_core/
auth_store_verify.rs1use super::secrets::*;
2use super::support::*;
3use super::RegistryAuthStore;
4use crate::auth::{AuthSessionKind, AuthVerificationResponse};
5use crate::{PrayError, PrayResult};
6use rusqlite::OptionalExtension;
7
8impl RegistryAuthStore {
9 pub fn verify_email(&self, email: &str, code: &str) -> PrayResult<AuthVerificationResponse> {
10 validate_email(email)?;
11 let connection = self.connection()?;
12 let stored: Option<(String, u64, i64)> = connection
13 .query_row(
14 "SELECT code, created_at, failed_attempts FROM email_verification_codes WHERE email = ?1",
15 rusqlite::params![email],
16 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
17 )
18 .optional()?;
19 let Some((stored_code, created_at, failed_attempts)) = stored else {
20 return verification_failed();
21 };
22 if failed_attempts >= i64::from(MAX_VERIFICATION_ATTEMPTS)
23 || record_expired(created_at, VERIFICATION_CODE_TTL_SECONDS)?
24 || code.trim().is_empty()
25 || !constant_time_eq(&stored_code, &stored_token(code))
26 {
27 connection.execute(
28 "UPDATE email_verification_codes SET failed_attempts = failed_attempts + 1 WHERE email = ?1",
29 rusqlite::params![email],
30 )?;
31 return verification_failed();
32 }
33 let timestamp = current_unix_timestamp()?;
34 connection.execute(
35 "UPDATE users SET email_verified = 1 WHERE email = ?1",
36 rusqlite::params![email],
37 )?;
38 connection.execute(
39 "UPDATE email_verification_codes SET verified_at = ?2 WHERE email = ?1",
40 rusqlite::params![email, timestamp],
41 )?;
42 let session = self.issue_session(email, AuthSessionKind::Email)?;
43 Ok(AuthVerificationResponse {
44 email: email.to_string(),
45 verified: true,
46 token: session.token,
47 kind: AuthSessionKind::Email,
48 })
49 }
50}
51
52fn verification_failed() -> PrayResult<AuthVerificationResponse> {
53 Err(PrayError::Resolution(VERIFICATION_FAILED.to_string()))
54}