fix: harden authentication system (JWT, cookies, rate limiting, password policy)

- Replace hardcoded JWT secret with randomly generated key persisted to file
- Replace hardcoded default password with random password shown in logs
- Migrate token storage from localStorage to HttpOnly SameSite=strict cookie
- Add IP-based login rate limiter (5 attempts / 15 min, 429 on lockout)
- Add token_version for JWT revocation on password change
- Add password strength validation (min 6 chars, 3+ unique characters)
- Inject decoded user payload into request.state.user in auth middleware
- Add /api/auth/me and /api/auth/logout endpoints
- Narrow auth middleware exception handling (JWTError only, not all Exception)
- Update updated_at timestamp on password change
- Remove localStorage token management from frontend (axios, router, store)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
祀梦
2026-05-17 15:54:45 +08:00
parent 1047bcece9
commit 9d4d869d57
13 changed files with 249 additions and 75 deletions

View File

@@ -1,5 +1,9 @@
# 硬编码配置
import os
import secrets
import logging
_logger = logging.getLogger("app.config")
# api 目录的绝对路径(基于本文件位置计算,不依赖工作目录)
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -28,6 +32,20 @@ DEFAULT_PAGE_SIZE = 20
HOST = "0.0.0.0"
PORT = 23994
# JWT 认证配置
JWT_SECRET = "elysia-todo-secret-key-change-in-production"
# JWT 密钥(首次启动随机生成,持久化到文件)
def _load_jwt_secret() -> str:
secret_file = os.path.join(_BASE_DIR, "data", ".jwt_secret")
if os.path.exists(secret_file):
with open(secret_file) as f:
return f.read().strip()
secret = secrets.token_hex(32)
os.makedirs(os.path.dirname(secret_file), exist_ok=True)
with open(secret_file, "w") as f:
f.write(secret)
_logger.warning("已生成新的 JWT 密钥")
return secret
JWT_SECRET = _load_jwt_secret()
ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24小时