Skip to main content

authx_plugins/email_verification/
service.rs

1use std::time::Duration;
2
3use tracing::instrument;
4use uuid::Uuid;
5
6use authx_core::{
7    error::{AuthError, Result},
8    events::{AuthEvent, EventBus},
9    models::UpdateUser,
10};
11use authx_storage::ports::UserRepository;
12
13use crate::one_time_token::{OneTimeTokenStore, TokenKind};
14
15/// Issues and verifies email verification tokens.
16///
17/// # Flow
18/// 1. Call `issue(user_id)` → send the returned token in an email link.
19/// 2. User clicks link → call `verify(token)` → sets `email_verified = true`.
20pub struct EmailVerificationService<S> {
21    storage: S,
22    events: EventBus,
23    token_store: OneTimeTokenStore,
24}
25
26impl<S> EmailVerificationService<S>
27where
28    S: UserRepository + Clone + Send + Sync + 'static,
29{
30    pub fn new(storage: S, events: EventBus) -> Self {
31        Self {
32            storage,
33            events,
34            token_store: OneTimeTokenStore::new(Duration::from_secs(24 * 60 * 60)),
35        }
36    }
37
38    /// Issue a 24-hour verification token for the given user.
39    #[instrument(skip(self), fields(user_id = %user_id))]
40    pub async fn issue(&self, user_id: Uuid) -> Result<String> {
41        UserRepository::find_by_id(&self.storage, user_id)
42            .await?
43            .ok_or(AuthError::UserNotFound)?;
44
45        let token = self
46            .token_store
47            .issue(user_id, TokenKind::EmailVerification);
48        tracing::info!(user_id = %user_id, "email verification token issued");
49        Ok(token)
50    }
51
52    /// Consume the token and mark the user's email as verified.
53    #[instrument(skip(self, raw_token))]
54    pub async fn verify(&self, raw_token: &str) -> Result<()> {
55        let user_id = self
56            .token_store
57            .consume(raw_token, TokenKind::EmailVerification)
58            .ok_or(AuthError::InvalidToken)?;
59
60        UserRepository::update(
61            &self.storage,
62            user_id,
63            UpdateUser {
64                email_verified: Some(true),
65                ..Default::default()
66            },
67        )
68        .await?;
69
70        self.events.emit(AuthEvent::EmailVerified { user_id });
71        tracing::info!(user_id = %user_id, "email verified");
72        Ok(())
73    }
74}