diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts index 9751853..7130ae4 100644 --- a/apps/api/src/config.ts +++ b/apps/api/src/config.ts @@ -66,6 +66,16 @@ export const config = { sessionCookie: "oj2_session", sessionTtlSeconds: Number(process.env.SESSION_TTL_SECONDS ?? 7 * 24 * 60 * 60), secureCookies: process.env.COOKIE_SECURE === "true", + // 登录成功时把 Django 的 pbkdf2 哈希升级成 argon2id 写回库。 + // + // **默认关闭,这是一道单向门。** Django 存 argon2 的格式是 + // `argon2$argon2id$v=19$…`,而 Bun 写出来的是 `$argon2id$v=19$…`(少了算法标签), + // 旧后端按 `$` 切第一段拿到空串、认不出这个哈希 —— 而且旧后端连 argon2-cffi + // 都没装,格式对了也验不了。**只要在新站登录过一次,这个账号就回不去旧站。** + // + // 所以并行试跑期间必须关着,一次性切换后的回滚窗口内也该关着。 + // 等确定不会再回滚了,再设 PASSWORD_HASH_UPGRADE=true。 + passwordHashUpgrade: process.env.PASSWORD_HASH_UPGRADE === "true", judgeServerUrl: process.env.JUDGE_SERVER_URL ?? "http://localhost:8081", judgeServerToken: judgeServerToken(), judgeConcurrency: Number(process.env.JUDGE_CONCURRENCY ?? 2), diff --git a/apps/api/src/routes/auth.ts b/apps/api/src/routes/auth.ts index 4b6fefe..2b3477c 100644 --- a/apps/api/src/routes/auth.ts +++ b/apps/api/src/routes/auth.ts @@ -7,6 +7,7 @@ import { and, eq, sql } from "drizzle-orm" import { Hono } from "hono" import { optionalAuth, type AppEnv } from "../auth/middleware" +import { config } from "../config" import { createSession, destroySession } from "../auth/session" import { verifyPassword } from "../auth/password" import { db, schema } from "../db" @@ -43,7 +44,9 @@ authRoutes.post("/auth/login", async (c) => { const now = new Date().toISOString() const update: { lastLogin: string; password?: string } = { lastLogin: now } - if (password.needsUpgrade) { + // 见 config.passwordHashUpgrade 的注释:升级成 argon2 之后旧后端就验不了这个账号了, + // 是一道单向门。默认关闭,回滚窗口内不要打开。 + if (password.needsUpgrade && config.passwordHashUpgrade) { update.password = await Bun.password.hash(parsed.data.password, { algorithm: "argon2id", }) diff --git a/docker/compose.debian.yml b/docker/compose.debian.yml index 88f7f82..790af3e 100644 --- a/docker/compose.debian.yml +++ b/docker/compose.debian.yml @@ -131,6 +131,10 @@ services: JUDGE_SERVER_TOKEN: ${OJ2_JUDGE_TOKEN:?} JUDGE_CONCURRENCY: ${JUDGE_CONCURRENCY:-2} AI_KEY: ${AI_KEY:-} + # 登录时把 Django 的 pbkdf2 哈希升级成 argon2 写回库。**默认关闭**: + # 升级过的账号旧后端再也验不了,是一道单向门,回滚窗口内打开会锁死学生。 + # 详见 apps/api/src/config.ts 里 passwordHashUpgrade 的注释。 + PASSWORD_HASH_UPGRADE: ${PASSWORD_HASH_UPGRADE:-} # 走 NPM 终止 TLS,浏览器侧是 https,Cookie 必须带 Secure COOKIE_SECURE: "true" healthcheck: diff --git a/docker/compose.school.yml b/docker/compose.school.yml index ff0ac35..fe448b7 100644 --- a/docker/compose.school.yml +++ b/docker/compose.school.yml @@ -77,6 +77,7 @@ services: AI_KEY: ${AI_KEY:-} # 机房走 http 直连 IP,没有 TLS。带 Secure 的 Cookie 浏览器不会回传, # 学生会「登录成功但立刻又是未登录」。这里必须是 false。 + PASSWORD_HASH_UPGRADE: ${PASSWORD_HASH_UPGRADE:-} COOKIE_SECURE: ${COOKIE_SECURE:-false} healthcheck: test: ["CMD", "oj2-api", "healthcheck"]