mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-08-07 11:15:00 +00:00
Rewrite the reset-password using better auth internal adapter (#7549)
This commit is contained in:
+20
-36
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user