//! Account mail: the password reset, email verification and invite links. Each function mints //! its token in the store and queues one message keyed by the token's hash, so a retried call //! queues nothing twice. The token itself appears only in the link. use forge_core::User; use forge_core::token::hash_token; use super::{enqueue, templates}; use crate::state::AppState; /// The base URL without a trailing slash, for building links. fn base(state: &AppState) -> &str { state.config.base_url.trim_end_matches('/') } /// Queues a reset link to the user's verified address. The caller decides that the address is /// verified. A user without an address gets nothing. pub async fn send_reset(state: &AppState, user: &User) -> forge_core::Result<()> { let Some(address) = user.email.as_deref() else { return Ok(()); }; let token = state.store.create_reset_token(&user.id).await?; let link = format!("{}/reset/{token}", base(state)); let (subject, body) = templates::password_reset(&user.username, &link); enqueue( state, &format!("reset:{}", hash_token(&token)), address, &subject, &body, ) .await?; Ok(()) } /// Queues a verification link for `address`, which is the user's current address. pub async fn send_verify(state: &AppState, user: &User, address: &str) -> forge_core::Result<()> { let token = state.store.create_verify_token(&user.id, address).await?; let link = format!("{}/verify/{token}", base(state)); let (subject, body) = templates::email_verify(&user.username, &link); enqueue( state, &format!("verify:{}", hash_token(&token)), address, &subject, &body, ) .await?; Ok(()) } /// Creates an invite for `address` and queues its link. Returns the store's refusal, such as an /// address that already has an account. pub async fn send_invite( state: &AppState, address: &str, invited_by: &str, ) -> forge_core::Result<()> { let token = state.store.create_invite(address, invited_by).await?; let link = format!("{}/register?invite={token}", base(state)); let (subject, body) = templates::invite(&link); enqueue( state, &format!("invite:{}", hash_token(&token)), address, &subject, &body, ) .await?; Ok(()) }