id_session 落地:opaque refresh(token_urlsafe(48))+ sha256 哈希存储 + 一次性轮换(旧行撤销与新行同一事务)+ logout 幂等撤销 + 已撤销重放检测 告警(V1 不做家族连坐);错误码 40103/40104/40105 接入。新增 POST /auth/refresh、POST /auth/logout;dev-token 扩展为双令牌,旧字段 access_token/token_type/expires_in/user_id 原名保留,新增 refresh_token/refresh_expires_in(smoke.sh 不受影响)。 测试:tests/unit/test_refresh_tokens.py 覆盖 token 长度/字符域/唯一性、 哈希稳定性与不可逆(7 例,不连网不连库);tests/integration/ test_auth_refresh.py 覆盖轮换后新 access 可调 /quota、旧 refresh 二次 使用 40105、logout 后失效 40105、伪造 40103、TTL=0 过期 40104(5 例)。 门禁全绿:ruff check/format、mypy 两段、lint-imports 4 契约、 单测 176 通过、集成 8 通过。 偏差说明:1) SessionIssued 较任务文档字段清单增加 user_id —— /auth/refresh 换发 access JWT 必须知道用户,opaque refresh 本身不携带 信息,只能随签发结果带回;2) 过期分支「顺手落库 revoked=true」在 HTTP 路径会随 401 的统一回滚一起丢失(仅事务内标记,已注释说明,不影响判定 语义);3) repo 撤销用 SELECT FOR UPDATE + ORM 变更而非 UPDATE rowcount —— 对齐 billing.repo 现行模式,且 rowcount 在 mypy 下类型 不可用。
packages/identity/ — 账号与会话
职责(架构 §4.1):账号、登录、会话、设备、游客迁移、同意授权记录。
不负责:权益判定(归 billing)。
铁律
identity 不得依赖任何其他业务域(架构 §4.2)—— 登录链路不能被业务拖累。
import-linter 契约 identity must not depend on any other business domain 会
在 CI 拦下违规 import。
已实现
id_user/id_user_identity/id_consent表- 外部身份 → 内部
user_id映射(内部主键与外部身份解耦,便于加登录通道) - 同意记录写入
- JWT access token(
packages/shared/security.py)
待实现
| 需求 | 内容 |
|---|---|
| FR-ACC-01 | 华为账号登录(UnionID/OpenID) |
| FR-ACC-04 | 手机号验证码登录(5 分钟有效、单号限频) |
| FR-ACC-05 | 双令牌:id_session + 服务端可撤销的 refresh token |
| FR-ACC-02/03 | 游客模式与数据合并(合并必须幂等) |
| FR-ACC-08 | 人脸单独同意页(CR-01:必须独立,不得混入总协议) |
| FR-ACC-09 | 账号注销:7 日内彻底删除,含 COS 对象(CR-03) |
| FR-ACC-11 | 未成年人判定与功能限制(CR-04) |
| FR-SYS-01 | 越权防护:所有资源访问校验归属,不能只靠 ID 不可猜 |
POST /auth/dev-token 只是脚手架,DEV_AUTH_ENABLED=false 时不可用,
生产环境必须关闭。