Skip to main content

mockforge_registry_server/handlers/
verification.rs

1//! Email verification handlers
2
3use axum::{
4    extract::{Query, State},
5    Json,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    email::EmailService,
11    error::{ApiError, ApiResult},
12    middleware::AuthUser,
13    AppState,
14};
15
16#[derive(Debug, Deserialize)]
17pub struct VerifyEmailQuery {
18    pub token: String,
19}
20
21#[derive(Debug, Serialize)]
22pub struct VerifyEmailResponse {
23    pub success: bool,
24    pub message: String,
25}
26
27/// Verify email address with token
28pub async fn verify_email(
29    State(state): State<AppState>,
30    Query(params): Query<VerifyEmailQuery>,
31) -> ApiResult<Json<VerifyEmailResponse>> {
32    // Find token
33    let verification_token = state
34        .store
35        .find_verification_token_by_token(&params.token)
36        .await?
37        .ok_or_else(|| {
38            ApiError::InvalidRequest("Invalid or expired verification token".to_string())
39        })?;
40
41    // Check if token is valid
42    if !verification_token.is_valid() {
43        return Err(ApiError::InvalidRequest(
44            "Verification token has expired or already been used".to_string(),
45        ));
46    }
47
48    // Get user
49    let user = state
50        .store
51        .find_user_by_id(verification_token.user_id)
52        .await?
53        .ok_or_else(|| ApiError::InvalidRequest("User not found".to_string()))?;
54
55    // Mark user as verified
56    state.store.mark_user_verified(verification_token.user_id).await?;
57
58    // Mark token as used
59    state.store.mark_verification_token_used(verification_token.id).await?;
60
61    tracing::info!("Email verified: user_id={}, email={}", user.id, user.email);
62
63    Ok(Json(VerifyEmailResponse {
64        success: true,
65        message: "Email address verified successfully!".to_string(),
66    }))
67}
68
69#[derive(Debug, Serialize)]
70pub struct ResendVerificationResponse {
71    pub success: bool,
72    pub message: String,
73}
74
75/// Resend verification email
76pub async fn resend_verification(
77    State(state): State<AppState>,
78    AuthUser(user_id): AuthUser,
79) -> ApiResult<Json<ResendVerificationResponse>> {
80    // Get user
81    let user = state
82        .store
83        .find_user_by_id(user_id)
84        .await?
85        .ok_or_else(|| ApiError::InvalidRequest("User not found".to_string()))?;
86
87    // Check if already verified
88    if user.is_verified {
89        return Ok(Json(ResendVerificationResponse {
90            success: true,
91            message: "Email address is already verified".to_string(),
92        }));
93    }
94
95    // Create new verification token
96    let verification_token = state.store.create_verification_token(user_id).await?;
97
98    // Send verification email (non-blocking)
99    let verification_email = EmailService::generate_verification_email(
100        &user.username,
101        &user.email,
102        &verification_token.token,
103    );
104
105    tokio::spawn(async move {
106        match EmailService::from_env() {
107            Ok(email_service) => {
108                if let Err(e) = email_service.send(verification_email).await {
109                    tracing::warn!("Failed to send verification email: {}", e);
110                }
111            }
112            Err(e) => {
113                tracing::warn!("Failed to create email service: {}", e);
114            }
115        }
116    });
117
118    Ok(Json(ResendVerificationResponse {
119        success: true,
120        message: "Verification email sent. Please check your inbox.".to_string(),
121    }))
122}