用户要求:内测的固定验证码 123456 等于"知道手机号就能进任何账号",先关掉; 登录改用账号密码,并多一种"邮箱当账号名"的方式;发信邮箱要能在后台改。 短信那条路(默认关): - `SMS_LOGIN_ENABLED=false`,`/auth/sms/send` 与 `/auth/login` 统一回 2017,不是静默失败; - 生产校验改成"开着短信登录就必须配真通道",不开时 provider 是 dev 也不拦; - 新增 `GET /auth/methods`,登录页据此渲染,关着的路不出现(少一次"点进去才发现不能用")。 邮箱登录: - `app_user.email` + `uq_app_user_email`(`lower(email)` 部分索引),登录与绑定都归一成小写; - `POST /auth/login/password` 收 `phone` 或 `email`(二选一),失败统一 2013、连错 10 次锁 15 分钟; - 绑定在 `PATCH /auth/me`:格式错 2018、被占用 2019、留空串表示解绑。 系统设置(跨租户,只有平台管理员能改): - 新表 `system_setting`(不挂租户、无 RLS,写权限由 `deps.platform_admin` 把关); - `GET/PUT /settings/smtp`、`POST /settings/smtp/test`,授权码只写不读,留空表示沿用旧值; - `services/mail.py` 只用标准库 smtplib:465 走 SSL、587 走 STARTTLS,认证/拒收/网络失败都归 2020 但消息不同; - Web 新增「系统设置」页(状态卡 + 测试发信),侧栏只对平台管理员显示入口;账号页补「邮箱」。 验证:pytest 130 passed、ruff check/format 全绿、tsc 通过、vite build 通过; `web/e2e/login-email-smtp.mjs` 15 项全绿(含用 126 授权码真发一封测试邮件)。 顺带记下的两个坑:`platform_admin_phones` 是 list,pydantic-settings 会按 JSON 解析, 要用 `NoDecode` 交给 before-validator 拆逗号;迁移号写成 0009 会撞既有的 0009,一并改成 0010。
This commit is contained in:
+31
-7
@@ -4,10 +4,10 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from typing import Literal
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
|
||||
|
||||
DEV_JWT_SECRET = "dev-only-change-me" # noqa: S105 - 仅开发默认值,生产启动时会被校验拦下
|
||||
|
||||
@@ -60,6 +60,30 @@ class Settings(BaseSettings):
|
||||
# 内测开关:生产默认禁止固定验证码。确实要在内网试用时显式打开,
|
||||
# 启动会打 WARNING(见 main.py),其余生产校验照旧——绝不能配合公网暴露。
|
||||
internal_test_login: bool = False
|
||||
# 登录方式开关:手机验证码这条**默认关掉**。内测期的固定验证码等于"知道手机号就能进任意账号",
|
||||
# 关掉后 /auth/sms/send 与 /auth/login 统一回 2017,登录走「手机号 / 邮箱 + 密码」。
|
||||
# 将来接上真短信通道把它打开即可,登录页会按 /auth/methods 自动列出可用的方式。
|
||||
sms_login_enabled: bool = False
|
||||
|
||||
# 平台管理员:能改系统设置(SMTP 等)。按手机号白名单,不挂工作区角色——
|
||||
# 系统设置是**跨租户**的,某个工作区的 owner 也不该看到平台的发信配置。
|
||||
# `NoDecode` + 下面的 before-validator:这个值环境变量里是逗号分隔的一串,
|
||||
# 不让 pydantic-settings 先按 JSON 解析(那样 `1360...,1390...` 会直接启动失败)。
|
||||
platform_admin_phones: Annotated[list[str], NoDecode] = Field(
|
||||
default_factory=lambda: ["13605513683"]
|
||||
)
|
||||
|
||||
# ── 邮件(SMTP)────────────────────────────────────────
|
||||
# 以「系统设置」页里存的值为准(DB),这里只是首次启动前的兜底。
|
||||
# 改 SMTP 不该要人登服务器改 env 再重启——所以真配置在 DB 里(见 services/mail.py)。
|
||||
smtp_host: str | None = None
|
||||
smtp_port: int = 465
|
||||
smtp_username: str | None = None
|
||||
smtp_password: str | None = None
|
||||
smtp_from_name: str = "达道Task"
|
||||
# 465 走 SSL,587 走 STARTTLS;126/163 这类用 465 + SSL
|
||||
smtp_use_ssl: bool = True
|
||||
smtp_timeout_seconds: int = 10
|
||||
|
||||
# ── 幂等 ──────────────────────────────────────────────
|
||||
idempotency_enabled: bool = True
|
||||
@@ -117,10 +141,10 @@ class Settings(BaseSettings):
|
||||
build_commit: str = "unknown"
|
||||
build_time: str = "unknown"
|
||||
|
||||
@field_validator("cors_origins", mode="before")
|
||||
@field_validator("cors_origins", "platform_admin_phones", mode="before")
|
||||
@classmethod
|
||||
def _split_origins(cls, value: object) -> object:
|
||||
"""支持 CORS_ORIGINS="a,b" 这种写法。"""
|
||||
"""支持 CORS_ORIGINS="a,b" 这种写法(平台管理员白名单同理)。"""
|
||||
if isinstance(value, str) and not value.startswith("["):
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
return value
|
||||
@@ -134,10 +158,10 @@ class Settings(BaseSettings):
|
||||
problems.append("JWT_SECRET 仍是开发默认值或长度不足 32")
|
||||
if "dadao_dev_pw" in self.database_url:
|
||||
problems.append("DATABASE_URL 仍在用开发默认口令")
|
||||
if self.sms_provider == "dev" and not self.internal_test_login:
|
||||
if self.sms_login_enabled and self.sms_provider == "dev":
|
||||
problems.append(
|
||||
"生产环境不允许 SMS_PROVIDER=dev(任意验证码可登录);"
|
||||
"内网试用确需固定验证码时显式设置 INTERNAL_TEST_LOGIN=true"
|
||||
"短信验证码登录开着(SMS_LOGIN_ENABLED=true),但 SMS_PROVIDER=dev 只会发固定验证码"
|
||||
"(等于知道手机号就能进);接真短信通道之前请把 SMS_LOGIN_ENABLED 关掉"
|
||||
)
|
||||
if problems:
|
||||
raise RuntimeError("配置校验失败:" + ";".join(problems))
|
||||
|
||||
Reference in New Issue
Block a user