用户要求:内测的固定验证码 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:
+67
-7
@@ -29,10 +29,14 @@
|
||||
| 2010 | 429 | 验证码请求太频繁 | 倒计时 60 秒 |
|
||||
| 2011 | 400 | 验证码错误/已过期 | 重发 |
|
||||
| 2012 | 403 | 账号被禁用 | 联系管理员 |
|
||||
| 2013 | 400 | 密码错误,或这个号还没设过密码 | 提示改用验证码登录 |
|
||||
| 2013 | 400 | 密码错误,或这个号还没设过密码 | 提示核对账号与密码 |
|
||||
| 2014 | 400 | 该账号没有密码(保留码) | 先设密码 |
|
||||
| 2015 | 422 | 密码太弱(少于 8 位 / 缺字母或数字 / 与旧密码相同) | 就地提示规则 |
|
||||
| 2016 | 429 | 密码连错太多次(15 分钟内 10 次) | 提示稍后再试或用验证码 |
|
||||
| 2016 | 429 | 密码连错太多次(15 分钟内 10 次) | 提示稍后再试 |
|
||||
| 2017 | 403 | 这条登录方式没开(默认只开密码) | 换密码登录 |
|
||||
| 2018 | 422 | 邮箱格式不对 | 就地提示 |
|
||||
| 2019 | 409 | 邮箱已被别的账号占用 | 换一个 |
|
||||
| 2020 | 502 | 发信失败(SMTP 认证/收件人/网络) | 去「系统设置」核对授权码 |
|
||||
| 3001 | 404 | 工作区不存在 | — |
|
||||
| 3003 | 403 | 不是该工作区成员 | **不要**提示"工作区不存在" |
|
||||
| 3004 | 403 | 角色权限不足 | 隐藏对应按钮 |
|
||||
@@ -65,6 +69,19 @@
|
||||
|
||||
## 1. 认证 `/auth`
|
||||
|
||||
登录方式有三种,**默认只开密码那一种**。服务端把"现在开着哪些"告诉前端,前端据此渲染,
|
||||
免得用户点进一条关着的路:
|
||||
|
||||
### `GET /auth/methods`
|
||||
|
||||
```json
|
||||
{ "password": true, "email_password": true, "sms_code": false }
|
||||
```
|
||||
|
||||
`SMS_LOGIN_ENABLED=false`(默认)时,`POST /auth/sms/send` 与 `POST /auth/login` 都回 `2017`
|
||||
「短信验证码登录暂未开放」。理由:dev 环境的固定码等于"知道手机号就能进任何账号",
|
||||
这条路必须显式打开,生产打开时还会强制要求真短信通道(见 `deploy` 文档的启动校验)。
|
||||
|
||||
### `POST /auth/sms/send`
|
||||
|
||||
```json
|
||||
@@ -72,7 +89,7 @@
|
||||
```
|
||||
|
||||
响应 `data: { dev_code?: "123456" }`。`ENV=dev` 时才回显验证码,生产没有这个字段。
|
||||
60 秒内重复请求 → `2010`。
|
||||
60 秒内重复请求 → `2010`。短信登录关着时 → `2017`。
|
||||
|
||||
### `POST /auth/login`
|
||||
|
||||
@@ -97,12 +114,19 @@
|
||||
|
||||
### `POST /auth/login/password`
|
||||
|
||||
手机号和邮箱都走这一个入口,`phone` 与 `email` **二选一**(都给或都不给 → 422):
|
||||
|
||||
```json
|
||||
{ "phone": "13800000000", "password": "dadao2026" }
|
||||
{ "email": "shufen@126.com", "password": "dadao2026" }
|
||||
```
|
||||
|
||||
返回与验证码登录同形状的 `{ user, tokens }`。**没设过密码的账号一律回 `2013`**,与"密码错"同一个码
|
||||
(不区分账号是否存在,少一个探测面)。15 分钟内连错 10 次 → `2016`,期间正确密码也挡住。
|
||||
(不区分账号是否存在,少一个探测面;手机号与邮箱两种账号也不会互相暴露)。15 分钟内连错 10 次 → `2016`,
|
||||
期间正确密码也挡住,计数键是"手机号或邮箱"本身。
|
||||
|
||||
邮箱按 `lower(email)` 比较,绑定与登录都做 `strip` + 小写归一,所以 `Shufen@126.com` 与
|
||||
`shufen@126.com` 是同一个账号。
|
||||
|
||||
密码校验是 scrypt(`n=2^14, r=8, p=1`,标准库实现,不引 passlib),库里存
|
||||
`scrypt$n$r$p$salt$hash`。`password_hash` 这一列**永不出现在任何响应里**。
|
||||
@@ -119,15 +143,51 @@
|
||||
### `PATCH /auth/me`
|
||||
|
||||
```json
|
||||
{ "nickname": "大道" }
|
||||
{ "nickname": "大道", "email": "shufen@126.com" }
|
||||
```
|
||||
|
||||
返回 `{ "user": { … } }`。空昵称(strip 后为空)→ `1002`。手机号暂不支持自助换号。
|
||||
字段都可选,只改传上来的。邮箱留空串表示**解绑**(之后不能再用邮箱登录)。
|
||||
格式不对 → `2018`,已被别的账号占用 → `2019`。返回 `{ "user": { … } }`。
|
||||
空昵称(strip 后为空)→ `1002`。手机号暂不支持自助换号。
|
||||
|
||||
### `GET /auth/me`
|
||||
|
||||
返回 `{ user, workspaces[] }`,`workspaces[].role` 是当前用户在该工作区的角色。
|
||||
`user` 里带 `has_password`,前端据此决定账号页显示"设置密码"还是"修改密码"。
|
||||
`user` 里带 `has_password`(前端据此显示"设置密码"还是"修改密码")、`email`、`platform_admin`
|
||||
(能不能进「系统设置」)。
|
||||
|
||||
---
|
||||
|
||||
## 1.5 系统设置 `/settings`
|
||||
|
||||
平台级配置,**全站一份、不挂租户**,只有 `PLATFORM_ADMIN_PHONES` 里的人能读写(否则 `3004`)。
|
||||
目前只有发信邮箱一项——它承载找回密码与以后的邮件通知,属于"改一次全站受影响"的东西,
|
||||
放后台可改而不用登服务器改 env。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/settings/smtp` | 当前配置(**不含授权码**) |
|
||||
| PUT | `/settings/smtp` | 保存,立即生效,不重启 |
|
||||
| POST | `/settings/smtp/test` | 发一封测试邮件,确认通道通不通 |
|
||||
|
||||
```json
|
||||
// PUT /settings/smtp
|
||||
{ "host": "smtp.126.com", "port": 465, "username": "shufenhf@126.com",
|
||||
"password": "客户端授权码", "from_name": "达道Task", "use_ssl": true }
|
||||
```
|
||||
|
||||
`password` 传 `null` 或空串表示**沿用旧值**(改别的字段时不用重填授权码)。
|
||||
|
||||
```json
|
||||
// GET /settings/smtp
|
||||
{ "host": "smtp.126.com", "port": 465, "username": "shufenhf@126.com",
|
||||
"password_set": true, "from_name": "达道Task", "use_ssl": true,
|
||||
"configured": true, "source": "db", "updated_at": "2026-11-02T12:00:00Z" }
|
||||
```
|
||||
|
||||
`source` 说明当前值来自数据库(`db`)、服务器环境变量兜底(`env`),还是根本没配(`none`)。
|
||||
发信失败统一 `2020`(`502`),消息里写清是认证失败还是收件人被拒——授权码要用邮箱服务商的
|
||||
**客户端授权码**,不是登录密码。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user