oa-email-code-login 实现邮箱验证码登录
这个提交包含在:
@@ -0,0 +1,7 @@
|
||||
{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Validate Trellis task structure, checks and commit readiness."}
|
||||
{"file": ".trellis/spec/hyb-oa/backend-fastify-prisma.md", "reason": "Verify backend auth route, Prisma migration and validation rules."}
|
||||
{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "Verify auth UI hierarchy, PWA behavior and frontend checks."}
|
||||
{"file": ".trellis/spec/hyb-oa/wecom-boundary.md", "reason": "Verify Enterprise WeChat login entry behavior remains bounded."}
|
||||
{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Verify no sensitive auth values are persisted or written to evidence."}
|
||||
{"file": ".trellis/spec/backend/index.md", "reason": "Backend quality review entry point."}
|
||||
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend quality review entry point."}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa Trellis spec entry point and read order."}
|
||||
{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Task contract, approvals, context curation and commit rules."}
|
||||
{"file": ".trellis/spec/hyb-oa/backend-fastify-prisma.md", "reason": "Backend Fastify/Prisma boundaries for new auth endpoints and migration."}
|
||||
{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "Auth page layout, login hierarchy, PWA and frontend verification contracts."}
|
||||
{"file": ".trellis/spec/hyb-oa/wecom-boundary.md", "reason": "Enterprise WeChat login entry must remain separate from email-code login."}
|
||||
{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Sensitive auth data, OTP and Trellis evidence handling."}
|
||||
{"file": ".trellis/spec/backend/index.md", "reason": "Backend coding guideline entry point."}
|
||||
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend React/Vite guideline entry point."}
|
||||
@@ -0,0 +1,57 @@
|
||||
# 邮箱验证码登录实施记录
|
||||
|
||||
## Implementation Scope
|
||||
|
||||
- 新增 `EmailLoginCode` Prisma 模型和迁移,用于登录验证码,不复用 `PasswordResetCode`。
|
||||
- 新增后端接口:
|
||||
- `POST /api/auth/email-login/code`
|
||||
- `POST /api/auth/email-login/verify`
|
||||
- 邮箱验证码使用 HMAC-SHA256 保存 hash,验证码明文只进入邮件正文,不写库。
|
||||
- 验证成功后复用现有 refresh cookie、`has_session`、access token、设备指纹和 session 数量上限逻辑。
|
||||
- 前端登录页新增“密码登录 / 邮箱验证码”切换。
|
||||
- 使用 `frontend-design` 作为前端视觉主准则,将认证页升级为 refined industrial minimal:细网格背景、顶部品牌轨道、单一认证面板。
|
||||
|
||||
## Safety Boundaries
|
||||
|
||||
- 不复用密码找回验证码表和语义。
|
||||
- 不新增短信登录。
|
||||
- 不修改企业微信 OAuth/绑定逻辑。
|
||||
- 不修改企业邮箱/企业微信后台配置。
|
||||
- 不写真实账号密码、验证码、token、Secret 或真实 payload。
|
||||
- 不部署公网,不写真实业务数据。
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- 新增表:`email_login_codes`。
|
||||
- 外键:`email_login_codes.employee_id -> employees.id`,`ON DELETE RESTRICT`。
|
||||
- 索引:
|
||||
- `email_login_codes_employee_id_created_at_idx`
|
||||
- `email_login_codes_expires_at_idx`
|
||||
- 回滚路径:若需回滚本功能,先停用新增前端入口和接口,再执行 `DROP TABLE "email_login_codes";`。该表只保存登录验证码 hash 和请求上下文,不承载业务事实源。
|
||||
|
||||
## Validation Log
|
||||
|
||||
- `cd server && node --check src/routes/auth.js && node --check src/config.js`:已通过。
|
||||
- `cd server && npm run prisma:validate`:已通过。
|
||||
- `cd server && npm test -- --run tests/unit/routes/auth.login.test.js`:已通过,9 passed。
|
||||
- `cd client && npm run typecheck`:已通过。
|
||||
- `cd client && npm run build`:已通过,production build 和 PWA precache 正常生成。
|
||||
- 登录 focused E2E:已通过,3 passed。
|
||||
- 企业微信入口 E2E:已通过,2 passed。
|
||||
- `node scripts/qa/check-ui-design-rules.mjs --strict`:已通过。
|
||||
- `python3 ./.trellis/scripts/task.py validate 05-26-oa-email-code-login`:已通过。
|
||||
- `git diff --check`:已通过。
|
||||
- 视觉截图已生成:
|
||||
- `output/playwright/oa-email-code-login/desktop-password-login.png`
|
||||
- `output/playwright/oa-email-code-login/desktop-email-code-login.png`
|
||||
- `output/playwright/oa-email-code-login/mobile-wecom-login.png`
|
||||
|
||||
## Design Governance Update
|
||||
|
||||
- 已同步 `AGENTS.md`、`.trellis/spec/hyb-oa/frontend-react-vite-design.md` 和 `docs/产品设计规则.md`:前端视觉与交互设计以 `frontend-design` skill 为准。
|
||||
- `docs/产品设计规则.md` 继续保留业务事实、品牌识别、历史页面治理和验收参考,不作为视觉裁决优先级。
|
||||
|
||||
## Known Notes
|
||||
|
||||
- Trellis governance 对历史任务 `05-26-oa-auth-layout-wecom-login-ux` 的 jsonl 仍保留 `docs/产品设计规则.md` context warning;这是已提交历史任务的上下文提醒,不影响本任务通过。
|
||||
- 公网部署和 live smoke 需要本地提交后另行确认。
|
||||
@@ -0,0 +1,131 @@
|
||||
# 邮箱验证码登录
|
||||
|
||||
## Goal
|
||||
|
||||
在现有账号密码登录、企业微信登录、密码找回基础上,新增“邮箱验证码登录”。用户可通过企业邮箱接收 6 位验证码,在不输入密码的情况下登录 OA;同时保持验证码风控、审计、会话签发和现有登录体验一致。
|
||||
|
||||
## Scope
|
||||
|
||||
- 后端新增邮箱验证码登录接口、验证码持久化模型和安全校验。
|
||||
- 前端登录页新增“密码登录 / 邮箱验证码”方式切换。
|
||||
- 复用现有 SMTP 邮件服务,兼容企业微信里的企业邮箱。
|
||||
- 验证成功后复用现有 access token、refresh cookie、`has_session` 和设备指纹会话逻辑。
|
||||
- 增加单元测试、前端 E2E 和视觉截图证据。
|
||||
|
||||
## What I Already Know
|
||||
|
||||
- 用户已确认实现邮箱验证码登录,并要求前端设计优先使用 `frontend-design` skill。
|
||||
- `frontend-design` 已安装到 `/home/shunli/.codex/skills/frontend-design`,当前会话已读取其规则。
|
||||
- 当前登录页已有共享 `AuthPanel`、账号密码登录、企业微信入口、找回密码入口和 PWA 更新保护。
|
||||
- 后端已有 `PasswordResetCode`,仅用于密码找回,不能直接复用为登录验证码。
|
||||
- 后端已有 `sendNotificationEmailSafe` + SMTP 配置,企业邮箱可通过 SMTP 发验证码。
|
||||
- 现有 `/api/auth/login` 已实现设备指纹校验、登录失败锁定、session 数量上限、refresh cookie、`has_session`、审计日志和企业微信待绑定处理。
|
||||
|
||||
## Design Direction
|
||||
|
||||
- 采用 `frontend-design` 的“production-grade、精致、有明确审美方向”要求。
|
||||
- 本项目登录页方向定为“refined industrial minimal”:克制、干净、可信、精密,不做营销式大图和复杂装饰。
|
||||
- 前端视觉和界面设计以 `frontend-design` skill 为准;旧有前端设计文档不再作为审美裁决依据。
|
||||
- 旧有项目文档仅保留业务事实、品牌识别、验收命令和安全边界参考;与 `frontend-design` 的视觉建议冲突时,采用 `frontend-design`。
|
||||
- 安全、隐私、Trellis、生产部署和真实数据边界不属于前端审美规范,仍按项目强约束执行。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 新增登录验证码数据模型,字段至少包括员工、验证码 hash、过期时间、使用时间、尝试次数、创建 IP、创建 UA。
|
||||
- 请求验证码接口:
|
||||
- `POST /api/auth/email-login/code`
|
||||
- 入参:`email`
|
||||
- 无论账号是否存在或是否可用,返回通用成功,避免枚举账号。
|
||||
- 对有效在职账号发送邮件验证码。
|
||||
- 对同一邮箱/IP 使用现有登录限流,并在模型层记录验证码请求上下文。
|
||||
- 校验登录接口:
|
||||
- `POST /api/auth/email-login/verify`
|
||||
- 入参:`email`、`code`
|
||||
- 请求头必须包含 `x-device-fingerprint`。
|
||||
- 验证码必须未过期、未使用、hash 匹配、尝试次数未超限。
|
||||
- 成功后标记验证码已使用,创建 auth session,写 refresh cookie 和 `has_session`,返回 `LoginResponse`。
|
||||
- 成功后清理员工登录失败锁定状态;如 `must_change_password=true`,仍返回该状态让前端跳转修改密码。
|
||||
- 前端登录页:
|
||||
- 普通浏览器下,在主登录区提供“密码登录 / 邮箱验证码”切换。
|
||||
- 邮箱验证码登录包含邮箱输入、发送验证码、验证码输入、登录按钮。
|
||||
- 发送验证码后保留邮箱和验证码输入,不自动刷新,不持久化验证码。
|
||||
- 企业微信入口仍位于次级登录区;企业微信 WebView 下企业微信一键进入仍为主 CTA。
|
||||
- 邮件内容:
|
||||
- 标题清楚标识 OA 登录验证码。
|
||||
- 正文只包含验证码、有效期和非本人操作提醒。
|
||||
- 不写入密码、token、完整敏感 payload。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 普通浏览器登录页可切换密码登录和邮箱验证码登录。
|
||||
- [ ] 邮箱验证码请求成功时显示通用提示,不暴露账号是否存在。
|
||||
- [ ] 正确验证码可完成登录并进入原 redirect 目标。
|
||||
- [ ] 错误、过期或已使用验证码不能登录,并有可理解错误提示。
|
||||
- [ ] 企业微信 WebView 登录层级不回退,企微一键进入仍为主入口。
|
||||
- [ ] 找回密码验证码和登录验证码数据、接口、审计语义隔离。
|
||||
- [ ] 前端不持久化验证码、密码或 token。
|
||||
- [ ] 后端单测、前端 typecheck/build、关键 E2E 和设计规则检查通过。
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- Prisma schema/migration 可验证。
|
||||
- `server` 相关 auth 单测通过。
|
||||
- `client` typecheck 和 build 通过。
|
||||
- Playwright 覆盖密码登录、邮箱验证码登录、找回密码、企业微信入口。
|
||||
- 视觉截图覆盖 PC 登录页和移动/企微登录页。
|
||||
- Trellis validate、governance、`git diff --check` 通过。
|
||||
- 本地通过后再申请部署和公网 smoke;未部署前不告知用户可公网测试。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 不接入企业微信通讯录邮箱 API 或专业版企业邮箱 API。
|
||||
- 不新增短信登录。
|
||||
- 不修改企业微信 OAuth/绑定后端逻辑。
|
||||
- 不修改真实企业微信后台配置。
|
||||
- 不执行真实账号重置、真实业务写入或公网部署。
|
||||
- 不改变密码找回流程。
|
||||
|
||||
## Proposed Implementation
|
||||
|
||||
1. Backend schema:
|
||||
- 新增 `EmailLoginCode` 模型和迁移。
|
||||
- 员工表增加 relation。
|
||||
2. Backend route:
|
||||
- 在 `server/src/routes/auth.js` 新增请求验证码和校验登录接口。
|
||||
- 抽取或复用 session 创建逻辑,确保邮箱验证码登录和密码登录会话行为一致。
|
||||
- 增加审计 action:`AUTH_EMAIL_LOGIN_CODE_REQUESTED`、`AUTH_EMAIL_LOGIN_SUCCESS`、`AUTH_EMAIL_LOGIN_FAILED`。
|
||||
3. Config:
|
||||
- 新增邮箱登录验证码 TTL 和最大尝试次数配置,默认 10 分钟、5 次。
|
||||
4. Frontend API:
|
||||
- 在 `client/src/api/auth.ts` 增加请求验证码和校验登录函数。
|
||||
5. Frontend UI:
|
||||
- `LoginPage` 增加 segmented login mode。
|
||||
- 保持 `AuthPanel`,按 `frontend-design` 做更精致的单面板交互、状态提示和间距。
|
||||
6. Tests:
|
||||
- 后端单测覆盖请求验证码、账号枚举保护、正确验证码登录、错误验证码失败、重复使用失败。
|
||||
- 前端 E2E 覆盖邮箱验证码登录路径和现有登录入口不回退。
|
||||
|
||||
## Test Plan
|
||||
|
||||
```bash
|
||||
cd server && npm run prisma:validate
|
||||
cd server && npm test -- --run tests/unit/routes/auth.login.test.js
|
||||
cd client && npm run typecheck
|
||||
cd client && npm run build
|
||||
E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/login-portal-ux.spec.ts -g "邮箱验证码登录|登录页提供找回密码|找回密码流程" --workers=1 --reporter=list
|
||||
E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/wecom-entry.spec.ts --workers=1 --reporter=list
|
||||
node scripts/qa/check-ui-design-rules.mjs --strict
|
||||
python3 ./.trellis/scripts/task.py validate 05-26-oa-email-code-login
|
||||
node scripts/qa/check-trellis-governance.mjs
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Existing auth route: `server/src/routes/auth.js`
|
||||
- Existing email service: `server/src/services/email/index.js`
|
||||
- Existing password reset model: `server/prisma/schema.prisma`
|
||||
- Existing frontend login: `client/src/pages/LoginPage.tsx`
|
||||
- Existing auth API client: `client/src/api/auth.ts`
|
||||
- Existing auth E2E: `client/tests/e2e/login-portal-ux.spec.ts`
|
||||
- Frontend design skill: `/home/shunli/.codex/skills/frontend-design/SKILL.md`
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"id": "oa-email-code-login",
|
||||
"name": "oa-email-code-login",
|
||||
"title": "邮箱验证码登录",
|
||||
"description": "新增邮箱验证码登录:独立登录验证码模型和接口,复用现有 SMTP 邮件服务、会话签发与审计;前端登录页增加密码登录/邮箱验证码切换,并按 frontend-design 做认证页视觉升级。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "server+client/auth-email-code-login",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-05-26",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"server/prisma/schema.prisma",
|
||||
"server/src/config.js",
|
||||
"server/src/routes/auth.js",
|
||||
"server/tests/unit/routes/auth.login.test.js",
|
||||
"client/src/api/auth.ts",
|
||||
"client/src/api/errorMessages.ts",
|
||||
"client/src/auth/AuthContext.tsx",
|
||||
"client/src/auth/authService.ts",
|
||||
"client/src/components/auth/AuthPanel.tsx",
|
||||
"client/src/layouts/AuthLayout.tsx",
|
||||
"client/src/pages/LoginPage.tsx",
|
||||
"client/tests/e2e/login-portal-ux.spec.ts"
|
||||
],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户