feat(server): OIDC 登录支持(F23)

- 引入 openid-client,新增 /api/auth/oidc/login + /api/auth/oidc/callback
- 通过 email 绑定既有员工账号,保留密码登录 fallback
- OIDC 状态存 cookie(HMAC) + PKCE + SameSite=lax 防 CSRF
- 更新 env/README 与 v3 CSV 状态推进
这个提交包含在:
2026-02-09 21:01:36 +08:00
父节点 1855a82011
当前提交 3cc3013d5a
共修改 10 个文件,包含 386 行新增和 1 行删除
@@ -24,7 +24,7 @@ F19d,作业迁移(超期归档 -> pg-boss),"将 90 天游离归档迁移到
F20,Worker进程拆分(可选),"提供 API 进程与 Worker 进程拆分的部署方式(compose 开关),保持单机也可跑","开启后:API 不跑作业;Worker 只跑作业;两者共享 DATABASE_URL;功能不回归","manual: docker compose 起两个进程并跑回归脚本",manual,DONE,DONE,DONE,"deploy/trialrun/docker-compose.yml | server/src/index.js | server/README.md","[""F19""]","仍保持单机;为未来横向扩展铺路"
F21,对象存储(MinIO/S3兼容),"存储从本地目录演进到 S3 兼容(先 MinIO),保持 signed-url 契约不变或提供兼容层","上传/预览/签名URL 在 MinIO 下可用;回归与 E2E 通过;迁移路径清晰可回滚","manual: trialrun 环境启用 MinIO 并完成一次上传预览",manual,DONE,DONE,DONE,"server/src/services/storage/* | deploy/trialrun/* | server/README.md","[""F20""]","优先用 @aws-sdk/client-s3;单机优先 MinIO"
F22,可观测性增强(metrics/日志),"新增 /metrics(Prometheus)与更完整的结构化日志字段规范(不落敏感)","metrics 可抓取关键指标(HTTP/作业/DB);日志具备 requestId/actorId/targetId 等字段","manual: curl /metrics 并核对字段;触发作业观察指标变化",manual,DONE,DONE,DONE,"server/src/app.js | server/src/observability/* | deploy/trialrun/*","[""F19""]","优先用 prom-client;OTel 放更后"
F23,SSO/OIDC(可选,P7+),"支持 OIDC 登录(企业 SSO),保留现有密码登录作为 fallback","可通过 OIDC 登录并绑定员工;权限模型不变;可配置开关","manual: 配置 OIDC provider 后走一次登录回调",manual,TODO,TODO,TODO,"server/src/routes/auth.js | server/src/services/oidc/* | server/README.md","[""F1""]","优先复用 openid-client/passport 策略"
F23,SSO/OIDC(可选,P7+),"支持 OIDC 登录(企业 SSO),保留现有密码登录作为 fallback","可通过 OIDC 登录并绑定员工;权限模型不变;可配置开关","manual: 配置 OIDC provider 后走一次登录回调",manual,DONE,DONE,DONE,"server/src/routes/auth.js | server/src/services/oidc/* | server/README.md","[""F1""]","优先复用 openid-client/passport 策略"
F24,工作流扩展到其他实体(P7+),"抽象 entity_type 扩展到请假/采购等(先骨架:definition+instance+task 复用)","新实体可创建 instance 并走同审批动作;不影响报销既有链路","manual: seed 一个 leave 定义并跑一条最小链路",manual,TODO,TODO,TODO,"server/src/routes/workflow.js | server/src/routes/leave.js | server/prisma/schema.prisma","[""F19""]","只做骨架与复用点,不做完整业务表单"
F25,敏感字段加密与脱敏(P6+),"落实员工银行卡/身份证等敏感字段的可逆加密与脱敏输出,并对查看/变更写审计","对外响应仅脱敏;解密仅限特权权限并写 audit;不在日志/错误中泄露明文","manual: 创建员工并查看返回脱敏;用特权接口取明文并在 audit 检索记录",manual,DONE,DONE,DONE,"server/src/routes/users.js | server/src/services/crypto/* | server/src/routes/audit.js","[""F1""]","优先用成熟库(libsodium/crypto),并明确密钥轮换策略"
F26,部门API(departments),"补齐部门树/列表与管理接口:GET /api/departments(默认隐藏归档,可 query 开启);POST/PATCH;POST /:id/archive(无子部门/无在职员工约束);写 audit_logs","部门树可用且过滤正确;归档约束正确;system.users 才可写;审计可追踪","manual: 启动 server;以 [email protected]/AdminPassword1234 登录拿 accessToken;curl -sS -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/departments' | jq;curl -sS -X POST -H 'authorization: Bearer <TOKEN>' -H 'content-type: application/json' 'http://127.0.0.1:3000/api/departments' -d '{name:...}';curl -sS -X PATCH -H 'authorization: Bearer <TOKEN>' -H 'content-type: application/json' 'http://127.0.0.1:3000/api/departments/<ID>' -d '{name:...}';curl -sS -X POST -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/departments/<ID>/archive';可选 curl -sS -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/audit-logs?action=DEPARTMENT_*' | jq",manual,DONE,DONE,DONE,"server/src/routes/departments.js | server/src/app.js | server/README.md","[""F1""]","单机试运行优先;树构造按 parent_id 组装"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
24 F20 Worker进程拆分(可选) 提供 API 进程与 Worker 进程拆分的部署方式(compose 开关),保持单机也可跑 开启后:API 不跑作业;Worker 只跑作业;两者共享 DATABASE_URL;功能不回归 manual: docker compose 起两个进程并跑回归脚本 manual DONE DONE DONE deploy/trialrun/docker-compose.yml | server/src/index.js | server/README.md ["F19"] 仍保持单机;为未来横向扩展铺路
25 F21 对象存储(MinIO/S3兼容) 存储从本地目录演进到 S3 兼容(先 MinIO),保持 signed-url 契约不变或提供兼容层 上传/预览/签名URL 在 MinIO 下可用;回归与 E2E 通过;迁移路径清晰可回滚 manual: trialrun 环境启用 MinIO 并完成一次上传预览 manual DONE DONE DONE server/src/services/storage/* | deploy/trialrun/* | server/README.md ["F20"] 优先用 @aws-sdk/client-s3;单机优先 MinIO
26 F22 可观测性增强(metrics/日志) 新增 /metrics(Prometheus)与更完整的结构化日志字段规范(不落敏感) metrics 可抓取关键指标(HTTP/作业/DB);日志具备 requestId/actorId/targetId 等字段 manual: curl /metrics 并核对字段;触发作业观察指标变化 manual DONE DONE DONE server/src/app.js | server/src/observability/* | deploy/trialrun/* ["F19"] 优先用 prom-client;OTel 放更后
27 F23 SSO/OIDC(可选,P7+) 支持 OIDC 登录(企业 SSO),保留现有密码登录作为 fallback 可通过 OIDC 登录并绑定员工;权限模型不变;可配置开关 manual: 配置 OIDC provider 后走一次登录回调 manual TODO DONE TODO DONE TODO DONE server/src/routes/auth.js | server/src/services/oidc/* | server/README.md ["F1"] 优先复用 openid-client/passport 策略
28 F24 工作流扩展到其他实体(P7+) 抽象 entity_type 扩展到请假/采购等(先骨架:definition+instance+task 复用) 新实体可创建 instance 并走同审批动作;不影响报销既有链路 manual: seed 一个 leave 定义并跑一条最小链路 manual TODO TODO TODO server/src/routes/workflow.js | server/src/routes/leave.js | server/prisma/schema.prisma ["F19"] 只做骨架与复用点,不做完整业务表单
29 F25 敏感字段加密与脱敏(P6+) 落实员工银行卡/身份证等敏感字段的可逆加密与脱敏输出,并对查看/变更写审计 对外响应仅脱敏;解密仅限特权权限并写 audit;不在日志/错误中泄露明文 manual: 创建员工并查看返回脱敏;用特权接口取明文并在 audit 检索记录 manual DONE DONE DONE server/src/routes/users.js | server/src/services/crypto/* | server/src/routes/audit.js ["F1"] 优先用成熟库(libsodium/crypto),并明确密钥轮换策略
30 F26 部门API(departments) 补齐部门树/列表与管理接口:GET /api/departments(默认隐藏归档,可 query 开启);POST/PATCH;POST /:id/archive(无子部门/无在职员工约束);写 audit_logs 部门树可用且过滤正确;归档约束正确;system.users 才可写;审计可追踪 manual: 启动 server;以 [email protected]/AdminPassword1234 登录拿 accessToken;curl -sS -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/departments' | jq;curl -sS -X POST -H 'authorization: Bearer <TOKEN>' -H 'content-type: application/json' 'http://127.0.0.1:3000/api/departments' -d '{name:...}';curl -sS -X PATCH -H 'authorization: Bearer <TOKEN>' -H 'content-type: application/json' 'http://127.0.0.1:3000/api/departments/<ID>' -d '{name:...}';curl -sS -X POST -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/departments/<ID>/archive';可选 curl -sS -H 'authorization: Bearer <TOKEN>' 'http://127.0.0.1:3000/api/audit-logs?action=DEPARTMENT_*' | jq manual DONE DONE DONE server/src/routes/departments.js | server/src/app.js | server/README.md ["F1"] 单机试运行优先;树构造按 parent_id 组装