From 587e8831746b9cbebfd44c5da9feef2b4e477ac1 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Fri, 22 May 2026 10:25:19 +0800 Subject: [PATCH] docs: add auth system design spec Co-Authored-By: Claude Opus 4.7 --- docs/superpowers/specs/2026-05-22-auth-design.md | 215 +++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-22-auth-design.md diff --git a/docs/superpowers/specs/2026-05-22-auth-design.md b/docs/superpowers/specs/2026-05-22-auth-design.md new file mode 100644 index 0000000..9a9f42d --- /dev/null +++ b/docs/superpowers/specs/2026-05-22-auth-design.md @@ -0,0 +1,215 @@ +# 登录注册系统设计 + +**日期:** 2026/05/22 +**状态:** 已批准,待实现 + +--- + +## 1. 核心架构:Strategy 模式 + +``` +AuthService (统一入口) + ├── PasswordStrategy ← 现在实现 + └── OAuthStrategy ← 接口预留,以后实现 Google/GitHub 等 +``` + +- `AuthService` 持有所有 Strategy 实例,通过 `type` 参数选择使用哪个 Strategy +- `IAuthStrategy` 接口定义 `authenticate()` 和 `generateTokens()` +- 以后加 OAuth 只新增 `GoogleStrategy extends OAuthStrategy`,`AuthService` 无需改动 + +--- + +## 2. 数据表扩展 + +### users 表扩展字段 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `email` | text | unique,加索引 | +| `emailVerified` | integer (bool) | 邮箱是否验证 | +| `passwordHistory` | text (JSON) | 最近 5 个密码 hash,防复用 | +| `failedLoginAttempts` | integer | 连续失败次数 | +| `lockoutUntil` | integer (timestamp) | 锁定截止时间 | +| `lastLoginAt` | integer (timestamp) | 最后登录时间 | +| `lastLoginIp` | text | 最后登录 IP | +| `refreshToken` | text | 当前 refresh token hash(兼容旧字段) | + +### user_sessions 表(新建) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | text | session id (uuid) | +| `userId` | integer | 外键 references users.id | +| `refreshTokenHash` | text | token hash(bcrypt) | +| `userAgent` | text | 浏览器 UA | +| `ip` | text | 登录 IP | +| `createdAt` | integer (timestamp_ms) | 创建时间 | +| `expiresAt` | integer (timestamp_ms) | 过期时间 | +| `revokedAt` | integer (timestamp_ms) | 撤销时间,nullable | + +--- + +## 3. 安全机制 + +### 密码 + +- bcrypt 加密,cost factor 12 +- 注册:密码强度校验(8位以上,大小写+数字+特殊字符) +- 登录失败 5 次锁定 15 分钟 +- 密码历史:最近 5 个密码不允许复用 + +### Token + +- Access Token:JWT,15 分钟有效期,payload 含 userId + sessionId + role +- Refresh Token:UUIDv4,7 天有效期,存储 hash 到 user_sessions +- Refresh Token 放 HTTP Only + Secure + SameSite=Strict Cookie +- Refresh Token Rotation:每次 refresh 颁发新 token,旧 token 作废 + +### 登录安全 + +- 记录 lastLoginAt / lastLoginIp +- 失败次数累计 + 锁定机制 +- 通用限流:同一 IP 1 分钟内最多 5 次登录尝试(所有 auth 路由共享) + +--- + +## 4. API 路由 + +``` +POST /api/auth/register 注册 +POST /api/auth/login 登录 +POST /api/auth/logout 登出 +POST /api/auth/refresh 刷新 token +GET /api/auth/me 获取当前用户 +``` + +### 请求响应格式 + +**POST /api/auth/register** +```json +// Request +{ "email": "user@example.com", "password": "Str0ng!", "username": "john" } + +// Response 201 +{ "user": { "id": 1, "email": "user@example.com", "username": "john" } } +``` + +**POST /api/auth/login** +```json +// Request +{ "email": "user@example.com", "password": "Str0ng!" } + +// Response 200 +{ + "user": { "id": 1, "email": "...", "username": "john", "role": "user" }, + "accessToken": "" +} +// RefreshToken 放 HTTP Only Cookie +``` + +**POST /api/auth/logout** +```json +// Response 200 +{ "success": true } +``` + +**POST /api/auth/refresh** +```json +// Response 200(Cookie 自动发送) +{ "accessToken": "" } +``` + +**GET /api/auth/me** +```json +// Response 200 +{ "user": { "id": 1, "email": "...", "username": "john", "role": "user" } } +``` + +### 错误响应格式 +```json +{ "error": { "code": "INVALID_CREDENTIALS", "message": "邮箱或密码错误" } } +``` + +错误码: +- `INVALID_CREDENTIALS` — 登录失败 +- `ACCOUNT_LOCKED` — 账户锁定 +- `WEAK_PASSWORD` — 密码强度不足 +- `EMAIL_EXISTS` — 邮箱已注册 +- `TOKEN_EXPIRED` — access token 过期 +- `SESSION_REVOKED` — session 已撤销 + +--- + +## 5. 目录结构 + +``` +server/ +├── api/auth/ +│ ├── register.post.ts +│ ├── login.post.ts +│ ├── logout.post.ts +│ ├── refresh.post.ts +│ └── me.get.ts +├── service/auth/ +│ ├── index.ts ← AuthService +│ ├── strategies/ +│ │ ├── index.ts ← StrategyRegistry +│ │ ├── password.strategy.ts +│ │ └── oauth.strategy.ts ← 抽象类预留 +│ └── lib/ +│ ├── jwt.ts +│ ├── password.ts +│ └── rate-limit.ts +├── middleware/ +│ └── auth.ts ← 请求认证中间件 +└── types/ + └── auth.ts +``` + +--- + +## 6. OAuth2 预留接口 + +```typescript +interface IAuthStrategy { + readonly type: 'password' | 'oauth'; + authenticate(credentials: unknown): Promise; + generateTokens(user: User, sessionId: string): TokenPair; + revokeSession(sessionId: string): Promise; +} + +abstract class OAuthStrategy implements IAuthStrategy { + abstract readonly provider: 'google' | 'github' | string; + abstract readonly type: 'oauth'; + + abstract authenticate(oauthToken: string): Promise; + generateTokens(user: User, sessionId: string): TokenPair { /* ... */ } + revokeSession(sessionId: string): Promise { /* ... */ } +} +``` + +以后实现 OAuth 时: +1. 新增 `GoogleStrategy extends OAuthStrategy` +2. 在 `StrategyRegistry` 注册 `google: new GoogleStrategy()` +3. `AuthService` 无需改动 + +--- + +## 7. 实现顺序 + +1. Schema 迁移(users 字段扩展 + user_sessions 表) +2. `PasswordStrategy` 实现(密码加密、校验、历史) +3. `AuthService` + Strategy 模式 +4. API 路由(注册、登录、登出、刷新、获取用户) +5. 中间件 + JWT 验证 +6. 限流 + 锁定机制 +7. 测试覆盖 + +--- + +## 8. 依赖组件(已有) + +- bcryptjs — 密码加密 +- Zod — 输入校验 +- drizzle-orm — 数据库 ORM +- @libsql/client — SQLite 客户端