CI / backend (push) Canceled after 0s
用户要求:内测的固定验证码 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。
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
"""生产配置校验:固定验证码必须显式开关,否则启动即失败。
|
|
|
|
这两条是「部署纪律」的守门员:生产一旦误用 dev 验证码,等于任意手机号可登录。
|
|
现在的规则更简单——**短信验证码登录默认关着**,关着时 SMS_PROVIDER 是 dev 也无所谓,
|
|
因为那条路根本不通;只有在 SMS_LOGIN_ENABLED=true 时才要求真通道。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.config import Settings
|
|
|
|
_STRONG_SECRET = "x" * 48
|
|
_PROD_DB = "postgresql+asyncpg://dadao_prod_app:pw@192.168.8.160:8432/dadaotask_prod"
|
|
|
|
|
|
def _prod(**overrides: object) -> Settings:
|
|
values: dict[str, object] = {
|
|
"env": "prod",
|
|
"jwt_secret": _STRONG_SECRET,
|
|
"database_url": _PROD_DB,
|
|
"sms_provider": "aliyun",
|
|
}
|
|
values.update(overrides)
|
|
return Settings(**values)
|
|
|
|
|
|
def test_prod_rejects_dev_sms_when_login_enabled() -> None:
|
|
with pytest.raises(RuntimeError, match="SMS_LOGIN_ENABLED"):
|
|
_prod(sms_provider="dev", sms_login_enabled=True).validate_runtime()
|
|
|
|
|
|
def test_prod_is_fine_with_sms_login_closed() -> None:
|
|
# 默认就是这条:短信登录关着,provider 配成 dev 也不会有人能用它进来
|
|
_prod(sms_provider="dev", sms_login_enabled=False).validate_runtime()
|
|
|
|
|
|
def test_prod_allows_real_sms_provider_when_enabled() -> None:
|
|
_prod(sms_provider="aliyun", sms_login_enabled=True).validate_runtime()
|
|
|
|
|
|
def test_prod_still_rejects_weak_jwt_secret() -> None:
|
|
with pytest.raises(RuntimeError, match="JWT_SECRET"):
|
|
_prod(jwt_secret="short").validate_runtime()
|