feat(email): require verified approval email

Backfill approved legacy users with verified emails and replace the old unverified-email warning setting with a single approval email policy.
This commit is contained in:
2026-05-06 22:12:23 +08:00
parent a17a913618
commit ce55cfc6b3
18 changed files with 328 additions and 55 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ class AdminService:
email_changed = next_email is not None and next_email != user.email
has_verified_email = bool(user.email and user.email_verified_at and not email_changed)
should_warn = (
EmailSettingsService.should_warn_unverified_email_before_approval()
EmailSettingsService.is_verified_email_required_for_approval()
and not has_verified_email
)
if should_warn and not allow_unverified_email:
@@ -28,7 +28,7 @@ class EmailSettingsSnapshot:
notify_token_expiring: bool
notify_check_in_success: bool
require_admin_approval_for_registration: bool
warn_unverified_email_before_approval: bool
require_verified_email_for_approval: bool
has_smtp_sender_password: bool
created_at: datetime | None = None
updated_at: datetime | None = None
@@ -49,7 +49,7 @@ class EmailSettingsService:
notify_token_expiring=True,
notify_check_in_success=True,
require_admin_approval_for_registration=True,
warn_unverified_email_before_approval=True,
require_verified_email_for_approval=True,
)
@staticmethod
@@ -65,7 +65,7 @@ class EmailSettingsService:
notify_token_expiring=True,
notify_check_in_success=True,
require_admin_approval_for_registration=True,
warn_unverified_email_before_approval=True,
require_verified_email_for_approval=True,
has_smtp_sender_password=bool(password),
created_at=None,
updated_at=None,
@@ -102,7 +102,7 @@ class EmailSettingsService:
require_admin_approval_for_registration=bool(
row.require_admin_approval_for_registration
),
warn_unverified_email_before_approval=bool(row.warn_unverified_email_before_approval),
require_verified_email_for_approval=bool(row.require_verified_email_for_approval),
has_smtp_sender_password=bool(password),
created_at=row.created_at,
updated_at=row.updated_at,
@@ -119,7 +119,7 @@ class EmailSettingsService:
notify_token_expiring=snapshot.notify_token_expiring,
notify_check_in_success=snapshot.notify_check_in_success,
require_admin_approval_for_registration=snapshot.require_admin_approval_for_registration,
warn_unverified_email_before_approval=snapshot.warn_unverified_email_before_approval,
require_verified_email_for_approval=snapshot.require_verified_email_for_approval,
has_smtp_sender_password=snapshot.has_smtp_sender_password,
created_at=snapshot.created_at,
updated_at=snapshot.updated_at,
@@ -147,7 +147,7 @@ class EmailSettingsService:
row.require_admin_approval_for_registration = (
payload.require_admin_approval_for_registration
)
row.warn_unverified_email_before_approval = payload.warn_unverified_email_before_approval
row.require_verified_email_for_approval = payload.require_verified_email_for_approval
if payload.clear_smtp_sender_password:
row.smtp_sender_password = ""
@@ -223,14 +223,12 @@ class EmailSettingsService:
db.close()
@staticmethod
def should_warn_unverified_email_before_approval() -> bool:
def is_verified_email_required_for_approval() -> bool:
db = SessionLocal()
try:
try:
return EmailSettingsService.get_snapshot(db).warn_unverified_email_before_approval
return EmailSettingsService.get_snapshot(db).require_verified_email_for_approval
except SQLAlchemyError:
return (
EmailSettingsService._default_snapshot().warn_unverified_email_before_approval
)
return EmailSettingsService._default_snapshot().require_verified_email_for_approval
finally:
db.close()
+2 -4
View File
@@ -257,13 +257,11 @@ class UserService:
user.alias = update_data["alias"]
logger.info(f"用户 ID {user_id} 别名更新: {user.alias}")
# 更新邮箱
# 邮箱必须走 /users/me/email 的验证码流程,不能从普通资料保存绕过。
if "email" in update_data:
next_email = str(update_data["email"]) if update_data["email"] is not None else None
if next_email != user.email:
UserService._clear_email_verification(user)
user.email = next_email
logger.info(f"用户 ID {user_id} 邮箱更新: {user.email}")
raise ValueError("邮箱修改需要先完成验证码验证")
# 更新密码
if "new_password" in update_data and update_data["new_password"]: