fix: skip HIBP check when migrating a v2 user (#7947)

This commit is contained in:
Louis Lam authored and GitHub committed 2026-10-09 11:16:20 +08:00
1 parent 0d9728993d
commit 4e7c4ae382
3 files changed
+41 -3

No files matched your search

+2
View File
@@ -120,6 +120,8 @@ npm run dev # Frontend on port 3000, backend on port 3001
- 4 spaces indentation, double quotes, Unix line endings (LF), semicolons required
- Prettier enforces `printWidth: 120`; run `npm run fmt` to format (also run automatically in CI)
- **Comments**: keep them short (one concise line explaining what/why); no multi-line essays or restating the code
- **Commits**: one short sentence describing the change; no long body
- **Naming**: JavaScript/TypeScript (camelCase), SQLite (snake_case), CSS/SCSS (kebab-case)
- JSDoc is required on every function declaration and method (`jsdoc/require-jsdoc`); `*.ts` relaxes some JSDoc and `any` rules
- Notable ESLint rules: `no-var`, `one-var: never`, `max-statements-per-line: 1`, `jsdoc/require-throws`
+28 -1
View File
@@ -14,11 +14,28 @@ import { createAuthMiddleware } from "better-auth/api";
import * as oldAuth from "./auth.js";
import { hasUser } from "./routers/better-auth-router";
import { symmetricEncrypt } from "better-auth/crypto";
// @ts-ignore
import config from "./config.js";
export type BetterAuthUser = ReturnType<typeof createAuthInstance>["$Infer"]["Session"]["user"];
let authInstance: ReturnType<typeof createAuthInstance>;
/**
* Whether the HIBP password check is enabled (`UPTIME_KUMA_DISABLE_LEAK_PWD_CHECK` / `--disable-leak-pwd-check`).
* @returns True to run the check
*/
function isHaveIBeenPwnedEnabled() {
const disableHibp = process.env.UPTIME_KUMA_DISABLE_LEAK_PWD_CHECK;
const disabledByEnv = !!disableHibp && disableHibp !== "false" && disableHibp !== "0";
return !disabledByEnv && !config.args["disable-leak-pwd-check"];
}
// Shared haveIBeenPwned plugin options; `enabled` is turned off during legacy user migration (#7946).
const haveIBeenPwnedOptions = {
enabled: true,
};
/**
* Get the singleton instance of better-auth
* Mainly used for http and socket.io authentication
@@ -56,6 +73,12 @@ function createAuthInstance() {
);
}
// Apply the UPTIME_KUMA_DISABLE_LEAK_PWD_CHECK / --disable-leak-pwd-check setting
haveIBeenPwnedOptions.enabled = isHaveIBeenPwnedEnabled();
if (!haveIBeenPwnedOptions.enabled) {
log.warn("auth", "haveIBeenPwned password check is disabled (UPTIME_KUMA_DISABLE_LEAK_PWD_CHECK).");
}
const authDatabase = Database.createAuthDatabase(Database.dbConfig);
return betterAuth({
@@ -108,7 +131,7 @@ function createAuthInstance() {
admin(),
// Check if the password has been pwned in data breaches
haveIBeenPwned(),
haveIBeenPwned(haveIBeenPwnedOptions),
twoFactor({
schema: {
@@ -299,6 +322,8 @@ export async function doubleCheckPassword(cookie: string, currentPassword: strin
export async function migrateUser(username: string, password: string) {
const legacyUser = await oldAuth.login(username, password);
if (legacyUser) {
// Skip the HIBP check: the legacy user's existing password must not be rejected (#7946)
haveIBeenPwnedOptions.enabled = false;
try {
// Create BetterAuth user with the same username and password
const newUser = await auth().api.createUser({
@@ -338,6 +363,8 @@ export async function migrateUser(username: string, password: string) {
log.info("auth", `Migrated legacy user: ${username}`);
} catch (e) {
log.error("auth", `Failed to migrate legacy user ${username}:`, e);
} finally {
haveIBeenPwnedOptions.enabled = isHaveIBeenPwnedEnabled();
}
} else {
log.info("auth", `No legacy user found for username: ${username}, do not migrate.`);
+11 -2
View File
@@ -7,6 +7,8 @@ import zlib from "node:zlib";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { io } from "socket.io-client";
import BetterSqlite3 from "better-sqlite3";
import bcrypt from "bcryptjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -14,7 +16,9 @@ const fixturePath = path.join(__dirname, "../fixtures/v2-kuma.db.gz");
const dataDir = path.join(__dirname, "../../data/test-migration-v2-upgrade");
const username = "admin";
const password = "Kuma-V2-Test-Password";
// A password that Have I Been Pwned reports as breached, to cover the migration path from #7946.
const password = "Admin123!x";
/**
* Get a free TCP port from the OS.
@@ -76,7 +80,7 @@ async function callOk(socket, event, ...args) {
return result;
}
test("A v2.5.3 database still works after upgrading to v3 (#7944)", { timeout: 180000 }, async () => {
test("A v2.5.3 database still works after upgrading to v3 (#7944, #7946)", { timeout: 180000 }, async () => {
assert.ok(fs.existsSync(fixturePath), `v2 fixture not found: ${fixturePath}`);
// Start from a real 2.5.3 database (see extra/prepare-v2-test-db.mjs) and let the server
@@ -85,6 +89,11 @@ test("A v2.5.3 database still works after upgrading to v3 (#7944)", { timeout: 1
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(path.join(dataDir, "kuma.db"), zlib.gunzipSync(fs.readFileSync(fixturePath)));
// Seed the admin with a breached password, so migrating it must skip the leak check (#7946).
const seedDb = new BetterSqlite3(path.join(dataDir, "kuma.db"));
seedDb.prepare("UPDATE user SET password = ? WHERE username = ?").run(await bcrypt.hash(password, 10), username);
seedDb.close();
let child;
let socket;