# 认证模块审计报告 > 审计日期:2026-06-25 > 审计范围:`src/auth.ts`、`src/proxy.ts`、`src/next-auth.d.ts`、`src/modules/auth/**`、`src/modules/onboarding/**`、`src/app/(auth)/**`、`src/app/(onboarding)/**`、`src/app/api/auth/**`、`src/app/api/onboarding/**`、`src/shared/components/{auth-session-provider,onboarding-gate}.tsx`、`src/shared/lib/{auth-guard,session,permissions,role-utils,bcrypt-utils,http-utils,password-policy,password-security-service,rate-limit,login-logger,audit-logger,change-logger}.ts`、`src/shared/hooks/use-permission.ts`、`src/shared/types/permissions.ts` > 审计依据:`e:\Desktop\CICD\.trae\rules\project_rules.md`、`docs/architecture/004_architecture_impact_map.md` §2.24/§3/§4/§5、`docs/architecture/005_architecture_data.json` `auth`/`usersAndAuth` > 前序报告:`docs/architecture/audit/permissions-audit-report.md`、`docs/architecture/audit/00_summary.md` --- ## 一、现有实现概要 ### 1.1 文件分布 认证模块共 **42** 个文件,按职责分布如下: | 层级 | 文件数 | 主要文件 | |------|--------|----------| | 根目录(应用层) | 3 | `auth.ts`(234)、`proxy.ts`(158)、`next-auth.d.ts`(24) | | 认证 UI 模块 `modules/auth` | 3 | `auth-layout.tsx`(32)、`login-form.tsx`(196)、`register-form.tsx`(259) | | Onboarding 模块 `modules/onboarding` | 5 | `actions.ts`(294)、`data-access.ts`(146)、`schema.ts`(84)、`types.ts`(57)、`onboarding-stepper.tsx`(451) | | (auth) 路由组 | 7 | `layout.tsx`(5)、`login/page.tsx`(16)、`register/page.tsx`(167)、`privacy/page.tsx`(128)、`terms/page.tsx`(130+)、`error.tsx`(23)、`not-found.tsx`(22) | | (onboarding) 路由组 | 1 | `onboarding/page.tsx`(49) | | API 路由 | 3 | `api/auth/[...nextauth]/route.ts`(2)、`api/onboarding/status/route.ts`(19)、`api/onboarding/complete/route.ts`(20) | | shared/lib(认证辅助) | 12 | `auth-guard.ts`(78)、`session.ts`(35)、`permissions.ts`(304)、`role-utils.ts`(50)、`bcrypt-utils.ts`(19)、`http-utils.ts`(44)、`password-policy.ts`(119)、`password-security-service.ts`(94)、`rate-limit.ts`(119)、`login-logger.ts`(42)、`audit-logger.ts`(46)、`change-logger.ts`(43) | | shared/components | 2 | `auth-session-provider.tsx`(12)、`onboarding-gate.tsx`(313,**已废弃未删除**) | | shared/hooks | 1 | `use-permission.ts`(53) | | shared/types | 1 | `permissions.ts`(236) | | i18n | 2 | `auth.json`(31)、`onboarding.json`(78) — **存在但完全未被 login-form / register-form 使用** | | e2e 测试 | 1 | `tests/e2e/smoke-auth.spec.ts`(20) — **与实际 UI 不一致** | ### 1.2 数据流 ``` [匿名访问] proxy.ts → 检查 token → 未登录 → /login ↓ LoginForm → signIn("credentials") ↓ auth.ts authorize → rate-limit → user 查询 → 锁定校验 → bcrypt 比对 → 2FA 校验 → 角色/权限加载 ↓ jwt callback → 写入 token.{id,roles,permissions,onboarded} ↓ proxy.ts → onboarding 检查 → 未完成 → /onboarding ↓ OnboardingStepper → completeOnboardingAction → data-access + classes data-access ↓ users.onboardedAt = now → jwt refresh → 默认路径 [已认证访问] proxy.ts → 检查 ROUTE_PERMISSIONS → 通过 → 页面 requirePermission → getAuthContext → resolveDataScope(rbac resolver)→ data-access ``` ### 1.3 架构图完整性 `docs/architecture/004_architecture_impact_map.md` §2.24(auth 认证 UI 模块)与 §3(根目录 auth.ts/proxy.ts)记录的导出函数、依赖关系与实际代码**大体一致**,但存在以下遗漏: 1. **未记录 `modules/onboarding`**:架构图 §2.24 仅描述 `modules/auth`,但 onboarding 与 auth 强耦合(首次登录引导),未在 auth 章节中关联说明,仅在 §5 `usersAndAuth` 域中提及。 2. **未记录 `auth.ts` 对 `modules/settings` 的反向依赖**:`auth.ts#L106` `await import("@/modules/settings/actions-security")` 调用 `verifyTwoFactorForLogin`,但架构图 §3 描述 auth.ts 仅依赖 `shared/lib/*`,遗漏了跨层依赖。 3. **未记录 `proxy.ts` 的 `resolveDefaultPath` 函数**:导出函数仅记录 `proxy` 主入口,但 `resolveDefaultPath` 被 `onboarding/data-access.ts` 重复实现(`resolveDefaultPathByRoles`),属于逻辑重复未在依赖矩阵中标记。 4. **未记录 `onboarding-gate.tsx`**:架构图 `shared/components` 清单中未列出此组件,但文件仍存在。 5. **未记录 `use-permission.ts` 与 `auth-session-provider.tsx` 在 auth 模块中的归属**:被随意划入 shared,但语义上属于 auth 模块对外暴露的客户端 API。 --- ## 二、现存问题与原因分析 ### 2.1 三层架构违规(P0) #### 问题 1:注册页面 Server Action 内联直接访问数据库 - **位置**:[register/page.tsx](file:///e:/Desktop/CICD/src/app/(auth)/register/page.tsx#L36-L164) - **问题**:`registerAction` 直接内联在页面文件中,通过 `await import("@/shared/db")` 与 `await import("@/shared/db/schema")` 直接 `INSERT users / usersToRoles / roles` 三张表,未通过任何 `modules/*/data-access`。同时文件内重复实现了 `calcAge`、`normalizeBcryptHash` 两个纯函数(前者与 `register-form.tsx#L24-35` 重复,后者与 `shared/lib/bcrypt-utils.ts#L15-19` 重复)。 - **违反规则**: - 项目规则"`app/` 只能调用 `modules/` 的 Server Actions 和 data-access,不直接访问数据库" - 项目规则"模块标准结构要求 `data-access.ts`、`actions.ts` 分层" - 项目规则"Reusable UI components must be extracted to avoid JSX duplication"(此处是逻辑重复) - **后果**:注册逻辑无法被单测、无法被 mock;DB schema 变更需改页面文件;后续添加邮箱验证、审批流、邀请码等扩展需重写;与 `modules/users/data-access.ts` 的 `getUserProfile`/`updateUserProfileById` 等同样操作 `users` 表的函数形成两套并行的写入口,职责割裂。 #### 问题 2:`src/auth.ts` 跨越 modules 边界依赖 settings 模块 - **位置**:[auth.ts](file:///e:/Desktop/CICD/src/auth.ts#L106) `const { verifyTwoFactorForLogin } = await import("@/modules/settings/actions-security")` - **问题**:根目录 `auth.ts`(属于应用层)通过 dynamic import 直接调用 `modules/settings/actions-security.ts` 中的 `verifyTwoFactorForLogin`,且 `actions-security.ts` 是 `"use server"` 文件,内部函数默认被认为是 Server Action,被当作普通函数调用语义混乱。 - **违反规则**: - 项目规则"严格三层架构,依赖方向单向:`app → modules → shared`"——auth.ts 通过 import modules 是合法方向,但 2FA 校验逻辑被嵌入 settings 模块而非 auth 自身或 shared,造成"settings 反向服务于 auth"的语义错乱 - 项目规则"模块间只能通过对方 data-access 通信"——此处直接调用 `actions-security.ts`,未通过 settings data-access - **后果**:settings 模块重构(如拆分 actions-security)会破坏 auth;`verifyTwoFactorForLogin` 被 `auth.ts` 调用导致其内部依赖的 `getTwoFactorEnabled/getTotpSecret/getBackupCodesHashed` 等函数在登录时被加载,bundle 体积无谓增加。 #### 问题 3:onboarding 页面绕过统一认证入口 - **位置**:[onboarding/page.tsx](file:///e:/Desktop/CICD/src/app/(onboarding)/onboarding/page.tsx#L6-L19) `import { auth } from "@/auth"`、`const session = await auth()` - **问题**:项目硬约束明确要求"Authentication must use `getAuthContext()` and `getCurrentStudentUser()`; avoid mixing with other auth methods",但 onboarding 页面绕过 `shared/lib/auth-guard.getAuthContext()`,直接调用 `auth()` 获取 session。同时仅靠 `if (!userId) redirect("/login")` 手动处理未登录态,不享受 `PermissionDeniedError` 统一异常与 DataScope 解析。 - **违反规则**: - 项目硬约束"Authentication must use `getAuthContext()` and `getCurrentStudentUser()`" - 项目 memory"Authentication must use `getAuthContext()` and `getCurrentStudentUser()`; avoid mixing with other auth methods" - **后果**:未登录异常未走 `route-error.tsx` 统一处理;JWT session 与 DB 中 `users.onboardedAt` 不一致时只能靠手动 `redirect`;与同项目中所有其他 dashboard 页面使用 `requireAuth()` 的模式不一致,认知成本增加。 #### 问题 4:废弃的 `OnboardingGate` 仍残留在代码库 - **位置**:[onboarding-gate.tsx](file:///e:/Desktop/CICD/src/shared/components/onboarding-gate.tsx)(313 行) - **问题**:架构图明确记录"P2-1 已修复:用 middleware 强制重定向替代客户端 Dialog",但 `OnboardingGate` 文件仍存在且未被任何代码 import(`grep OnboardingGate` 仅返回定义行)。文件内含 `isAdmin`/`isTeacher`/`isStudent`/`isParent` 用权限点反推角色的硬编码逻辑: ```ts const isAdmin = permissions.includes(Permissions.SETTINGS_ADMIN) const isTeacher = permissions.includes(Permissions.EXAM_CREATE) const isStudent = permissions.includes(Permissions.HOMEWORK_SUBMIT) && !permissions.includes(Permissions.EXAM_CREATE) const isParent = !permissions.includes(Permissions.EXAM_CREATE) && !permissions.includes(Permissions.HOMEWORK_SUBMIT) && permissions.includes(Permissions.EXAM_READ) ``` - **违反规则**: - 项目规则"前端权限判断统一使用 `usePermission().hasPermission()`,严禁 `role === 'xxx'` 硬编码" - 项目规则"避免 backwards-compatibility 残留" - **后果**:误用风险(新人可能 import 它);阅读代码困惑;增加了未使用的 313 行代码体积;权限点反推角色的逻辑本身存在漏洞(如同时拥有 EXAM_CREATE 和 HOMEWORK_SUBMIT 会被判定为 teacher)。 ### 2.2 文件大小与组织(P1) #### 问题 5:`onboarding-stepper.tsx` 451 行已接近 500 行上限 - **位置**:[onboarding-stepper.tsx](file:///e:/Desktop/CICD/src/modules/onboarding/components/onboarding-stepper.tsx) - **问题**:单组件混合 4 步骤 UI 渲染、状态管理、子女行动态增删、URL 参数同步、表单提交、跳过逻辑、权限校验。文件中 `step === 0/1/2/3` 分支硬编码,新增"邮箱验证"、"隐私同意"等步骤需改大组件。 - **违反规则**:项目规则"React 组件 ≤ 500 行(复杂表单可放宽至 800 行)"虽未超出,但已接近上限,且组件未拆分。 - **后果**:单测困难;步骤间复用无法实现;新增步骤需大改。 #### 问题 6:`auth.ts` authorize 回调仍混合 7 类职责 - **位置**:[auth.ts#L34-L141](file:///e:/Desktop/CICD/src/auth.ts#L34-L141) - **问题**:authorize 回调内联:① 速率限制 ② 用户查询 ③ 账户锁定校验 ④ bcrypt 密码比对 ⑤ 失败计数 ⑥ 2FA 调用(跨模块 import settings) ⑦ 角色加载与 `resolvePrimaryRole`。架构图 §3.2.4 标注"后续可选优化:authorize 回调可进一步拆分为 checkRateLimit / checkAccountLockout / verifyPassword / loadUserRoles,使 auth.ts 降至 ≤150 行",但实际未拆分。 - **违反规则**:项目规则"Server Actions / Data Access 模块 ≤ 800 行"虽未超,但 authorize 单函数 107 行违反单一职责。 - **后果**:难以单测;任何修改都可能破坏登录链路;rate-limit 改为 Redis 时需改 auth.ts。 ### 2.3 权限缺失与硬编码(P0/P1) #### 问题 7:注册 Server Action 完全无安全防护 - **位置**:[register/page.tsx#registerAction](file:///e:/Desktop/CICD/src/app/(auth)/register/page.tsx#L36-L164) - **问题**: 1. **无速率限制**:auth.ts authorize 有 `rateLimit(RATE_LIMIT_RULES.LOGIN)`,但 registerAction 完全无限制 2. **无 Zod 校验**:仅 `if (!email)` / `if (password.length < 6)` 字符串判断,未使用项目标准的 `OnboardingSchema` 模式 3. **无邮箱验证流程**:注册即激活,无验证邮件、无管理员审批 4. **无 CAPTCHA**:机器人可批量注册 5. **无审计日志**:注册成功未调用 `logLoginEvent({ action: "signup", status: "success" })`(schema 中 `loginLogActionEnum` 已包含 "signup",但实际未使用) 6. **角色查找静默失败并自动创建**:第 92-94 行 `if (!roleRow) { await db.insert(roles).values({ name: "student" }) }`——任何访问 /register 的匿名用户都可触发 `roles` 表插入,且无 `isSystem: true`、无 `isEnabled: true`,污染 RBAC 配置 7. **密码哈希 cost=10**:低于推荐 cost=12;未做 breached password 检测 8. **错误信息泄露数据库细节**:第 158-162 行直接返回 `创建账户失败:${msg}`(含 sqlMessage) - **违反规则**: - 项目规则"Server Action 必须使用 `requirePermission()` 进行权限校验"(注册虽无登录态,但应有等效防护) - 项目规则"输入使用 Zod 验证,验证失败返回结构化错误" - 项目安全规范"JWT/session ID 存储在 httpOnly + Secure + SameSite=Strict 的 Cookie 中" - 项目规则"禁止 `dangerouslySetInnerHTML`"——此处虽未用,但错误消息直接暴露 SQL 错误违反同源安全原则 - **后果**:批量注册攻击、roles 表污染、SQL 错误信息泄露、注册行为无法审计与监控。 #### 问题 8:`proxy.ts` 的 `resolveDefaultPath` 硬编码角色名 - **位置**:[proxy.ts#L45-L52](file:///e:/Desktop/CICD/src/proxy.ts#L45-L52) ```ts function resolveDefaultPath(roles: string[]): string { if (roles.includes("admin")) return "/admin/dashboard" if (roles.includes("grade_head") || roles.includes("teaching_head")) return "/teacher/dashboard" if (roles.includes("teacher")) return "/teacher/dashboard" if (roles.includes("student")) return "/student/dashboard" if (roles.includes("parent")) return "/parent/dashboard" return "/dashboard" } ``` - **问题**:硬编码 6 个角色名。同时 `onboarding/data-access.ts#resolveDefaultPathByRoles` 重复实现了相同逻辑,两份代码存在同步风险。 - **违反规则**:项目规则"严禁 `role === 'xxx'` 硬编码"(虽然 proxy 是服务端,但应使用权限点或角色配置驱动)。 - **后果**:新增自定义角色(如"教研员"、"访客")时不会自动路由;与 RBAC 模块动态角色支持不一致;两份实现可能漂移。 #### 问题 9:onboarding 完成时角色信息从 JWT 读取但未与 DB 二次校验 - **位置**:[onboarding/actions.ts#L100-L107](file:///e:/Desktop/CICD/src/modules/onboarding/actions.ts#L100-L107) `const roleNames = ctx.roles` - **问题**:`ctx.roles` 来自 JWT,但 JWT 在用户被撤销角色后到下次 refresh 之间存在窗口期。onboarding 仍按旧角色执行 `enrollStudentByInvitationCode` / `enrollTeacherByInvitationCode` / `bindParentToChild`,可能为已被撤销 student 角色的用户绑定班级。 - **违反规则**:项目规则"Server Action 必须调用 `requirePermission()` 进行权限校验"——onboarding Action 仅 `requireAuth()`,未对"执行 student/teacher/parent 角色特定操作"做权限校验。 - **后果**:被撤销角色的用户仍能完成 onboarding 并绑定班级/子女,造成数据污染。 ### 2.4 国际化遗漏(P1) #### 问题 10:`login-form.tsx` 完全未使用 i18n,硬编码英文 - **位置**:[login-form.tsx](file:///e:/Desktop/CICD/src/modules/auth/components/login-form.tsx) - **问题**:硬编码 "Welcome back"、"Enter your email to sign in to your account"、"Email"、"Password"、"Forgot password?"、"Invalid email or password"、"Invalid 2FA code"、"Sign In with Email"、"Verify & Sign In"、"Or continue with"、"GitHub"、"Don't have an account?"、"Sign up"。但 `shared/i18n/messages/{en,zh-CN}/auth.json` 已存在完整翻译键,**完全未被使用**。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键"。 - **后果**:中英文混杂(login-form 全英文,register-form 全中文);多语言切换无效;与项目其他模块均使用 `useTranslations` 的模式不一致。 #### 问题 11:`register-form.tsx` 硬编码中文,未使用 i18n - **位置**:[register-form.tsx](file:///e:/Desktop/CICD/src/modules/auth/components/register-form.tsx) - **问题**:硬编码 "创建账户"、"填写以下信息以创建您的账户"、"姓名"、"邮箱"、"密码"、"出生日期"、"当前年龄:{age} 岁"、"检测到您是未成年人,请填写监护人信息"、"监护人姓名"、"监护人电话"、"监护人与您的关系"、"父亲"、"母亲"、"其他法定监护人"、"我已阅读并同意"、"《隐私政策》"、"《用户协议》"、"我确认已获得监护人同意使用本服务"、"创建账户"、"已有账户?"、"立即登录"。 - **违反规则**:同上。 - **后果**:英文用户无法理解;翻译键已存在但未使用;toast 错误消息也硬编码。 #### 问题 12:onboarding `actions.ts` 与 `data-access.ts` 错误消息硬编码中文 - **位置**: - [onboarding/actions.ts#L94,130,147,159,225](file:///e:/Desktop/CICD/src/modules/onboarding/actions.ts#L94) - [onboarding/data-access.ts#L90-103](file:///e:/Desktop/CICD/src/modules/onboarding/data-access.ts#L90-L103) - **问题**:硬编码 "输入校验失败"、"班级码绑定失败"、"Onboarding 提交失败"、"未找到该邮箱对应的学生"、"学生未设置出生日期,请联系管理员"、"子女生日不匹配"、"子女手机号后 4 位不匹配"。`onboarding.json#toast` 已有 `submitFailed`、`inputInvalid` 等键,但 Server Action 未通过 `getTranslations` 读取。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:英文 locale 下 toast 显示中文;与项目其他模块 Server Action 已迁移至 i18n 的模式不一致。 #### 问题 13:`(auth)` 路由组的 `error.tsx` 与 `not-found.tsx` 静态英文 - **位置**: - [error.tsx](file:///e:/Desktop/CICD/src/app/(auth)/error.tsx) - [not-found.tsx](file:///e:/Desktop/CICD/src/app/(auth)/not-found.tsx) - **问题**:硬编码 "Authentication Error"、"There was a problem signing you in. Please try again."、"Try again"、"Page Not Found"、"The authentication page you are looking for does not exist."、"Return to Login"。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:与 (auth) 路由组下的 login/register(混合中英文)语言不一致。 #### 问题 14:`privacy/page.tsx` 与 `terms/page.tsx` 静态中文 - **位置**: - [privacy/page.tsx](file:///e:/Desktop/CICD/src/app/(auth)/privacy/page.tsx) - [terms/page.tsx](file:///e:/Desktop/CICD/src/app/(auth)/terms/page.tsx) - **问题**:整页隐私政策与用户协议硬编码中文,未提取 i18n 键。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:英文 locale 下展示中文;维护政策文本需改代码而非改 JSON。 #### 问题 15:`AuthLayout` 左侧品牌 testimonial 硬编码英文且与 K12 场景不符 - **位置**:[auth-layout.tsx#L19-L22](file:///e:/Desktop/CICD/src/modules/auth/components/auth-layout.tsx#L19-L22) - **问题**: ```tsx
``` 硬编码英文 testimonial 与虚构人物 "Sofia Davis",与 K12 教育系统定位不符。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:i18n 失效;学校白标部署无法配置 testimonial;与产品定位不符。 ### 2.5 错误处理与边界(P1) #### 问题 16:`LoginForm` 未区分账户锁定、速率限制、2FA 缺失等错误 - **位置**:[login-form.tsx#L62-L69](file:///e:/Desktop/CICD/src/modules/auth/components/login-form.tsx#L62-L69) - **问题**:所有失败统一显示 "Invalid email or password" 或 "Invalid 2FA code"。`signIn("credentials", { redirect: false })` 返回的 `result.error` 是字符串(如 "CredentialsSignin"),代码仅判断 `!result?.error`,未针对不同 error code 给出差异化提示。 - **违反规则**:项目规则"明确处理空数据、无权限、网络异常等边界状态"。 - **后果**:合法用户被锁定后不知如何解锁;攻击者无法感知是否成功;2FA 缺失与密码错误提示混淆。 #### 问题 17:`LoginForm` 的 `preflightTwoFactorAction` 失败时静默降级 - **位置**:[login-form.tsx#L44-L46](file:///e:/Desktop/CICD/src/modules/auth/components/login-form.tsx#L44-L46) ```ts try { const preflight = await preflightTwoFactorAction(email) ... } catch { // 预检失败时静默降级为普通登录 } ``` - **问题**:2FA 预检失败时降级为普通登录,但用户可能已启用 2FA,会被 authorize 拒绝,用户需重复输入密码。 - **违反规则**:项目规则"明确处理空数据、无权限、网络异常等边界状态"。 - **后果**:用户体验差;暴露 2FA 状态(攻击者可通过预检失败推断用户启用 2FA)。 #### 问题 18:`(auth)` 路由组无 `loading.tsx` - **位置**:`src/app/(auth)/`(缺文件) - **问题**:缺少 `loading.tsx`,违反项目硬约束"所有 student 路由必须包含 loading.tsx 和 error.tsx"(虽约束明确为 student 路由,但 (auth) 应同样对待)。 - **违反规则**:项目硬约束。 - **后果**:路由切换时无骨架屏;与 (dashboard) 路由组模式不一致。 #### 问题 19:`LoginForm` 与 `RegisterForm` 无 React Error Boundary 包裹 - **位置**:[login/page.tsx](file:///e:/Desktop/CICD/src/app/(auth)/login/page.tsx)、[register/page.tsx](file:///e:/Desktop/CICD/src/app/(auth)/register/page.tsx) - **问题**:仅 `“This platform has completely transformed how we deliver education to our students. The attention to detail and performance is unmatched.”
{error}
` 未使用 `role="alert"`、未 `aria-describedby` 关联到输入框 2. 密码输入框无显示/隐藏切换 3. 无 Caps Lock 检测提示 4. GitHub 登录按钮无 onClick 是死链 - **违反规则**:项目规则"可访问性(a11y):语义化标签、ARIA 属性、键盘导航"。 - **后果**:屏幕阅读器用户无法感知错误;违反 WCAG 2.2 Target Size 与 Focus Not Obscured。 #### 问题 33:`(auth)` 路由组无 loading.tsx、无骨架屏 - **位置**:`src/app/(auth)/`(缺文件) - **问题**:路由切换无骨架屏;login-form 内部用 `isLoading` state 控制按钮禁用,但无整体加载态。 - **违反规则**:项目规则"异步数据使用 React Suspense + 骨架屏"。 - **后果**:网络慢时用户体验差。 ### 2.11 监控埋点(P1) #### 问题 34:auth 模块未定义任何 `trackEvent` 事件 - **位置**:[track-event.ts#EventName](file:///e:/Desktop/CICD/src/shared/lib/track-event.ts) - **问题**:`EventName` 类型枚举中无 `auth.signin`、`auth.signout`、`auth.signup`、`auth.2fa_enabled`、`auth.password_reset`、`auth.account_locked`、`auth.rate_limited` 等。 - **违反规则**:项目规则"监控:方案中预留关键操作埋点接口"。 - **后果**:登录成功率、2FA 启用率、账户锁定触发率等核心指标无法统计;无法接 Sentry/PostHog。 #### 问题 35:`auth.ts` `events.signIn/signOut` 仅写 login_logs,未触发业务埋点 - **位置**:[auth.ts#L205-L232](file:///e:/Desktop/CICD/src/auth.ts#L205-L232) - **问题**:登录成功仅 `logLoginEvent`,未 `void trackEvent({ name: "auth.signin", ... })`。 - **违反规则**:项目规则"监控:方案中预留关键操作埋点接口"。 - **后果**:监控仪表盘缺失;无法告警异常登录地理/设备。 ### 2.12 可扩展性(P1) #### 问题 36:`proxy.ts` 路由权限配置硬编码,无配置驱动 - **位置**:[proxy.ts#L13-L43](file:///e:/Desktop/CICD/src/proxy.ts#L13-L43) - **问题**:`ROUTE_PERMISSIONS`、`DASHBOARD_ROUTE_PERMISSIONS`、`SPECIFIC_ROUTE_PERMISSIONS`、`API_PERMISSIONS` 4 个常量写死在文件中,新增角色或权限点需改代码。 - **违反规则**:项目规则"可扩展性:采用配置驱动设计"。 - **后果**:与 RBAC 模块的 `DATA_SCOPE_RULES` 配置驱动不一致;新增路由时易遗漏权限配置。 #### 问题 37:onboarding 流程不支持配置驱动 - **位置**:[onboarding-stepper.tsx](file:///e:/Desktop/CICD/src/modules/onboarding/components/onboarding-stepper.tsx) - **问题**:步骤硬编码 4 步(roleConfirm/basicInfo/roleInfo/complete),新增"邮箱验证"、"隐私同意"、"家长扫码绑定子女"等步骤需改组件代码。 - **违反规则**:项目规则"可扩展性:采用配置驱动设计,例如通过角色配置决定该模块渲染哪些 Widget/子模块"。 - **后果**:扩展性差;与项目其他模块(如 dashboard widget-configs.ts)的配置驱动模式不一致。 ### 2.13 数据库与 Schema 一致性(P2) #### 问题 38:`users` 表混合了认证、用户资料、未成年人保护等多类字段 - **位置**:[schema.ts#L27-L57](file:///e:/Desktop/CICD/src/shared/db/schema.ts#L27-L57) - **问题**:`users` 表 31 个字段混合:认证(password, emailVerified, image)、用户资料(name, phone, address, gender, age)、未成年人保护(birthDate, guardianName, guardianPhone, guardianRelation, consentAcceptedAt)、组织(gradeId, departmentId)、onboarding(onboardedAt)。 - **违反规则**:项目规则"模块标准结构要求 schema 按职责拆分"。 - **后果**:schema 文件 1111 行(架构图 P0-1 已记录);认证模块修改 password 字段时影响用户资料模块;难以按表分库分表。 --- ## 三、行业差距对比 ### 3.1 K12 认证主流模式对标 | 维度 | 行业最佳实践 | 本项目现状 | 差距 | |------|------------|----------|------| | 账号创建 | **管理员预分配 + 开放注册关闭**(PowerSchool、Veracross、Clever) | 开放注册,任何人可注册 student 账号 | **重大差距**:未实现邀请码/批量导入预分配;任何人可污染 student 角色池 | | SSO 单点登录 | **Clever SSO(2750 万师生)/ SAML/OIDC / Clever Badges(低年级二维码徽章)** | 仅 Credentials Provider,无 SSO/SAML/OIDC | **重大差距**:未对接 Google Workspace / Microsoft Entra ID / ClassLink;无法满足大型学区统一身份需求 | | 家长-学生绑定 | **PowerSchool Access ID + Access Password 双因子凭据**(校方发放) | 邮箱 + 生日 + 手机后4 三因子(学生已有信息) | **中等差距**:组合空间 3.65M 已接近,但因子均来自学生已有数据,校方未发放独立绑定码;缺少学校发放的 Access Code 表 | | 邮箱验证 | 注册后强制邮箱验证 + 接受条款 | 注册即激活,无邮箱验证 | **重大差距**:违反 PowerSchool/Veracross 标准 | | Breached Password 检测 | Auth0 Breached Passwords 比对已知泄露库 | 仅 bcrypt cost=10 | **重大差距**:未接入 HaveIBeenPwned API 或 k-anonymity 范围查询 | | 2FA 覆盖 | TOTP + 备用码 + 推送 + 短信 + WebAuthn 多因子 | TOTP + 备用码 | **良好**:TOTP+备用码已实现,符合主流;可扩展 WebAuthn | | 未成年人保护 | COPPA/FERPA 声明 + 监护人同意书 | 隐私政策/用户协议页 + 注册时勾选 | **良好**:已实现未成年人监护人字段;但缺少监护人电子签名/同意书记录 | ### 3.2 UI/UX 差距 | 维度 | 行业最佳实践 | 本项目现状 | 差距 | |------|------------|----------|------| | 品牌展示 | PowerSchool District Branding / Auth0 Universal Login 自定义域+模板 | AuthLayout 硬编码 Next_Edu + Sofia Davis testimonial | **差距**:无学校 logo/配色/标语自定义;无 admin 端品牌配置入口 | | 多角色切换 UI | Auth0 Actions 在 Token 中下发多角色,前端 role-switcher 下拉 | 已实现(layout/app-sidebar.tsx 多角色切换) | **达标** | | Onboarding 步骤 | 3-5 步可跳过式 + 进度持久化到 localStorage/Cookie | 4 步可跳过 + URL query 持久化 | **达标**:URL query 比 localStorage 更优 | | 错误反馈 | Auth0 区分"用户不存在/密码错/账户锁定/速率限制/2FA 缺失" | 统一 "Invalid email or password" | **差距**:违反 NIST SP 800-63-4 "明确错误反馈"建议;用户体验差 | | 显示密码按钮 | WCAG 2.2 Target Size ≥24×24 + 可见焦点环 | 无 | **差距**:违反 WCAG 2.2 | | Caps Lock 检测 | 主流浏览器原生支持 + UI 提示 | 无 | **差距** | ### 3.3 安全合规差距 | 维度 | 行业最佳实践 | 本项目现状 | 差距 | |------|------------|----------|------| | 账户锁定 | NIST SP 800-63-4 risk-based throttling,5-10 次失败后临时锁定 15-30 分钟 | 5 次失败后锁定 30 分钟(PASSWORD_RULES) | **达标** | | 速率限制 | 按 IP + 账号双维度,登录端点 ≥10 req/min + 指数退避 | 5 attempts / 15 min(IP+email),单实例内存 | **差距**:未实现 Redis 分布式;阈值偏严(5 次/15min 可能误锁合法用户) | | 密码策略 | NIST 800-63-4 取消强制复杂度与定期轮换,最低 8 位,强制 breached password 检查 | 8 位 + 大小写 + 数字(无特殊字符要求) | **部分达标**:长度符合;未做 breached password 检查;强制复杂度与 NIST 最新建议有偏差 | | 审计日志保留 | CoSN K-12 CVAT + CIS 2025 K-12 报告建议 ≥1 年;FERPA 要求可审计 | login_logs 表存在但无保留期配置 | **差距**:无保留期清理任务(audit 模块已有 retention.ts,但 login_logs 未接入) | | 跨角色越权 | 服务端强制 RBAC/ABAC,禁止仅前端隐藏 | proxy.ts + requirePermission 双重校验 | **达标** | | Sessions 远程登出 | JWT 黑名单或短期 token + refresh token | sessions 表存在但 JWT 策略下未使用 | **差距**:远程登出形同虚设 | ### 3.4 a11y 与 i18n 差距 | 维度 | 行业最佳实践 | 本项目现状 | 差距 | |------|------------|----------|------| | WCAG 2.2 | Focus Not Obscured (AA)、Target Size ≥24×24、Consistent Help (A)、Dragging Movements (AA) | LoginForm 无 ARIA 错误关联、无显示密码按钮、无 Caps Lock 检测 | **差距** | | ARIA | `aria-label`、`aria-invalid`、`aria-describedby`、`role="alert"` | LoginForm 错误 p 标签无 role="alert" | **差距** | | 键盘导航 | 完整 Tab 序、可见焦点环(对比度 ≥3:1) | 基本支持,但未显式验证 | **部分达标** | | i18n | PowerSchool 支持 EN/ES/ZH,URL 持久化 | auth.json/onboarding.json 存在但 LoginForm/RegisterForm 未使用 | **重大差距**:i18n 翻译键已准备但完全未接入 | ### 3.5 可观测性差距 | 维度 | 行业最佳实践 | 本项目现状 | 差距 | |------|------------|----------|------| | 核心指标 | 登录成功率、首次登录失败原因分布、2FA 启用率、密码重置率、锁定触发率、SSO 同步失败率 | login_logs 表记录原始事件,但无聚合仪表盘 | **差距**:无仪表盘;无埋点 | | 异常告警 | 异常登录地理/设备、暴力破解阈值触发、breached password 命中激增;接入 SIEM | 无 | **重大差距** | | 审计日志切片 | 按学区/学校/角色维度切片 | 仅按 userId/userEmail | **差距** | --- ## 四、改进优先级建议 ### P0(紧急,影响安全/架构合规) | # | 问题 | 改进方向 | |---|------|---------| | P0-1 | 注册 Server Action 直接访问 DB(问题 1) | 新建 `modules/auth/data-access.ts` 与 `modules/auth/actions.ts`,将 `registerAction` 迁移至 actions.ts 并通过 data-access 访问 users 表;删除页面内联 `registerAction` 与重复的 `calcAge`/`normalizeBcryptHash` | | P0-2 | `auth.ts` 跨模块依赖 settings(问题 2) | 将 2FA 校验逻辑从 `modules/settings/actions-security.ts` 抽取到 `modules/auth/services/two-factor-service.ts`(纯函数 + 通过 data-access 访问 settings 表),auth.ts 改为 `await import("@/modules/auth/services/two-factor-service")` | | P0-3 | onboarding 页面绕过统一认证入口(问题 3) | 改 `onboarding/page.tsx` 使用 `getAuthContext()` 替代直接 `auth()`,未登录抛 `PermissionDeniedError` 由 `route-error.tsx` 处理 | | P0-4 | 废弃 OnboardingGate 残留(问题 4) | 删除 `src/shared/components/onboarding-gate.tsx` | | P0-5 | 注册接口无安全防护(问题 7) | 在 `modules/auth/actions.ts` 中:① 添加 `registerRateLimit`(独立于 LOGIN);② 用 `RegisterSchema` Zod 校验;③ 注册成功调用 `logLoginEvent({ action: "signup" })`;④ 删除"自动创建 student 角色"逻辑,改为查找失败时返回错误;⑤ 错误消息不暴露 SQL 细节;⑥ 接入 CAPTCHA(hCaptcha 或 Cloudflare Turnstile) | | P0-6 | `auth.ts` 多处 `as` 断言(问题 21) | 用类型守卫替代:`function isAuthUser(u: unknown): u is { id: string; role?: string; ... }`;jwt callback 中 token 类型已声明,无需断言 | | P0-7 | e2e 测试与 UI 不一致(问题 24) | 重写 `tests/e2e/smoke-auth.spec.ts`,使用 i18n 后的翻译键或 `data-testid` 定位;同时为 `rate-limit.ts`/`password-policy.ts`/`role-utils.ts`/`bcrypt-utils.ts` 添加 `.test.ts` 单元测试 | | P0-8 | `proxy.ts` resolveDefaultPath 硬编码角色(问题 8) | 抽取 `shared/lib/route-resolver.ts`,按权限点优先 + 角色兜底解析默认路径;`proxy.ts` 与 `onboarding/data-access.ts` 共用一份实现 | ### P1(重要,影响体验/可维护性) | # | 问题 | 改进方向 | |---|------|---------| | P1-1 | LoginForm/RegisterForm 未使用 i18n(问题 10/11/12/13/14/15) | 接入 `useTranslations("auth")` / `useTranslations("onboarding")`;Server Action 通过 `getTranslations` 读取错误消息;扩展 `auth.json` 增加 `errors.accountLocked`/`errors.rateLimited`/`errors.twoFactorRequired` 等键 | | P1-2 | onboarding-stepper 拆分(问题 5) | 拆为 `step-role-confirm.tsx`/`step-basic-info.tsx`/`step-role-info.tsx`/`step-complete.tsx` 4 个子组件 + `useOnboardingForm` hook 管理状态;主组件仅做步骤编排 | | P1-3 | auth.ts authorize 回调拆分(问题 6) | 拆为 `checkRateLimit`/`checkAccountLockout`/`verifyPassword`/`verifyTwoFactor`/`loadUserRoles` 5 个纯函数 + service,authorize 仅编排 | | P1-4 | LoginForm 错误反馈细化(问题 16/17/32) | 区分 error code(`CredentialsSignin`/`CredentialsSignout`/`OAuthCallbackError`);添加 ARIA `role="alert"` 与 `aria-describedby`;添加显示密码按钮、Caps Lock 检测 | | P1-5 | (auth) 路由组添加 loading.tsx + Error Boundary(问题 18/19/33) | 新建 `app/(auth)/loading.tsx` 骨架屏;用 `shared/components/widget-boundary.tsx` 包裹 LoginForm/RegisterForm | | P1-6 | rate-limit 改为分布式(问题 27) | 抽象 `RateLimiter` 接口,默认内存实现,可注入 Redis 实现;环境变量 `RATE_LIMIT_DRIVER=memory\|redis` 切换 | | P1-7 | JWT 体积优化(问题 28) | 方案 A:JWT 仅存 `userId` + `roles`,permissions 每次请求通过 `getAuthContext()` 从 DB/缓存查询(牺牲少量性能换 token 体积);方案 B:JWT 存 permissions 哈希,DB 中按哈希缓存权限集 | | P1-8 | bindParentToChild 速率限制(问题 29) | 在 `modules/onboarding/actions.ts` 中针对 `children.length` 添加独立速率限制 key `onboarding:bind:{parentId}`,限制每小时 5 次 | | P1-9 | sessions 表清理(问题 30) | 删除 schema.ts 中 `sessions` 表定义 + `revokeAllOtherSessionsAction` 改为基于 JWT 短期 token + refresh token 黑名单;或在 `auth.ts` 中切换为 `strategy: "database"`(不推荐,性能差) | | P1-10 | proxy.ts 细粒度权限(问题 31) | 扩展 `SPECIFIC_ROUTE_PERMISSIONS` 覆盖 `/admin/announcements`/`/admin/audit-logs`/`/admin/users` 等子路径;或改为基于 RBAC 模块的 `ROUTE_PERMISSION_CONFIG` 配置驱动 | | P1-11 | trackEvent 接入 auth 事件(问题 34/35) | 在 `EventName` 中新增 `auth.signin_success`/`auth.signin_failure`/`auth.signout`/`auth.signup`/`auth.2fa_enabled`/`auth.account_locked`/`auth.rate_limited`;`auth.ts` events.signIn/signOut 与 authorize 失败分支调用 `void trackEvent(...)` | | P1-12 | proxy.ts 配置驱动(问题 36) | 抽取 `shared/lib/route-permissions.ts`,导出 `ROUTE_PERMISSIONS` 等常量;proxy.ts 从此文件 import | | P1-13 | onboarding 配置驱动(问题 37) | 定义 `OnboardingStepConfig` 类型 + `ONBOARDING_STEPS` 数组,根据角色过滤步骤;onboarding-stepper 仅渲染配置 | ### P2(中长期,影响可扩展性/合规) | # | 问题 | 改进方向 | |---|------|---------| | P2-1 | users 表字段混合(问题 38) | 长期拆分:`users`(认证核心:id/email/password/emailVerified)+ `user_profiles`(资料:name/phone/address/gender/age)+ `user_minor_protection`(监护人信息)+ `user_onboarding`(onboardedAt);通过 view 或 data-access 层聚合 | | P2-2 | 引入 SSO/SAML/OIDC | 接入 NextAuth 的 `GoogleProvider`/`MicrosoftEntraIDProvider`/`SAMLProvider`;admin 端配置 IdP 元数据 | | P2-3 | 引入邀请码预分配机制 | 新增 `invitation_codes` 表(code/email/role/classId/createdAt/expiresAt/usedBy);admin 批量生成邀请码替代开放注册 | | P2-4 | Breached Password 检测 | 接入 `haveibeenpwned` API(k-anonymity 范围查询);注册/改密时校验 | | P2-5 | 登录审计日志保留期 | 复用 audit 模块的 `retention.ts`,为 `login_logs` 配置 1 年保留期;添加定时任务清理 | | P2-6 | AuthLayout 品牌配置 | admin 端新增"学校品牌"配置(logo/配色/标语/testimonial);AuthLayout 通过 props 注入 | | P2-7 | 监控仪表盘 | 新增 admin/monitoring 页面展示登录成功率/2FA 启用率/锁定触发率;接入 Sentry/PostHog | | P2-8 | WebAuthn 支持 | 接入 `@simplewebauthn/server`;2FA 设置页新增 WebAuthn 选项 | --- ## 五、架构图同步说明 本次审计发现架构图(`004_architecture_impact_map.md` 与 `005_architecture_data.json`)需补充/修改以下节点: ### 5.1 需新增节点 1. **`modules/onboarding`** 作为独立模块章节(§2.X),导出 `getOnboardingStatusAction`/`completeOnboardingAction`/`OnboardingSchema`,依赖 `shared/*` + `modules/classes/data-access` + `modules/rbac/lib/data-scope-resolver`(间接)。 2. **`shared/components/onboarding-gate.tsx`** 加入 shared/components 清单,标记为"已废弃,待删除"。 3. **`shared/lib/route-resolver.ts`**(重构后新增)作为 proxy.ts 与 onboarding/data-access.ts 共用的纯函数。 ### 5.2 需修改节点 1. **§3 根目录 auth.ts**: - 依赖列表新增 `modules/settings/actions-security`(当前遗漏) - 标注"重构后将改为依赖 `modules/auth/services/two-factor-service`" 2. **§2.24 modules/auth**: - 文件清单新增 `data-access.ts`、`actions.ts`、`schema.ts`、`types.ts`、`services/two-factor-service.ts`(重构后) - 删除"纯 UI 模块,无 data-access/actions/types"描述 3. **§3 proxy.ts**: - 导出函数列表新增 `resolveDefaultPath`(当前遗漏) - 依赖列表新增 `shared/lib/route-resolver`(重构后) 4. **`shared/lib` 清单**: - 新增 `route-permissions.ts`、`route-resolver.ts`(重构后) ### 5.3 需删除节点 1. **`shared/components/onboarding-gate.tsx`** 标记为"待删除(P0-4)",重构后从清单移除。 2. **`app/api/onboarding/status/route.ts` 与 `app/api/onboarding/complete/route.ts`** 标记为"已废弃(410 Gone)",重构后可删除。 --- ## 六、重构方案设计 ### 6.1 完全解耦:AuthService 接口抽象与依赖注入 #### 6.1.1 模块边界重新划分 ``` src/modules/auth/ ├─ actions.ts # Server Actions(registerAction / loginRateLimitedAction) ├─ data-access.ts # data-access(createUser / getUserByEmail / 等) ├─ schema.ts # Zod(RegisterSchema / LoginSchema) ├─ types.ts # 类型(AuthService interface / RegisterInput / 等) ├─ services/ │ ├─ two-factor-service.ts # 2FA 校验纯函数(供 auth.ts 调用) │ ├─ password-service.ts # 密码哈希纯函数 + breached password 检测 │ └─ rate-limit-service.ts # 速率限制抽象(memory / redis 双实现) ├─ context/ │ └─ auth-service-provider.tsx # 客户端 Context 注入 AuthService ├─ hooks/ │ ├─ use-login-form.ts # 登录表单状态机 │ └─ use-register-form.ts # 注册表单状态机 ├─ components/ │ ├─ auth-layout.tsx # 布局(接 brand props) │ ├─ login-form.tsx # 登录表单(接 AuthService 注入) │ ├─ register-form.tsx # 注册表单(接 RegisterService 注入) │ ├─ two-factor-input.tsx # 2FA 验证码输入组件(可复用) │ ├─ password-input.tsx # 密码输入 + 显示切换 + Caps Lock 检测(可复用) │ ├─ oauth-buttons.tsx # OAuth 按钮组(GitHub/Google/Microsoft) │ ├─ brand-panel.tsx # 品牌左侧面板(接 brand 配置) │ └─ auth-error-boundary.tsx # 认证错误边界 └─ config/ └─ brand.ts # 默认品牌配置(可被 admin 覆盖) ``` #### 6.1.2 AuthService 接口定义 ```ts // src/modules/auth/types.ts export interface AuthService { /** 登录预检:是否需要 2FA */ preflightTwoFactor(email: string): Promise<{ required: boolean }> /** 执行登录 */ signIn(input: LoginInput): Promise` 动态提示 - 键盘导航:Tab 序、可见焦点环、Enter 提交 - 显示密码按钮 Target Size ≥24×24 - Caps Lock 检测 + `aria-live="polite"` 提示 - 登录按钮加载态 `aria-busy="true"` #### 6.8.2 性能 - LoginForm/RegisterForm 使用 `"use client"`,页面其余部分 Server Component - AuthLayout 品牌 logo 使用 `next/image` 优先加载 - onboarding-stepper 拆分后按步骤 lazy load - proxy.ts 路由权限判断保持在 middleware 层(不进入应用代码) #### 6.8.3 安全性 - 注册接口:速率限制 + Zod + CAPTCHA + 邮箱验证 + 审计日志 - 登录接口:rate-limit + 账户锁定 + breached password 检测 - 2FA:TOTP + 备用码 + WebAuthn(P2) - JWT 体积优化(P1-7) - sessions 表清理或切换策略(P1-9) #### 6.8.4 监控 - 接入 `trackEvent` 埋点(P1-11) - admin/monitoring 页面展示核心指标(P2-7) - Sentry 接入异常告警(P2-7) --- ## 七、实施计划 ### 7.1 第一阶段(P0,立即实施) 1. **新建 `modules/auth/data-access.ts` 与 `actions.ts`** - 迁移 `register/page.tsx` 内联 `registerAction` 到 `modules/auth/actions.ts` - 新增 `createUser`、`getUserByEmail`、`assignRoleToUser` 到 `data-access.ts` - 删除页面内重复的 `calcAge` / `normalizeBcryptHash`,复用 `shared/lib` - 添加 `RegisterSchema` Zod 校验 - 添加注册速率限制(独立 key `register:{ip}`) - 添加 `logLoginEvent({ action: "signup" })` 审计日志 - 删除"自动创建 student 角色"逻辑 - 错误消息不暴露 SQL 细节 2. **抽取 `modules/auth/services/two-factor-service.ts`** - 从 `modules/settings/actions-security.ts` 提取 `verifyTwoFactorForLogin` / `preflightTwoFactorAction` - 通过 `modules/settings/data-access-two-factor.ts` 访问 DB - `auth.ts` 改为 `await import("@/modules/auth/services/two-factor-service")` 3. **修复 onboarding 页面认证入口** - `app/(onboarding)/onboarding/page.tsx` 改用 `getAuthContext()` 替代 `auth()` 4. **删除废弃 OnboardingGate** - 删除 `src/shared/components/onboarding-gate.tsx` 5. **修复 `auth.ts` 中的 `as` 断言** - 添加 `isAuthUser` 类型守卫 - 删除 `as typeof token.permissions` 等断言 6. **抽取 `shared/lib/route-resolver.ts`** - 合并 `proxy.ts#resolveDefaultPath` 与 `onboarding/data-access.ts#resolveDefaultPathByRoles` - 改用权限点优先 + 角色兜底 7. **修复 e2e 测试** - 重写 `tests/e2e/smoke-auth.spec.ts` 使用 i18n key 定位 ### 7.2 第二阶段(P1,本周内) 1. **接入 i18n** - LoginForm / RegisterForm / AuthLayout / error.tsx / not-found.tsx 接入 `useTranslations` - onboarding actions.ts / data-access.ts 接入 `getTranslations` - 扩展 `auth.json` 翻译键 2. **拆分 onboarding-stepper** - 拆为 4 个步骤组件 + `useOnboardingForm` hook 3. **拆分 auth.ts authorize** - 拆为 5 个纯函数 + service 4. **添加 LoginForm 错误反馈细化** - 区分 error code - ARIA 关联 - 显示密码按钮 - Caps Lock 检测 5. **添加 (auth) 路由组 loading.tsx + Error Boundary** 6. **rate-limit 抽象接口** - 支持 memory / redis 双实现 7. **bindParentToChild 速率限制** 8. **接入 trackEvent** - 新增 auth 事件类型 - auth.ts events 调用 trackEvent ### 7.3 第三阶段(P2,中长期) 1. users 表拆分 2. SSO/SAML 接入 3. 邀请码预分配机制 4. Breached Password 检测 5. 登录审计日志保留期清理任务 6. AuthLayout 品牌配置 7. 监控仪表盘 8. WebAuthn 支持 ### 7.4 架构图同步 每完成一个 P0/P1 项,需同步更新 `docs/architecture/004_architecture_impact_map.md` 与 `005_architecture_data.json`: - 新增/删除的导出函数 - 新增/删除的文件 - 修改的依赖关系 - 修改的权限点 --- ## 八、验证清单 完成所有 P0/P1 项后,需通过以下验证: - [ ] `npm run lint` 零错误 - [ ] `npx tsc --noEmit` 零错误 - [ ] `npm run test`(新增的单元测试)全部通过 - [ ] `npm run e2e`(修正后的 smoke-auth)通过 - [ ] 手动验证:注册 → 登录 → onboarding → 角色切换 → 登出 全流程 - [ ] 手动验证:账户锁定、速率限制、2FA 启用/关闭 - [ ] 手动验证:中英文 locale 切换 - [ ] 手动验证:a11y 键盘导航、屏幕阅读器 - [ ] 架构图 004/005 与代码一致 --- ## 九、风险与回滚 | 风险 | 影响 | 缓解措施 | |------|------|---------| | registerAction 迁移可能改变错误消息格式 | 前端 toast 显示异常 | 保持 `ActionState` 结构不变;e2e 覆盖 | | auth.ts 2FA 抽取可能破坏登录链路 | 用户无法登录 | 保留旧 import 路径作为 fallback;分阶段灰度 | | onboarding 改用 getAuthContext 可能改变未登录行为 | 未登录用户访问 /onboarding 行为变化 | 添加 e2e 覆盖未登录场景 | | rate-limit 改为 Redis 可能引入网络延迟 | 登录响应变慢 | 内存实现作为默认;Redis 为可选 | | JWT 体积优化可能改变权限刷新时机 | 权限撤销延迟变化 | 评估方案 A vs B 后再实施 |