Implement migrate user

This commit is contained in:
Louis Lam
2026-06-28 23:23:01 +08:00
parent 183cf21f6f
commit 8d167726ce
2 changed files with 56 additions and 16 deletions
+37 -11
View File
@@ -10,6 +10,9 @@ import { Socket } from "socket.io";
import { haveIBeenPwned } from "better-auth/plugins";
import { twoFactor } from "better-auth/plugins";
import { createAuthMiddleware, APIError } from "better-auth/api";
// @ts-ignore
import * as oldAuth from "./auth.js";
import { hasUser } from "./routers/better-auth-router";
export type BetterAuthUser = ReturnType<typeof createAuthInstance>["$Infer"]["Session"]["user"];
@@ -61,12 +64,6 @@ function createAuthInstance() {
revokeSessionsOnPasswordReset: true,
enabled: true,
disableSignUp: false,
sendResetPassword: async ({ user, url, token }, request) => {
// TODO
},
onPasswordReset: async ({ user }, request) => {
// TODO
},
},
rateLimit: {
// Seconds
@@ -111,10 +108,12 @@ function createAuthInstance() {
hooks: {
before: createAuthMiddleware(async (ctx) => {
// God Kuma is not allowed to login from the outside, only for internal usage
if (ctx.path.startsWith("/sign-in/")) {
const username = ctx.body?.username;
const password = ctx.body?.password;
const email = ctx.body?.email;
// God Kuma is not allowed to login from the outside, only for internal usage
if (username?.startsWith("god_kuma_") || email?.endsWith("@god.uptime-kuma.internal")) {
if (!godKumaInitSecret || ctx.headers?.get("god-kuma-init-secret") != godKumaInitSecret) {
throw new APIError("BAD_REQUEST", {
@@ -122,6 +121,12 @@ function createAuthInstance() {
});
}
}
// Migrate legacy user from old user table to better-auth
// Only do this when there is no user in better-auth
if (!(await hasUser())) {
await migrateUser(username, password);
}
}
}),
},
@@ -247,11 +252,32 @@ export async function doubleCheckPassword(cookie: string, currentPassword: strin
}
/**
* TODO
* Migrate a legacy user from the old `user` table to better-auth tables.
* @param username Legacy username
* @param password Plain-text password from the login form
*/
export async function migrateUser() {
// TODO: User have to input pwd one time to migrate, or we can not get the original password hash to create a better-auth user
// TODO: Disable Auth may need to directly create a user in the database
export async function migrateUser(username: string, password: string) {
const legacyUser = await oldAuth.login(username, password);
if (legacyUser) {
try {
await auth().api.createUser({
body: {
name: username,
email: `${username}@noreply.uptime-kuma.internal`,
password,
role: "admin",
data: {
username,
},
},
});
log.info("auth", `Migrated legacy user: ${username}`);
} catch (e) {
log.error("auth", `Failed to migrate legacy user ${username}:`, e);
}
} else {
log.info("auth", `No legacy user found for username: ${username}, do not migrate.`);
}
}
/**
+19 -5
View File
@@ -9,7 +9,7 @@ import { allowDevOrigin } from "../util-server.js";
import { generalErrorResponse } from "../util2";
let processingSetup = false;
let hasUser = false;
let _hasUser = false;
let expired = false;
const expiredMsg = "Setup has expired. Please restart the server to try again.";
@@ -71,7 +71,7 @@ export async function createBetterAuthRouter() {
log.debug("auth", "First user created:", user);
hasUser = true;
_hasUser = true;
res.json({ ok: true });
} finally {
processingSetup = false;
@@ -85,6 +85,16 @@ export async function createBetterAuthRouter() {
return betterAuthRouter;
}
/**
*
*/
export async function hasUser() {
if (_hasUser) {
return true;
}
return (await R.knex("better_auth_user").count("id as count").first()).count !== 0;
}
/**
* @returns Whether setup is needed.
*/
@@ -95,9 +105,13 @@ export async function needSetup() {
if (processingSetup) {
return false;
}
if (hasUser) {
if (await hasUser()) {
return false;
}
hasUser = (await R.knex("better_auth_user").count("id as count").first()).count !== 0;
return !hasUser;
// The user may be in the old user table, check that as well
const hasOldUser = (await R.knex("user").count("id as count").first()).count !== 0;
return !hasOldUser;
}