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>
111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
# app/modules/tenancy/routes/pages/email_verification.py
|
|
"""
|
|
Email verification page route.
|
|
|
|
Renders HTML result pages for email verification:
|
|
- GET /verify-email?token={token} - Verify email and show result page
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, Query, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.modules.tenancy.models.email_verification_token import EmailVerificationToken
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
ROUTE_CONFIG = {
|
|
"prefix": "",
|
|
}
|
|
|
|
# Shared HTML template for verification result
|
|
_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{title} - Orion</title>
|
|
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
|
<style>
|
|
body {{ font-family: 'Inter', Arial, sans-serif; margin: 0; padding: 0; background: #f9fafb;
|
|
display: flex; align-items: center; justify-content: center; min-height: 100vh; }}
|
|
.card {{ background: white; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
|
|
max-width: 480px; width: 90%; text-align: center; overflow: hidden; }}
|
|
.header {{ padding: 30px; background: linear-gradient(135deg, {color_from} 0%, {color_to} 100%); }}
|
|
.header h1 {{ color: white; margin: 0; font-size: 24px; }}
|
|
.body {{ padding: 30px; }}
|
|
.body p {{ color: #4b5563; line-height: 1.6; }}
|
|
.icon {{ font-size: 48px; margin-bottom: 16px; }}
|
|
.btn {{ display: inline-block; padding: 12px 24px; background: #6366f1; color: white;
|
|
text-decoration: none; border-radius: 8px; font-weight: 600; margin-top: 16px; }}
|
|
.btn:hover {{ background: #4f46e5; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<div class="header"><h1>{title}</h1></div>
|
|
<div class="body">
|
|
<div class="icon">{icon}</div>
|
|
<p>{message}</p>
|
|
<a href="{link_url}" class="btn">{link_text}</a>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>"""
|
|
|
|
|
|
@router.get("/verify-email", response_class=HTMLResponse, include_in_schema=False)
|
|
def verify_email_page(
|
|
request: Request,
|
|
token: str = Query(..., description="Email verification token"),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Verify email address via token link from email.
|
|
|
|
Validates the token, marks user's email as verified, and renders
|
|
a success or error HTML page.
|
|
"""
|
|
token_record = EmailVerificationToken.find_valid_token(db, token)
|
|
|
|
if not token_record:
|
|
logger.warning("Invalid or expired email verification token used")
|
|
return HTMLResponse(
|
|
content=_HTML_TEMPLATE.format(
|
|
title="Verification Failed",
|
|
color_from="#ef4444",
|
|
color_to="#dc2626",
|
|
icon="❌",
|
|
message="This verification link is invalid or has expired. "
|
|
"Please request a new verification email.",
|
|
link_url="/merchants/login",
|
|
link_text="Go to Login",
|
|
),
|
|
status_code=400,
|
|
)
|
|
|
|
# Mark token as used and verify user's email
|
|
user = token_record.user
|
|
user.is_email_verified = True
|
|
token_record.mark_used(db)
|
|
db.commit()
|
|
|
|
logger.info(f"Email verified for user {user.id} ({user.email})")
|
|
|
|
return HTMLResponse(
|
|
content=_HTML_TEMPLATE.format(
|
|
title="Email Verified",
|
|
color_from="#10b981",
|
|
color_to="#059669",
|
|
icon="✅",
|
|
message="Your email address has been successfully verified! "
|
|
"You can now log in to your account.",
|
|
link_url="/merchants/login",
|
|
link_text="Go to Login",
|
|
)
|
|
)
|