use std::time::Duration;
use tracing::instrument;
use uuid::Uuid;
use authx_core::{
error::{AuthError, Result},
events::{AuthEvent, EventBus},
models::UpdateUser,
};
use authx_storage::ports::UserRepository;
use crate::one_time_token::{OneTimeTokenStore, TokenKind};
pub struct EmailVerificationService<S> {
storage: S,
events: EventBus,
token_store: OneTimeTokenStore,
}
impl<S> EmailVerificationService<S>
where
S: UserRepository + Clone + Send + Sync + 'static,
{
pub fn new(storage: S, events: EventBus) -> Self {
Self {
storage,
events,
token_store: OneTimeTokenStore::new(Duration::from_secs(24 * 60 * 60)),
}
}
#[instrument(skip(self), fields(user_id = %user_id))]
pub async fn issue(&self, user_id: Uuid) -> Result<String> {
UserRepository::find_by_id(&self.storage, user_id)
.await?
.ok_or(AuthError::UserNotFound)?;
let token = self
.token_store
.issue(user_id, TokenKind::EmailVerification);
tracing::info!(user_id = %user_id, "email verification token issued");
Ok(token)
}
#[instrument(skip(self, raw_token))]
pub async fn verify(&self, raw_token: &str) -> Result<()> {
let user_id = self
.token_store
.consume(raw_token, TokenKind::EmailVerification)
.ok_or(AuthError::InvalidToken)?;
UserRepository::update(
&self.storage,
user_id,
UpdateUser {
email_verified: Some(true),
..Default::default()
},
)
.await?;
self.events.emit(AuthEvent::EmailVerified { user_id });
tracing::info!(user_id = %user_id, "email verified");
Ok(())
}
}