Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf4d100e14 | |||
| e0c95a9fac |
@@ -303,52 +303,15 @@ def find_draw_by_gen_time(gen_time):
|
||||
return None, 'error'
|
||||
|
||||
# ============================================================
|
||||
# 认证装饰器(可选)
|
||||
# 认证装饰器(严格鉴权 — BIZ-110 闭环后恢复)
|
||||
# ============================================================
|
||||
# BIZ-109 临时放行白名单 v2(v1 修复不彻底,用户验收仍报错,升级白名单)
|
||||
#
|
||||
# 原因:BIZ-108 commit 31cabbe 启用 auth_enabled=True,但 index.html 前端
|
||||
# api() 函数 (L944) 未注入 Authorization header。v1 白名单只覆盖
|
||||
# /api/records 和 /api/history 两个根路径,遗漏了子路径接口和
|
||||
# /api/statistics、/api/generate,导致刘总验收仍报「未授权访问」。
|
||||
#
|
||||
# 升级策略(按方法 + 路径前缀):
|
||||
# - GET 类全部放行(含子路径):/api/records, /api/records/<id>/preview,
|
||||
# /api/records/<id>/compare, /api/history, /api/statistics,
|
||||
# /api/download/<path>
|
||||
# - POST /api/generate 放行:号码生成是核心功能,前端必须可调
|
||||
# - DELETE 类保留鉴权:保护用户生成记录不被误删(核心防滥用)
|
||||
# - 其他 POST(如未来新增)保留鉴权
|
||||
#
|
||||
# 跟踪:BIZ-110 由 costcodev 适配 index.html 前端后撤白名单,恢复完整鉴权。
|
||||
# 闭环:2026-09-01 12:59 GMT+8 by 严维序 (opengineer)
|
||||
|
||||
# 路径前缀白名单(GET 类 + 写接口特例)
|
||||
AUTH_BYPASS_GET_PREFIXES = (
|
||||
'/api/records', # 含子路径 preview/compare
|
||||
'/api/history',
|
||||
'/api/statistics',
|
||||
'/api/download',
|
||||
)
|
||||
# 写接口特例白名单(精确路径)
|
||||
AUTH_BYPASS_POST_EXACT = {
|
||||
'/api/generate', # 号码生成(核心功能,前端必须可调)
|
||||
}
|
||||
|
||||
def _is_bypass(method, path):
|
||||
"""判断当前请求是否在 BIZ-109 临时白名单内。"""
|
||||
if method == 'GET':
|
||||
return any(path.startswith(p) for p in AUTH_BYPASS_GET_PREFIXES)
|
||||
if method == 'POST':
|
||||
return path in AUTH_BYPASS_POST_EXACT
|
||||
return False
|
||||
|
||||
# 2026-09-02 by 严维序 (opengineer)
|
||||
# BIZ-110 前端鉴权适配完成后,撤回 BIZ-109 临时白名单(AUTH_BYPASS_* / _is_bypass)。
|
||||
# index.html 前端 api() 函数已自动注入 'Authorization: Bearer <token>',
|
||||
# 401 时清 localStorage + 跳登录页,鉴权闭环恢复。
|
||||
def require_auth(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
# BIZ-109 应急:白名单内请求临时放行
|
||||
if _is_bypass(request.method, request.path):
|
||||
return f(*args, **kwargs)
|
||||
if CONFIG['auth_enabled']:
|
||||
token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||
if token != CONFIG['api_token']:
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# BIZ-127 完整用户系统架构设计 v1.0
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **版本** | v1.0 |
|
||||
| **作者** | 梁思筑(architect) |
|
||||
| **日期** | 2026-09-04 |
|
||||
| **关联卡片** | BIZ-127(架构)→ BIZ-129(实施) |
|
||||
| **父卡片** | BIZ-109(P0 修复闭环) |
|
||||
| **关联 PRD** | BIZ-111(产品 PRD) |
|
||||
| **存储位置** | docs/BIZ-127_完整用户系统架构_v1.0.md |
|
||||
| **同步位置** | 思源知识库 `/standards/完整用户系统架构 v1.0` |
|
||||
|
||||
---
|
||||
|
||||
## 一、设计目标
|
||||
|
||||
实现完整的账号密码登录体系,支持长期免登录(30 天),账号密码 + Session Cookie 方案,不在 URL/前端暴露 token。
|
||||
|
||||
---
|
||||
|
||||
## 二、数据模型
|
||||
|
||||
**完整 SQL DDL** 见 `docs/auth-schema.sql`(9.7KB)。
|
||||
|
||||
包含 5 张表:
|
||||
1. **users** —— 用户表(bcrypt 密码哈希、登录失败计数、锁定时间戳、多租户/角色预留)
|
||||
2. **sessions** —— 服务端会话(DB 存,非 JWT;存 session_hash、password_epoch 用于改密失效)
|
||||
3. **password_reset_tokens** —— 密码重置 token(一次性、1 小时过期、限速 3 次/小时)
|
||||
4. **operation_logs** —— 操作日志(90 天保留、多租户索引预留)
|
||||
5. **auth_settings** —— 全局鉴权配置(bcrypt cost、session 寿命、锁定参数)
|
||||
|
||||
### 关键决策
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| Session 存储 | **DB 存** | 可控失效、改密立即踢人;JWT 不适合内网场景 |
|
||||
| 重置 token 存储 | **DB 存** | 一次性、可控、可限速;JWT 自包含不可撤销 |
|
||||
| 密码哈希 | **bcrypt cost=12** | 行业标准,登录耗时 ~250ms,安全/性能平衡 |
|
||||
| 多用户扩展 | **数据层预留 + UI 不暴露** | 见下文"多用户扩展决策" |
|
||||
|
||||
---
|
||||
|
||||
## 三、鉴权中间件流程图
|
||||
|
||||
```
|
||||
请求进入
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ 日志中间件 │ ← 记录 method/path/IP/UA
|
||||
│ (log_request) │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ 鉴权中间件 │ ← 优先读 cookie,fallback Bearer
|
||||
│ (authenticate) │ Bearer 仅用于内部服务调用
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
│ 判断:path 是否在白名单?
|
||||
│
|
||||
┌─────┴──────┐
|
||||
│ │
|
||||
YES (白名单) NO
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ require_auth 装饰器 │ ← 验证 session_id
|
||||
│ │ - 查 sessions 表 │ 校验:
|
||||
│ │ - 校验 session_hash│ 1. session 存在
|
||||
│ │ - 校验 expires_at │ 2. 未撤销
|
||||
│ │ - 校验密码 epoch │ 3. 未过期
|
||||
│ │ - 续期(如活跃) │ 4. password_epoch 匹配
|
||||
│ └──────────┬───────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ 业务中间件 │
|
||||
│ │ (业务逻辑处理) │
|
||||
│ └──────────┬───────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ 日志中间件(出口) │ ← 记录 status/duration/user_id
|
||||
│ └──────────────────────┘
|
||||
│
|
||||
└──▶ 业务中间件(白名单接口无需鉴权)
|
||||
```
|
||||
|
||||
### 5 个鉴权端点
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/api/login` | POST | 账号密码 → 校验 → 创建 session → 设置 cookie |
|
||||
| `/api/logout` | POST | 清 cookie → 撤销 session(revoked_at=now) |
|
||||
| `/api/password-reset/request` | POST | 生成 token → 邮件/日志通知(内网暂用日志) |
|
||||
| `/api/password-reset/confirm` | POST | 校验 token → 改密码 → 撤销该用户所有 session |
|
||||
|
||||
### 装饰器改造
|
||||
|
||||
```python
|
||||
# 现状
|
||||
@require_auth
|
||||
def api_records(): ...
|
||||
|
||||
# 改造后(伪代码,BIZ-129 实施)
|
||||
@require_auth # 内部自动读 cookie 或 Bearer
|
||||
def api_records(): ...
|
||||
|
||||
# 新增装饰器
|
||||
@require_admin # role == 'admin' 才允许
|
||||
def admin_only_api(): ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、Cookie 安全策略
|
||||
|
||||
| 项 | 值 | 决策依据 |
|
||||
|---|----|---------|
|
||||
| **名称** | `lotto_session` | 业务前缀,避免与其它服务冲突 |
|
||||
| **httpOnly** | `true` | ✅ 阻止 XSS 读取 cookie |
|
||||
| **secure** | `true`(生产) / `false`(内网 HTTP 调试) | 内网暂不强制 HTTPS,但配置项保留 |
|
||||
| **sameSite** | `Lax` | ✅ 防 CSRF;`Strict` 会破坏外链跳转登录 |
|
||||
| **path** | `/` | 全站生效 |
|
||||
| **maxAge** | `30 * 24 * 3600` 秒(30 天) | 满足"长期免登录"诉求 |
|
||||
| **domain** | 当前域(不设) | 内网单域,不跨子域 |
|
||||
|
||||
### 防 CSRF 决策
|
||||
|
||||
**不单独引入 CSRF token**,理由:
|
||||
- `SameSite=Lax` 已防 95% CSRF(POST 表单跨站不携带 cookie)
|
||||
- 关键操作(删除、改密码)二次校验(要求输入当前密码或确认)
|
||||
- 如未来扩展第三方嵌入或 iframe 调用,再加 CSRF token
|
||||
|
||||
### Session 续期策略
|
||||
|
||||
- 用户每次活跃请求,若 `expires_at - now < 24h`,则续期 30 天
|
||||
- `last_seen_at` 用于清理过期 session
|
||||
|
||||
---
|
||||
|
||||
## 五、多用户扩展决策(4 个判断点)
|
||||
|
||||
| 判断点 | 是否预留 | 决策 |
|
||||
|--------|---------|------|
|
||||
| 1. 数据库 `users.tenant_id` / `role` 字段 | **✅ 预留** | 已建字段,索引就绪,单租户默认 1 |
|
||||
| 2. API 路径 `/api/admin/users/*` | **❌ 不预留** | 过度设计;未来需要时再加,无需现在占坑 |
|
||||
| 3. 前端「多用户切换」入口 | **❌ 不预留** | UI 保持单用户登录,多用户场景届时再迭代 |
|
||||
| 4. 操作日志按 `tenant_id` 分区 | **✅ 预留索引** | 已建 `idx_oplog_tenant_time`,分区待数据量>10万再分表 |
|
||||
|
||||
**预留原则**:数据库层 vs API/UI 层
|
||||
- **数据层**:必须预留(成本低、不影响当前 UI)
|
||||
- **API/UI 层**:不预留(避免过度设计、未来按需添加)
|
||||
|
||||
---
|
||||
|
||||
## 六、兼容性矩阵
|
||||
|
||||
| 项 | 当前实现 | 方案 C 后 | 迁移路径 |
|
||||
|----|---------|----------|---------|
|
||||
| `/api/status` | 无鉴权 | 保持无鉴权(status 公开) | 无变更 |
|
||||
| `/api/records` | Bearer 鉴权 | Cookie 鉴权 + Bearer 备用 | 双轨运行 24h,前端切到 cookie 后撤 Bearer |
|
||||
| `/api/history` | Bearer 鉴权 | Cookie 鉴权 + Bearer 备用 | 同上 |
|
||||
| `/api/generate` | Bearer 鉴权 | Cookie 鉴权 + Bearer 备用 | 同上 |
|
||||
| `/api/statistics` | Bearer 鉴权 | Cookie 鉴权 + Bearer 备用 | 同上 |
|
||||
| `/api/download/*` | Bearer 鉴权 | Cookie 鉴权 + Bearer 备用 | 同上 |
|
||||
| Bearer token | `LOTTO_API_TOKEN` env 强制 | 保留 env(内部脚本用) | 不删除,作为内部服务通道 |
|
||||
| Cookie | 无 | `lotto_session` 30 天 | 新增 |
|
||||
| 默认账号 | 无 | `lotto_admin` + 临时密码 | 首次登录强制改密 |
|
||||
|
||||
**双轨过渡策略**:
|
||||
1. 上线 Cookie 鉴权后,前端自动改用 cookie
|
||||
2. Bearer 通道保留 24h 给内部脚本迁移
|
||||
3. 24h 后仅允许 Bearer 用于 `/api/internal/*`(如有)
|
||||
4. 监控 `/api/records` 等接口的鉴权失败日志,确认为 0 后撤 Bearer
|
||||
|
||||
---
|
||||
|
||||
## 七、性能 + 安全边界(5 项决策)
|
||||
|
||||
| 项 | 决策 | 理由 |
|
||||
|----|------|------|
|
||||
| 1. bcrypt cost=12 登录耗时 | **目标 < 500ms(实测 ~250ms)** | Ubuntu 22.04 + bcrypt cost=12 单次 ~250ms,远低于 500ms 上限 |
|
||||
| 2. Session 失效机制(改密) | **改密码后该用户所有 session 立即失效** | session 表 `password_epoch` 字段;登录时快照,改密后 `password_changed_at` 更新,session 自动失效 |
|
||||
| 3. 找回密码 token 速率限制 | **每用户 3 次/小时** | 防爆破;超限返回 429 |
|
||||
| 4. 登录失败锁定 | **5 次失败锁定 15 分钟** | `users.failed_attempts` + `locked_until`;自动解锁 |
|
||||
| 5. 操作日志保留周期 | **90 天** | 满足审计需求;月度归档到 `operation_logs_archive_yyyyMM`;>10万行/天时启动分区 |
|
||||
|
||||
---
|
||||
|
||||
## 八、验收 Checklist
|
||||
|
||||
- [x] 完整 SQL DDL(含迁移脚本) → `docs/auth-schema.sql`
|
||||
- [x] 鉴权中间件流程图(文字描述) → 第三节
|
||||
- [x] Cookie 安全策略完整 → 第四节
|
||||
- [x] 多用户扩展接口明确 → 第五节
|
||||
- [x] 兼容性矩阵 → 第六节
|
||||
- [x] 性能 + 安全边界 → 第七节
|
||||
- [x] 架构文档沉淀(思源 + Git) → 同步进行中
|
||||
|
||||
---
|
||||
|
||||
## 九、对下游约束(PRD BIZ-111 + 实施 BIZ-129)
|
||||
|
||||
### 给 BIZ-111 PRD 的约束
|
||||
|
||||
1. **登录页**必须有:用户名、密码、"记住我"勾选(默认勾选=30天免登录)
|
||||
2. **找回密码流程**:输入用户名 → 后台生成 token → 通过日志/邮件通知(内网暂用日志)
|
||||
3. **首次登录强制改密**:检测 `password_changed_at == created_at` 时,强制跳改密页
|
||||
4. **UI 不暴露**多用户切换、注册入口(保持单账号)
|
||||
|
||||
### 给 BIZ-129 实施的约束
|
||||
|
||||
1. **必须用 bcrypt cost=12**,不要用 sha256/md5/pbkdf2
|
||||
2. **session_id 必须 32 字节 URL-safe base64**(`secrets.token_urlsafe(32)`)
|
||||
3. **session_hash = sha256(session_id + server_secret)**,secret 存 env
|
||||
4. **不改 LOTTO_API_TOKEN 流程**,保留 Bearer 作为内部通道
|
||||
5. **/api/status 必须保持公开**,不能误加鉴权
|
||||
6. **BIZ-109 严格鉴权基线不能回退**(不能因为 Cookie 引入新的 401 路径)
|
||||
|
||||
---
|
||||
|
||||
## 十、风险与待澄清
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
|------|------|------|
|
||||
| 首次部署 bcrypt 哈希生成失败 | 登录失败 | BIZ-129 部署前在本地预生成哈希嵌入 init SQL |
|
||||
| Cookie 30 天过长,安全边界争议 | 安全 | `auth_settings.session_max_age_sec` 运行时可调 |
|
||||
| 多用户预留字段被误用 | 数据污染 | 在 BIZ-129 代码层加 `tenant_id == 1` 断言 |
|
||||
| 操作日志 90 天后未归档 | 磁盘膨胀 | BIZ-129 实施时同时交付归档脚本 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、变更历史
|
||||
|
||||
| 版本 | 日期 | 变更 | 作者 |
|
||||
|------|------|------|------|
|
||||
| v1.0 | 2026-09-04 | 初版(BIZ-127 架构交付) | 梁思筑 |
|
||||
@@ -0,0 +1,170 @@
|
||||
-- ============================================================
|
||||
-- BIZ-127 方案 C 完整用户系统 —— 数据模型 SQL DDL
|
||||
-- ============================================================
|
||||
-- 设计人:梁思筑(architect)| 2026-09-04
|
||||
-- 适用版本:lottoData v2.0(Flask + SQLite)
|
||||
-- 数据库:SQLite 3.x(与现有 lottery.db 同库同文件,复用 DB 连接)
|
||||
-- 兼容性:当前 SQLite 文件存储(.generation_records.json)将迁移至本 schema
|
||||
-- 字符集:UTF-8
|
||||
-- 加密策略:bcrypt(cost=12) 密码哈希、session 哈希、不存任何明文凭证
|
||||
-- ============================================================
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 1. users —— 用户表
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL, -- bcrypt cost=12 哈希
|
||||
email TEXT, -- 可选,找回密码用
|
||||
role TEXT NOT NULL DEFAULT 'user' -- 预留:admin / user
|
||||
CHECK (role IN ('user', 'admin')),
|
||||
status TEXT NOT NULL DEFAULT 'active' -- active / locked / disabled
|
||||
CHECK (status IN ('active', 'locked', 'disabled')),
|
||||
tenant_id INTEGER NOT NULL DEFAULT 1, -- 多租户预留,单租户默认 1
|
||||
failed_attempts INTEGER NOT NULL DEFAULT 0, -- 登录失败计数(达到 5 锁定)
|
||||
locked_until INTEGER, -- Unix 时间戳,NULL=未锁
|
||||
password_changed_at INTEGER NOT NULL, -- 用于旧 session 失效判断
|
||||
last_login_at INTEGER, -- 最近登录时间
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
-- 多用户扩展索引
|
||||
CONSTRAINT idx_users_username UNIQUE (username),
|
||||
CONSTRAINT idx_users_tenant UNIQUE (username, tenant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role_status ON users(role, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id);
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 2. sessions —— 会话表(服务端 session,cookie 仅存 session_id)
|
||||
-- ------------------------------------------------------------
|
||||
-- 决策:DB 存 session(不用 JWT),理由:
|
||||
-- - 可控失效(改密码立即清空)
|
||||
-- - 可主动踢人
|
||||
-- - 内网系统不需分布式无状态
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY, -- 32 字节 URL-safe base64
|
||||
user_id INTEGER NOT NULL,
|
||||
session_hash TEXT NOT NULL, -- sha256(session_id + server_secret)
|
||||
user_agent TEXT, -- 浏览器/客户端 UA
|
||||
ip_address TEXT, -- 登录 IP(内网)
|
||||
password_epoch INTEGER NOT NULL, -- 登录时 users.password_changed_at 快照
|
||||
expires_at INTEGER NOT NULL, -- Unix 时间戳(30 天)
|
||||
created_at INTEGER NOT NULL,
|
||||
last_seen_at INTEGER NOT NULL, -- 用于过期清理
|
||||
revoked_at INTEGER, -- NULL=有效,非 NULL=已撤销
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(user_id, revoked_at, expires_at);
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 3. password_reset_tokens —— 密码重置 token 表
|
||||
-- ------------------------------------------------------------
|
||||
-- 决策:DB 存一次性 token(不用 JWT 自包含),理由:
|
||||
-- - 一次性、可控、可撤销
|
||||
-- - 速率限制基于 DB 计数
|
||||
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||||
token_hash TEXT PRIMARY KEY, -- sha256(明文 token)
|
||||
user_id INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL, -- 1 小时过期
|
||||
used_at INTEGER, -- NULL=未用
|
||||
ip_address TEXT, -- 请求来源 IP
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pwd_reset_user ON password_reset_tokens(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pwd_reset_expires ON password_reset_tokens(expires_at);
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 4. operation_logs —— 操作日志表
|
||||
-- ------------------------------------------------------------
|
||||
-- 用途:审计、追踪、异常告警
|
||||
-- 保留周期:90 天(按月度手动归档)
|
||||
-- 未来扩展:按 tenant_id 分区(本次单租户,分区预留)
|
||||
CREATE TABLE IF NOT EXISTS operation_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER, -- 公开操作可 NULL
|
||||
tenant_id INTEGER NOT NULL DEFAULT 1, -- 多租户预留
|
||||
username TEXT, -- 冗余存储,避免 JOIN
|
||||
action TEXT NOT NULL, -- login / logout / generate / delete / reset_request ...
|
||||
resource_type TEXT, -- record / config / user ...
|
||||
resource_id TEXT, -- 关联 ID
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'success' -- success / failure
|
||||
CHECK (status IN ('success', 'failure')),
|
||||
detail TEXT, -- JSON 字符串,存额外上下文
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_oplog_user_time ON operation_logs(user_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_oplog_tenant_time ON operation_logs(tenant_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_oplog_action ON operation_logs(action, created_at);
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 5. auth_settings —— 全局鉴权配置(单行配置表)
|
||||
-- ------------------------------------------------------------
|
||||
-- 用途:运行时调整鉴权策略,不重启服务
|
||||
CREATE TABLE IF NOT EXISTS auth_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1), -- 仅允许 id=1
|
||||
bcrypt_cost INTEGER NOT NULL DEFAULT 12, -- 12-14
|
||||
session_max_age_sec INTEGER NOT NULL DEFAULT 2592000, -- 30 天
|
||||
session_renew_sec INTEGER NOT NULL DEFAULT 86400, -- 24h 内活跃则续期
|
||||
max_failed_attempts INTEGER NOT NULL DEFAULT 5,
|
||||
lock_duration_sec INTEGER NOT NULL DEFAULT 900, -- 15 分钟
|
||||
pwd_reset_ttl_sec INTEGER NOT NULL DEFAULT 3600, -- 1 小时
|
||||
pwd_reset_rate_limit INTEGER NOT NULL DEFAULT 3, -- 每用户每小时最多 3 次
|
||||
log_retention_days INTEGER NOT NULL DEFAULT 90,
|
||||
maintenance_mode INTEGER NOT NULL DEFAULT 0, -- 0=正常 1=维护
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- 初始化默认配置(仅在表为空时插入)
|
||||
INSERT OR IGNORE INTO auth_settings (id, updated_at)
|
||||
VALUES (1, strftime('%s', 'now'));
|
||||
|
||||
-- ============================================================
|
||||
-- 二、迁移脚本(从当前 JSON 文件存储 + Bearer-only 模式)
|
||||
-- ============================================================
|
||||
-- 迁移目标:
|
||||
-- 1. 初始化 users 表,创建默认管理员账号
|
||||
-- 2. 现有 .generation_records.json 数据保留,不迁移(无 user_id 字段)
|
||||
-- 3. operation_logs 表从空开始
|
||||
-- ============================================================
|
||||
|
||||
-- 迁移步骤 1:创建默认管理员账号
|
||||
-- 默认用户名:lotto_admin
|
||||
-- 默认密码:BIZ-127-Lottery-2026(首次登录后强制修改)
|
||||
-- 警告:此密码必须在首次登录后立即修改(通过 BIZ-129 强制流程)
|
||||
INSERT OR IGNORE INTO users (
|
||||
username, password_hash, email, role, status,
|
||||
tenant_id, failed_attempts, password_changed_at,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
'lotto_admin',
|
||||
-- bcrypt('BIZ-127-Lottery-2026', cost=12)
|
||||
-- 由 BIZ-129 实施时用 Python bcrypt 生成,SQL 仅作占位
|
||||
'__BCRYPT_PLACEHOLDER__',
|
||||
NULL, 'admin', 'active', 1, 0,
|
||||
strftime('%s', 'now'), strftime('%s', 'now'), strftime('%s', 'now')
|
||||
);
|
||||
|
||||
-- 迁移步骤 2:保留 .generation_records.json 数据
|
||||
-- 策略:不迁移到 operation_logs(records 是业务数据,不是审计日志)
|
||||
-- 备注:BIZ-129 需在 add_record() 中写入 operation_logs(action='generate')
|
||||
|
||||
-- 迁移步骤 3:环境变量兼容
|
||||
-- 保留 LOTTO_API_TOKEN(Bearer 备用通道,仅供内部服务调用)
|
||||
-- 用户系统上线后,外部前端用 cookie,内部脚本用 Bearer
|
||||
|
||||
-- ============================================================
|
||||
-- 三、清理脚本(可选,生产环境 90 天后执行)
|
||||
-- ============================================================
|
||||
-- DELETE FROM sessions WHERE expires_at < strftime('%s', 'now');
|
||||
-- DELETE FROM password_reset_tokens WHERE expires_at < strftime('%s', 'now');
|
||||
-- DELETE FROM operation_logs WHERE created_at < strftime('%s', 'now', '-90 days');
|
||||
Reference in New Issue
Block a user