- 后端:新增 /api/auth/login 与 /api/auth/status;HMAC 签名令牌 30 天有效、 服务重启不失效;设置 SITE_PASSWORD 后中间件拦截除登录/健康检查/CORS 预检 外的所有 /api/*,未启用密码时行为不变(本地开发默认开放) - 前端:AuthGate 登录层 + BookMate 风格全屏登录页(墨绿卡片、轨道环、书标金句); 令牌存 localStorage 随请求带 X-Access-Token 头,401 自动清令牌重新弹出登录页; 侧栏底部新增「退出登录」按钮(仅在启用密码时显示) - 认证通过后才挂载主应用,避免未登录先发起数据请求 Co-Authored-By: Claude Code <noreply@anthropic.com>
32 lines
1.1 KiB
Python
32 lines
1.1 KiB
Python
import hmac
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.config import settings
|
|
from app.core.security import LOGIN_TTL_S, make_token, verify_token
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
password: str
|
|
|
|
|
|
@router.post("/login")
|
|
async def login(req: LoginRequest):
|
|
"""校验站点访问密码,签发登录令牌(30 天有效,服务重启不失效)。"""
|
|
if not settings.site_password:
|
|
raise HTTPException(400, "未启用访问密码")
|
|
if not hmac.compare_digest(req.password or "", settings.site_password):
|
|
raise HTTPException(401, "访问密码错误")
|
|
return {"token": make_token(settings.site_password), "expires_in": LOGIN_TTL_S}
|
|
|
|
|
|
@router.get("/status")
|
|
async def auth_status(request: Request):
|
|
"""探测:是否要求密码 + 当前令牌是否有效(本身不鉴权,供登录层判断)。"""
|
|
required = bool(settings.site_password)
|
|
token = request.headers.get("x-access-token", "")
|
|
ok = required and bool(token) and verify_token(token, settings.site_password)
|
|
return {"required": required, "ok": ok} |