- 公司账号创建改为随机一次性初始密码,只在创建响应中显示一次, 密码保证不等于用户名;删除「用户名即初始密码」兼容分支,继续 强制首次登录改密。 - 跨公司相同字节文件上传只返回通用重复状态:不再返回其他公司的 原批次 ID、银行、模板、期间、交易数或诊断;同公司重复上传的 幂等摘要保持可用。 - 补充服务端回归测试,覆盖同公司与跨公司两个分支及随机密码; 完整测试 85 项全绿,node --check 通过。
67 lines
3.7 KiB
Markdown
67 lines
3.7 KiB
Markdown
# 003 身份认证与租户隔离技术决策
|
||
|
||
对应 Issue:B-40(`docs/issues/003-p0-auth-and-tenant-isolation.md`)。
|
||
|
||
## 密码散列:标准库 PBKDF2-HMAC-SHA256
|
||
|
||
- 只使用 Python 标准库(`hashlib.pbkdf2_hmac` + `hmac.compare_digest` +
|
||
`secrets`),不引入 bcrypt/argon2 等第三方依赖,与离线内网部署约束一致。
|
||
- 260 000 次迭代、16 字节随机盐,存储格式
|
||
`pbkdf2_sha256$<迭代数>$<盐hex>$<摘要hex>`,自描述、可平滑升级参数。
|
||
- 明文密码永不入库、永不进日志、永不写入 `audit_log`;
|
||
`password_hash` 字段从不出现在任何 API 响应中(有测试断言)。
|
||
|
||
## 会话:数据库保存令牌摘要,8 小时绝对过期
|
||
|
||
- 令牌为 `secrets.token_urlsafe(32)`,数据库只存其 SHA-256 摘要;
|
||
数据库泄露不直接暴露可用令牌。
|
||
- 绝对过期 8 小时(一个工作班次),不做滑动续期,语义简单可测。
|
||
- 会话可吊销:退出登录、停用账号、重置密码都会立即吊销该用户全部会话。
|
||
- Cookie 名 `cw_session`,`HttpOnly; SameSite=Lax; Path=/`。
|
||
当前是纯 HTTP 的局域网部署,**刻意不加 `Secure`**(加了浏览器会直接拒发);
|
||
若未来上 HTTPS,应补上 `Secure` 并配置反向代理。
|
||
|
||
## 登录限流:同一 (账号, IP) 10 分钟内失败 5 次即锁定
|
||
|
||
- 计数来自 `login_attempts` 表,窗口为滚动 10 分钟;触发后返回 429,
|
||
且在窗口内不再记录新尝试,行为确定、可测试。
|
||
- 失败提示统一为「账号或密码不正确」,不泄露是哪一部分错误。
|
||
|
||
## 角色与公司绑定
|
||
|
||
- `users.role` 仅 `admin` / `company`;数据库 CHECK 约束强制
|
||
公司账号必须绑定公司、管理员不得绑定公司。
|
||
- 登录时前端选择工作端口(portal),服务端校验 portal 与角色一致,
|
||
不匹配返回 403「账号与该工作端口不匹配」。
|
||
- 管理员创建公司账号时,初始密码为随机一次性密码并置
|
||
`must_change_password=1`;首次登录必须改密,改密前所有业务 API 返回 403。
|
||
初始密码只在创建成功响应中显示一次,永不入库明文、永不写日志;
|
||
创建用户的审计 detail 只记录公司,不重复账号名。
|
||
- 「重置密码」同样生成随机一次性密码并吊销既有会话。
|
||
|
||
## 租户隔离在服务端强制,404 优于 403
|
||
|
||
- 公司用户的批次列表、行明细、CSV 导出、上传归属全部由服务端按
|
||
会话中的 `company_id` 过滤;请求体/参数里的 `company_id` 对公司用户
|
||
一律忽略(跨公司上传防护)。
|
||
- 访问他公司批次返回 404 而非 403:不暴露「该批次存在但属于别人」这一事实,
|
||
避免 IDOR 探测。导出时显式指定他公司 `company_id` 仍返回 403,
|
||
因为用户已声明知道该公司存在,此时给出明确拒绝更有操作性。
|
||
- `GET /admin.html`、`/company.html` 的 302 跳转只是 UX 层引导,
|
||
不是权限边界;真正的边界全部在 API 上。
|
||
|
||
## 引导管理员(bootstrap)
|
||
|
||
- 服务启动迁移后若无任何 admin 账号,创建 `APP_ADMIN_USERNAME`
|
||
(默认 `group-admin`);密码取 `APP_BOOTSTRAP_ADMIN_PASSWORD`,
|
||
未设置则生成随机初始密码并**只打印一次到 stdout**(不写日志文件),
|
||
`must_change_password=1`。
|
||
- 不在迁移或代码中预置任何公司或用户;公司与公司账号全部由管理员
|
||
通过 `/api/admin/companies`、`/api/admin/users` 动态创建。
|
||
|
||
## 审计
|
||
|
||
- `audit_log` 记录登录成功/失败、退出、改密、上传、导出(含公司范围与行数)、
|
||
建公司、建/停/启用户、重置密码等动作,含操作者、目标、IP、时间。
|
||
- 初始密码、新旧密码均不进入审计内容。
|