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

+12 -4
View File
@@ -1,7 +1,9 @@
import { Hono } from "hono"
import { basename, resolve } from "node:path"
import { getRequestSessionUser, readRequestSessionToken } from "./auth/session"
import { WS_AUTH_PROTOCOL } from "@oj2/contract"
import { authenticateWebSocket, loadJwtSecret } from "./auth/session"
import { config } from "./config"
import { adminRoutes } from "./routes/admin"
import { authRoutes } from "./routes/auth"
@@ -88,6 +90,8 @@ async function serveUpload(pathname: string, prefix: string, directory: string)
return null
}
await loadJwtSecret()
const server = Bun.serve<SubmissionSocketData>({
port: config.port,
// Bun 默认 10 秒没有字节往来就掐连接,而且不报任何错。AI 的 SSE 两条路都会静默
@@ -126,8 +130,9 @@ const server = Bun.serve<SubmissionSocketData>({
if (!isAllowedWebSocketOrigin(request.headers.get("origin"), url)) {
return new Response("Forbidden", { status: 403 })
}
const user = await getRequestSessionUser(request)
if (!user) return new Response("Unauthorized", { status: 401 })
const session = await authenticateWebSocket(request)
if (!session) return new Response("Unauthorized", { status: 401 })
const { user } = session
const kind =
url.pathname === "/ws/config"
? "config"
@@ -136,10 +141,13 @@ const server = Bun.serve<SubmissionSocketData>({
: "submissions"
if (
bunServer.upgrade(request, {
// 前端是借子协议带 token 来的,不回一个它传过的子协议 Chrome 就判握手失败
headers: { "Sec-WebSocket-Protocol": WS_AUTH_PROTOCOL },
data: {
userId: user.id,
kind,
token: readRequestSessionToken(request),
sid: session.sid,
tokenVersion: session.tokenVersion,
username: user.username,
adminType: user.adminType,
},