Rewrite the reset-password using better auth internal adapter (#7549)

This commit is contained in:
Ionys
2026-06-28 18:26:06 +02:00
committed by GitHub
parent 2a0a6cb114
commit ee39995e34
2 changed files with 20 additions and 136 deletions
+20 -36
View File
@@ -1,12 +1,11 @@
import { APIError } from "better-auth";
console.log("== Uptime Kuma Reset Password Tool ==");
import { loadEnvFile } from "node:process";
import { auth, BetterAuthUser, getGodKumaHeaders, removeOtherGodKumaUsers } from "../server/better-auth";
import { input, password as passwordInput, select } from "@inquirer/prompts";
import { auth } from "../server/better-auth";
import { password as passwordInput, select } from "@inquirer/prompts";
import { ExitPromptError } from "@inquirer/core";
import { genSecret, isDevEnv } from "../src/util";
import { isDevEnv } from "../src/util";
import { hashPassword } from "better-auth/crypto";
// @ts-ignore
import Database from "../server/database.js";
@@ -14,8 +13,6 @@ import Database from "../server/database.js";
// @ts-ignore No type package is available
import parseArgs from "args-parser";
let kumaGodHeader: Headers;
/**
*
*/
@@ -23,45 +20,42 @@ async function main() {
const args = parseArgs(process.argv);
try {
loadEnvFile();
} catch {}
} catch { }
console.log("Dev Environment:", isDevEnv());
Database.initDataDir(args);
await Database.connect();
kumaGodHeader = await getGodKumaHeaders(false);
const context = await auth().$context;
const internalAdapter = context.internalAdapter;
const result = await auth().api.listUsers({
query: {},
headers: kumaGodHeader,
});
const choices = result.users.map((user) => {
const email = user.email;
const username = (user as BetterAuthUser).username || email;
const users = await internalAdapter.listUsers();
const choices = users.map((user) => {
const userId = user.id;
const username = user.name || user.email;
return {
name: username,
value: email,
value: userId,
description: "",
};
});
let email: string;
let userId: string;
while (true) {
const selectEmail = await select({
const selectedUserId = await select({
message: "Select a username:",
choices,
});
if (!selectEmail) {
if (!selectedUserId) {
console.log("Invalid choice");
continue;
}
email = selectEmail;
userId = selectedUserId;
break;
}
@@ -72,18 +66,11 @@ async function main() {
});
try {
const ok = await auth().api.setUserPassword({
body: {
userId: email,
newPassword: password,
},
headers: kumaGodHeader,
});
const hash = await hashPassword(password); // We do that because the method under doesn't hash the password (it stores it in plain text)
await internalAdapter.updatePassword(userId, hash);
if (ok) {
console.log("Password reset succesfully!");
break;
}
console.log("Password reset succesfully!");
break;
} catch (_) {
// Better Auth shows Cli error message in cli already, we don't need to
}
@@ -96,9 +83,6 @@ async function main() {
*
*/
async function cleanUp() {
if (kumaGodHeader) {
// Better Auth is not allow to remove current user, so leave it as is first...
}
await Database.close();
}
-100
View File
@@ -17,8 +17,6 @@ import { hasUser } from "./routers/better-auth-router";
export type BetterAuthUser = ReturnType<typeof createAuthInstance>["$Infer"]["Session"]["user"];
let authInstance: ReturnType<typeof createAuthInstance>;
let godKumaHeaders: Headers;
let godKumaInitSecret: string = "";
/**
* Get the singleton instance of better-auth
@@ -122,16 +120,6 @@ function createAuthInstance() {
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", {
message: "God Kuma is not allowed to login from the outside.",
});
}
}
// Migrate legacy user from old user table to better-auth
// Only do this when there is no user in better-auth
@@ -290,91 +278,3 @@ export async function migrateUser(username: string, password: string) {
log.info("auth", `No legacy user found for username: ${username}, do not migrate.`);
}
}
/**
* Because there is no way to do admin operations without admin session
* We have to create a god admin user to do operations
* @param removeExistingGodKuma For server restart, remove previous god kuma user. But for short time scripts like reset password, you probably don't want to remove.
* @returns Headers for the temp admin user
*/
export async function getGodKumaHeaders(removeExistingGodKuma = true): Promise<Headers> {
if (!godKumaHeaders) {
const username = "god_kuma_" + genSecret(16);
const password = genSecret();
const email = `${username}@god.uptime-kuma.internal`;
await auth().api.createUser({
body: {
name: "God Kuma (Temp)",
email: email,
password,
role: "admin",
data: {
username,
},
},
});
godKumaInitSecret = genSecret();
// Sign in
const response = await auth().api.signInEmail({
body: {
email,
password,
},
headers: {
"god-kuma-init-secret": godKumaInitSecret,
},
asResponse: true,
});
const header = new Headers();
header.set("cookie", response.headers.get("set-cookie") || "");
header.set("god-kuma-email", email);
godKumaHeaders = header;
if (removeExistingGodKuma) {
await removeOtherGodKumaUsers();
}
}
return godKumaHeaders;
}
/**
* Remove other god kuma users
*/
export async function removeOtherGodKumaUsers() {
let searchValue = "@god.uptime-kuma.internal";
let email = godKumaHeaders.get("god-kuma-email");
if (!email) {
throw new Error("Unexpected error: God Kuma email not found in headers.");
}
const result = await auth().api.listUsers({
query: {
searchField: "email",
searchValue,
searchOperator: "ends_with",
},
headers: godKumaHeaders,
});
for (const user of result.users) {
if (user.email === email) {
continue;
}
log.debug("auth", "Removing existing god kuma user:", user.email);
// Delete the user
await auth().api.removeUser({
body: {
userId: user.id,
},
headers: godKumaHeaders,
});
}
}