forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
2.3 KiB
1//! Account mail: the password reset, email verification and invite links. Each function mints
2//! its token in the store and queues one message keyed by the token's hash, so a retried call
3//! queues nothing twice. The token itself appears only in the link.
4
5use forge_core::User;
6use forge_core::token::hash_token;
7
8use super::{enqueue, templates};
9use crate::state::AppState;
10
11/// The base URL without a trailing slash, for building links.
12fn base(state: &AppState) -> &str {
13 state.config.base_url.trim_end_matches('/')
14}
15
16/// Queues a reset link to the user's verified address. The caller decides that the address is
17/// verified. A user without an address gets nothing.
18pub async fn send_reset(state: &AppState, user: &User) -> forge_core::Result<()> {
19 let Some(address) = user.email.as_deref() else {
20 return Ok(());
21 };
22 let token = state.store.create_reset_token(&user.id).await?;
23 let link = format!("{}/reset/{token}", base(state));
24 let (subject, body) = templates::password_reset(&user.username, &link);
25 enqueue(
26 state,
27 &format!("reset:{}", hash_token(&token)),
28 address,
29 &subject,
30 &body,
31 )
32 .await?;
33 Ok(())
34}
35
36/// Queues a verification link for `address`, which is the user's current address.
37pub async fn send_verify(state: &AppState, user: &User, address: &str) -> forge_core::Result<()> {
38 let token = state.store.create_verify_token(&user.id, address).await?;
39 let link = format!("{}/verify/{token}", base(state));
40 let (subject, body) = templates::email_verify(&user.username, &link);
41 enqueue(
42 state,
43 &format!("verify:{}", hash_token(&token)),
44 address,
45 &subject,
46 &body,
47 )
48 .await?;
49 Ok(())
50}
51
52/// Creates an invite for `address` and queues its link. Returns the store's refusal, such as an
53/// address that already has an account.
54pub async fn send_invite(
55 state: &AppState,
56 address: &str,
57 invited_by: &str,
58) -> forge_core::Result<()> {
59 let token = state.store.create_invite(address, invited_by).await?;
60 let link = format!("{}/register?invite={token}", base(state));
61 let (subject, body) = templates::invite(&link);
62 enqueue(
63 state,
64 &format!("invite:{}", hash_token(&token)),
65 address,
66 &subject,
67 &body,
68 )
69 .await?;
70 Ok(())
71}