Some checks failed
- Add EmailVerificationToken and UserPasswordResetToken models with migration - Add email verification flow: verify-email page route, resend-verification API - Block login for unverified users (EmailNotVerifiedException in auth_service) - Add forgot-password/reset-password endpoints for merchant and store auth - Add "Forgot Password?" links to merchant and store login pages - Send welcome email with verification link on merchant creation - Seed email_verification and merchant_password_reset email templates - Fix db-reset Makefile to run all init-prod seed scripts - Add UserAuthService to satisfy architecture validation rules - Add 52 new tests (unit + integration) with full coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
82 lines
2.6 KiB
Python
82 lines
2.6 KiB
Python
# app/modules/tenancy/routes/api/email_verification.py
|
|
"""
|
|
Email verification API endpoints.
|
|
|
|
Public endpoints (no auth required):
|
|
- POST /resend-verification - Resend verification email
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.core.environment import should_use_secure_cookies
|
|
from app.modules.tenancy.models.email_verification_token import (
|
|
EmailVerificationToken, # noqa: API-007
|
|
)
|
|
from app.modules.tenancy.services.user_auth_service import user_auth_service
|
|
|
|
email_verification_api_router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ResendVerificationRequest(BaseModel):
|
|
email: str
|
|
|
|
|
|
class ResendVerificationResponse(BaseModel):
|
|
message: str
|
|
|
|
|
|
@email_verification_api_router.post(
|
|
"/resend-verification", response_model=ResendVerificationResponse
|
|
)
|
|
def resend_verification(
|
|
request: Request,
|
|
body: ResendVerificationRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Resend email verification link.
|
|
|
|
Always returns success to prevent email enumeration.
|
|
"""
|
|
user, plaintext_token = user_auth_service.request_verification_resend(db, body.email)
|
|
|
|
if user and plaintext_token:
|
|
try:
|
|
scheme = "https" if should_use_secure_cookies() else "http"
|
|
host = request.headers.get("host", "localhost:8000")
|
|
verification_link = f"{scheme}://{host}/verify-email?token={plaintext_token}"
|
|
|
|
from app.modules.messaging.services.email_service import EmailService
|
|
|
|
email_service = EmailService(db)
|
|
email_service.send_template(
|
|
template_code="email_verification",
|
|
to_email=user.email,
|
|
to_name=user.username,
|
|
language="en",
|
|
variables={
|
|
"first_name": user.username,
|
|
"verification_link": verification_link,
|
|
"expiry_hours": str(EmailVerificationToken.TOKEN_EXPIRY_HOURS),
|
|
"platform_name": "Orion",
|
|
},
|
|
)
|
|
|
|
db.commit()
|
|
logger.info(f"Verification email resent to {user.email}")
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to resend verification email: {e}") # noqa: SEC021
|
|
else:
|
|
logger.info(f"Resend verification requested for {body.email} (not found or already verified)")
|
|
|
|
return ResendVerificationResponse(
|
|
message="If an account exists with this email and is not yet verified, a verification link has been sent."
|
|
)
|