authx_plugins/email_verification/
service.rs1use 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
15pub 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 #[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 #[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}