refactor(登录): 登录态从 Cookie 换成 access token + refresh token
Deploy / deploy (push) Canceled after 0s

- access token 是 15 分钟的 JWT,走 Authorization: Bearer;refresh token 7 天,每次换都轮换
- 退出登录、改密码、禁用账号仍然立刻生效:每个请求核对 Redis 会话和库里新加的 user.token_version
- 在服务器后台改密码 / 禁用,机房那边登着的也会一起下线(原来两边 Redis 分开,管不到)
- WebSocket 握手借子协议带 token;JWT 密钥存在本站 Redis,部署不用加配置
- 去掉 COOKIE_SECURE;上线后所有人要重新登录一次

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-20 19:22:52 +08:00
1 parent 4be569d978
commit 793848b06f
31 files changed
+4639 -333

No files matched your search

+8 -5
View File
@@ -1,4 +1,4 @@
import type { LoginRequest, RegisterRequest } from "@oj2/contract"
import type { AuthTokens, LoginRequest, RefreshTokenRequest, RegisterRequest } from "@oj2/contract"
import type {
ClassActivity,
MarkAchievementsRead,
@@ -8,20 +8,23 @@ import type {
WebsiteConfig,
} from "@oj2/contract"
import api from "utils/api"
import { getRefreshToken, saveTokens } from "utils/auth"
import { contractDeferred } from "utils/contract"
import { toProblemRow } from "utils/transforms"
import type { PendingAchievement, Profile, Tag } from "utils/types"
export function login(data: { username: string; password: string }) {
return api.post("auth/login", data satisfies LoginRequest)
export async function login(data: { username: string; password: string }) {
saveTokens(await api.post<AuthTokens>("auth/login", data satisfies LoginRequest))
}
export function signup(data: { username: string; email: string; password: string }) {
return api.post("users", data satisfies RegisterRequest)
}
export function logout() {
return api.delete("auth/session")
/** 吊销服务端那张会话。本地的 token 由调用方清(user store 的 clearProfile) */
export async function logout() {
const refreshToken = getRefreshToken()
if (refreshToken) await api.post("auth/logout", { refreshToken } satisfies RefreshTokenRequest)
}
export async function getProfile(username: string = ""): Promise<Profile | null> {