- Split AuthService into AppleAuthService, TokenService, AuthService - Add dev-login endpoint (dev-only, disabled in production) - AppleLoginDto: authorizationCode optional, add userIdentifier/email/fullName/nonce - Login/refresh responses now include user object - logout: single-token revoke + JwtAuthGuard protection - users.repository: switch from in-memory Map to Prisma persistence - JWT payload includes role, guards attach full user info to request - Dual JWT secret support (JWT_ACCESS_SECRET / JWT_REFRESH_SECRET) - Replace jwks-rsa+jsonwebtoken with jose library - Prisma User model: add role field - Independent DTO files with @Transform for empty string safety - Add 5 iOS login flow documentation files
154 lines
4.0 KiB
Markdown
154 lines
4.0 KiB
Markdown
# iOS 登录流程 —— 数据库设计
|
||
|
||
---
|
||
|
||
## 一、users 表
|
||
|
||
用户主表,存储用户基础信息。
|
||
|
||
```prisma
|
||
model User {
|
||
id String @id @default(cuid())
|
||
email String?
|
||
nickname String?
|
||
avatarUrl String?
|
||
role UserRole @default(USER)
|
||
status UserStatus @default(ACTIVE)
|
||
onboardingCompleted Boolean @default(false)
|
||
|
||
authAccounts AuthAccount[]
|
||
refreshTokens RefreshToken[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
enum UserRole {
|
||
USER
|
||
ADMIN
|
||
SUPER_ADMIN
|
||
}
|
||
|
||
enum UserStatus {
|
||
ACTIVE
|
||
DISABLED
|
||
DELETED
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | String (cuid) | 主键 |
|
||
| `email` | String? | 邮箱,可选(Apple 登录首次可能提供) |
|
||
| `nickname` | String? | 昵称 |
|
||
| `avatarUrl` | String? | 头像 URL |
|
||
| `role` | UserRole | 角色,默认 USER |
|
||
| `status` | UserStatus | 状态,默认 ACTIVE |
|
||
| `onboardingCompleted` | Boolean | 是否完成引导,默认 false |
|
||
| `authAccounts` | 关联 | 一对多关联 auth_accounts |
|
||
| `refreshTokens` | 关联 | 一对多关联 refresh_tokens |
|
||
|
||
---
|
||
|
||
## 二、auth_accounts 表
|
||
|
||
记录用户通过什么方式(provider)登录,支持一个用户绑定多个登录方式。
|
||
|
||
```prisma
|
||
model AuthAccount {
|
||
id String @id @default(cuid())
|
||
userId String
|
||
provider AuthProvider
|
||
providerUserId String
|
||
email String?
|
||
|
||
user User @relation(fields: [userId], references: [id])
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([provider, providerUserId])
|
||
@@index([userId])
|
||
}
|
||
|
||
enum AuthProvider {
|
||
DEV
|
||
APPLE
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | String (cuid) | 主键 |
|
||
| `userId` | String | 关联 users 表 |
|
||
| `provider` | AuthProvider | 登录提供商(DEV / APPLE) |
|
||
| `providerUserId` | String | 提供商侧的用户唯一 ID |
|
||
| `email` | String? | 提供商侧邮箱 |
|
||
| `@@unique([provider, providerUserId])` | 约束 | 同一个提供商的用户唯一 |
|
||
|
||
**查找逻辑**:
|
||
|
||
```
|
||
Apple 登录时:
|
||
provider = APPLE
|
||
providerUserId = identityToken 里校验出来的 sub
|
||
→ 如果不存在,创建 User + AuthAccount
|
||
→ 如果存在,直接找到对应 User
|
||
```
|
||
|
||
---
|
||
|
||
## 三、refresh_tokens 表
|
||
|
||
**重要:refreshToken 不要明文存数据库,只存 hash。**
|
||
|
||
```prisma
|
||
model RefreshToken {
|
||
id String @id @default(cuid())
|
||
userId String
|
||
tokenHash String
|
||
expiresAt DateTime
|
||
revokedAt DateTime?
|
||
|
||
user User @relation(fields: [userId], references: [id])
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([userId])
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | String (cuid) | 主键,同时写入 JWT payload 作为 `tokenId` |
|
||
| `userId` | String | 关联 users 表 |
|
||
| `tokenHash` | String | refreshToken 的 hash 值(SHA-256) |
|
||
| `expiresAt` | DateTime | 过期时间 |
|
||
| `revokedAt` | DateTime? | 撤销时间(登出时设置) |
|
||
|
||
**刷新时的校验链**:
|
||
|
||
```
|
||
1. 解析 refreshToken JWT,拿到 userId + tokenId
|
||
2. 查 refresh_tokens 表,找到对应记录
|
||
3. 对比 tokenHash
|
||
4. 确认 revokedAt 为 null(未撤销)
|
||
5. 确认 expiresAt 未过期
|
||
6. 签发新的 accessToken(可选轮换新的 refreshToken)
|
||
```
|
||
|
||
**登出时**:将对应记录的 `revokedAt` 设为当前时间。
|
||
|
||
---
|
||
|
||
## 四、ER 关系总结
|
||
|
||
```
|
||
User (1) ──── (N) AuthAccount 一个用户可有多种登录方式
|
||
User (1) ──── (N) RefreshToken 一个用户可有多个活跃 refreshToken(多设备)
|
||
```
|
||
|
||
- 用户与登录方式是解耦的:用户是一个独立实体,通过 `auth_accounts` 关联到具体的第三方身份。
|
||
- 这种设计天然支持未来扩展更多登录方式(如 Google、微信等),只需在 `AuthProvider` 枚举中添加即可。
|