feat: harden account security flows

This commit is contained in:
2026-08-04 14:29:51 +08:00
parent 630252e2ff
commit c71b1351d6
14 changed files with 892 additions and 56 deletions
+34
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
from html import escape
from typing import Any
import resend
@@ -15,6 +16,39 @@ class EmailDeliveryError(RuntimeError):
"""Raised when Resend rejects or cannot deliver an email request."""
def render_transactional_email(*, title: str, content: str) -> str:
"""Render a conservative table-based email layout for broad client support."""
return f"""<!doctype html>
<html lang="zh-CN"><body style="margin:0;background:#f3f5f8;color:#172033;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',Arial,sans-serif;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="padding:32px 16px;background:#f3f5f8;"><tr><td align="center">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width:600px;background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;">
<tr><td style="padding:24px 32px;background:#005eb8;color:#ffffff;font-size:18px;font-weight:700;">供水管道健康评估系统</td></tr>
<tr><td style="padding:32px;"><h1 style="margin:0 0 12px;font-size:22px;line-height:1.4;color:#172033;">{escape(title)}</h1>{content}</td></tr>
<tr><td style="padding:18px 32px;border-top:1px solid #e2e8f0;color:#64748b;font-size:12px;line-height:1.7;">此邮件由系统自动发送,请勿直接回复。<br>如非本人操作,请忽略此邮件并及时检查账户安全。</td></tr>
</table>
</td></tr></table>
</body></html>"""
def verification_code_email(*, code: str, minutes: int, purpose: str) -> str:
"""Render the verification email without exposing dynamic HTML to callers."""
content = f"""
<p style="margin:0;color:#475569;font-size:15px;line-height:1.8;">你正在进行{escape(purpose)}。请在 {minutes} 分钟内输入下方验证码。</p>
<div style="margin:24px 0;padding:18px;border-radius:8px;background:#eaf3ff;color:#005eb8;text-align:center;font-size:30px;font-weight:700;letter-spacing:8px;">{escape(code)}</div>
<p style="margin:0;color:#64748b;font-size:13px;line-height:1.8;">验证码仅可使用一次,请勿向任何人透露。</p>"""
return render_transactional_email(title="邮箱验证码", content=content)
def password_reset_notice_email(*, username: str, reset_url: str) -> str:
"""Render an administrator-initiated password reset notification."""
safe_url = escape(reset_url, quote=True)
content = f"""
<p style="margin:0;color:#475569;font-size:15px;line-height:1.8;">{escape(username)},管理员已要求你重置密码。</p>
<p style="margin:16px 0 24px;color:#475569;font-size:15px;line-height:1.8;">请通过下方按钮进入找回密码流程,系统会向本邮箱发送一次性验证码。</p>
<p style="margin:0;"><a href="{safe_url}" style="display:inline-block;padding:12px 20px;border-radius:6px;background:#005eb8;color:#ffffff;font-size:14px;font-weight:700;text-decoration:none;">重置密码</a></p>"""
return render_transactional_email(title="请重置密码", content=content)
def send_transactional_email(*, to: str, subject: str, html: str) -> dict[str, Any]:
"""Send one application-generated email through Resend."""
api_key = current_app.config["RESEND_API_KEY"]