fix(saas): 3 项 P0 安全/功能修复 + TRUTH.md 数字校准
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

P0-01: Admin ApiKeys 创建功能前后端不匹配
- 前端 service 从 /keys 改回 /tokens(api_tokens 表)
- 前端 UI 字段 {name, expires_days, permissions} 与旧路由匹配

P0-02: 账户锁定检查错误处理
- unwrap_or(false) 改为 map_err + SaasError 传播
- SQL 查询失败时返回错误而非静默跳过锁定检查

P0-03: Logout refresh token 撤销增强
- 新增 access token cookie fallback 提取 account_id
- Tauri 桌面端 Bearer auth 场景下也能撤销 refresh token

TRUTH.md 校准: Tauri 183→190, invoke 95→104, .route() 136→137, 中间件 15→14
This commit is contained in:
iven
2026-04-16 22:22:12 +08:00
parent a0d1392371
commit 0d79993691
4 changed files with 45 additions and 12 deletions

View File

@@ -215,7 +215,10 @@ pub async fn login(
.bind(&r.id)
.fetch_one(&state.db)
.await
.unwrap_or(false);
.map_err(|e| {
tracing::warn!(account_id = %r.id, error = %e, "Lockout check query failed");
SaasError::Internal("账号状态检查失败,请重试".into())
})?;
if is_locked {
return Err(SaasError::AuthError("账号已被临时锁定,请稍后再试".into()));
@@ -631,5 +634,32 @@ pub async fn logout(
}
}
// Fallback: 如果没有找到 refresh token尝试从 access token cookie 提取 account_id
// Tauri 桌面端使用 Bearer auth 时logout body 可能不含 refresh_token
if tokens_to_check.is_empty() {
if let Some(access_cookie) = jar.get(ACCESS_TOKEN_COOKIE) {
let access_val = access_cookie.value().to_string();
if let Ok(claims) = verify_token_skip_expiry(&access_val, jwt_secret) {
let now = chrono::Utc::now();
let result = sqlx::query(
"UPDATE refresh_tokens SET used_at = $1 WHERE account_id = $2 AND used_at IS NULL"
)
.bind(&now)
.bind(&claims.sub)
.execute(&state.db)
.await;
match result {
Ok(r) => {
tracing::info!(account_id = %claims.sub, n = r.rows_affected(), "Refresh tokens revoked via access token fallback");
}
Err(e) => {
tracing::warn!(account_id = %claims.sub, error = %e, "Failed to revoke refresh tokens (access fallback)");
}
}
}
}
}
(clear_auth_cookies(jar), axum::http::StatusCode::NO_CONTENT)
}