Skip to content

Commit 3dd8840

Browse files
committed
add email verification models + cache functions
1 parent 0623116 commit 3dd8840

3 files changed

Lines changed: 91 additions & 0 deletions

File tree

src/cache.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,54 @@ pub async fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> {
317317
.err_into()
318318
}
319319

320+
pub async fn store_email_verification(
321+
user_id: u64,
322+
code: &str,
323+
pending_email: Option<&str>,
324+
) -> Result<()> {
325+
// 10 minute window to verify
326+
const VERIFICATION_CODE_TTL_SECS: u64 = 600;
327+
328+
let value = format!("{}:{}", code, pending_email.unwrap_or(""));
329+
get_con()
330+
.await?
331+
.set_ex(
332+
format!("essence-email-verify-{user_id}"),
333+
value,
334+
VERIFICATION_CODE_TTL_SECS,
335+
)
336+
.await
337+
.err_into()
338+
}
339+
340+
/// Returns ``Some((code, pending_email))``` if an entry exists.
341+
pub async fn get_email_verification(user_id: u64) -> Result<Option<(String, Option<String>)>> {
342+
let raw: Option<String> = get_con()
343+
.await?
344+
.get(format!("essence-email-verify-{user_id}"))
345+
.await?;
346+
347+
Ok(raw.map(|s| {
348+
let (code, email) = s.split_once(':').unwrap_or((&s, ""));
349+
(
350+
code.to_string(),
351+
if email.is_empty() {
352+
None
353+
} else {
354+
Some(email.to_string())
355+
},
356+
)
357+
}))
358+
}
359+
360+
pub async fn delete_email_verification(user_id: u64) -> Result<()> {
361+
get_con()
362+
.await?
363+
.del(format!("essence-email-verify-{user_id}"))
364+
.await
365+
.err_into()
366+
}
367+
320368
pub async fn resolve_invite_guild_id(code: &str) -> Result<Option<u64>> {
321369
get_con()
322370
.await?

src/db/user.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,30 @@ pub trait UserDbExt<'t>: DbExt<'t> {
409409
Ok((old, user))
410410
}
411411

412+
/// Updates the email address of a user.
413+
///
414+
/// # Note
415+
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
416+
/// rolled back, and the transaction must be committed to save the changes.
417+
///
418+
/// # Errors
419+
/// * If an error occurs with the database.
420+
async fn update_user_email(
421+
&mut self,
422+
id: u64,
423+
email: impl AsRef<str> + Send,
424+
) -> sqlx::Result<()> {
425+
sqlx::query!(
426+
"UPDATE users SET email = $1 WHERE id = $2",
427+
email.as_ref(),
428+
id as i64,
429+
)
430+
.execute(self.transaction())
431+
.await?;
432+
433+
Ok(())
434+
}
435+
412436
/// Deletes a user from the database.
413437
///
414438
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly

src/http/auth.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,22 @@ pub struct LoginResponse {
4343
/// The authentication token to use for future requests.
4444
pub token: String,
4545
}
46+
47+
/// The request body for POST /auth/verify
48+
#[derive(Clone, Debug, Deserialize)]
49+
#[cfg_attr(feature = "client", derive(Serialize))]
50+
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
51+
pub struct RequestEmailVerification {
52+
/// If provided, the email address to change to and verify. If absent, the existing email
53+
/// address on the account is used.
54+
pub new_email: Option<String>,
55+
}
56+
57+
/// The request body for POST /auth/verify/followup
58+
#[derive(Clone, Debug, Deserialize)]
59+
#[cfg_attr(feature = "client", derive(Serialize))]
60+
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
61+
pub struct EmailVerificationFollowup {
62+
/// The six-digit verification code.
63+
pub code: String,
64+
}

0 commit comments

Comments
 (0)