This commit is contained in:
Louis Lam
2026-01-25 09:01:07 +08:00
parent e5c679332e
commit fcb0af3fd1
10 changed files with 43 additions and 125 deletions

View File

@@ -104,9 +104,10 @@ module.exports = {
"jsdoc/require-returns-type": "off",
"jsdoc/require-param-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-const": "off",
"@typescript-eslint/no-unused-vars": "warn",
"eqeqeq": "off",
eqeqeq: "off",
},
},
],

View File

@@ -4,7 +4,6 @@ const Database = require("../server/database");
const { R } = require("redbean-node");
const readline = require("readline");
const { passwordStrength } = require("check-password-strength");
const { initJWTSecret } = require("../server/util-server");
const User = require("../server/model/user");
const { io } = require("socket.io-client");
const { localWebSocketURL } = require("../server/config");
@@ -62,8 +61,8 @@ const main = async () => {
if (!("dry-run" in args)) {
await User.resetPassword(user.id, password);
// Reset all sessions by reset jwt secret
await initJWTSecret();
// TODO: Reset all sessions by reset jwt secret
// await initJWTSecret();
// Disconnect all other socket clients of the user
await disconnectAllSocketClients(user.username, password);

View File

@@ -1,4 +1,5 @@
import { betterAuth } from "better-auth";
// @ts-ignore
import * as Database from "./database.js";
import { genSecret, log } from "../src/util";
import { R } from "redbean-node";
@@ -64,7 +65,20 @@ export function getAuthSecret() {
}
/**
*
* Get session from cookie
* @param cookie Cookie string
* @returns Session Object
*/
export function getSession(cookie: string) {
const context = {
headers: new Headers(),
};
context.headers.set("cookie", cookie || "");
return auth.api.getSession();
}
/**
* TODO
*/
export async function createUser() {
await auth.api.signUpEmail({
@@ -76,9 +90,10 @@ export async function createUser() {
});
}
// TODO
createUser();
/**
*
* TODO
*/
export async function migrateUser() {}

View File

@@ -31,22 +31,6 @@ class User extends BeanModel {
this.password = hashedPassword;
}
/**
* Create a new JWT for a user
* @param {User} user The User to create a JsonWebToken for
* @param {string} jwtSecret The key used to sign the JsonWebToken
* @returns {string} the JsonWebToken as a string
*/
static createJWT(user, jwtSecret) {
return jwt.sign(
{
username: user.username,
h: shake256(user.password, SHAKE256_LENGTH),
},
jwtSecret
);
}
}
module.exports = User;

View File

@@ -274,6 +274,7 @@ class RealBrowserMonitorType extends MonitorType {
await page.waitForTimeout(monitor.screenshot_delay);
}
// TODO fix without jwtSecret
let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png";
await page.screenshot({

View File

@@ -105,7 +105,6 @@ const {
getSettings,
setSettings,
setting,
initJWTSecret,
checkLogin,
doubleCheckPassword,
shake256,
@@ -227,7 +226,7 @@ let needSetup = false;
}
// Init Better Auth
const { auth } = await import("./better-auth");
const { auth, getSession } = await import("./better-auth");
// Database should be ready now
await server.initAfterDatabaseReady();
@@ -377,59 +376,14 @@ let needSetup = false;
socket.emit("setup");
}
// Auth Session
const session = await getSession(socket.request.headers.cookie);
// ***************************
// Public Socket API
// ***************************
socket.on("loginByToken", async (token, callback) => {
const clientIP = await server.getClientIP(socket);
log.info("auth", `Login by token. IP=${clientIP}`);
try {
let decoded = jwt.verify(token, server.jwtSecret);
log.info("auth", "Username from JWT: " + decoded.username);
let user = await R.findOne("user", " username = ? AND active = 1 ", [decoded.username]);
if (user) {
// Check if the password changed
if (decoded.h !== shake256(user.password, SHAKE256_LENGTH)) {
throw new Error("The token is invalid due to password change or old token");
}
log.debug("auth", "afterLogin");
await afterLogin(socket, user);
log.debug("auth", "afterLogin ok");
log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
callback({
ok: true,
});
} else {
log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${clientIP}`);
callback({
ok: false,
msg: "authUserInactiveOrDeleted",
msgi18n: true,
});
}
} catch (error) {
log.error("auth", `Invalid token. IP=${clientIP}`);
if (error.message) {
log.error("auth", error.message, `IP=${clientIP}`);
}
callback({
ok: false,
msg: "authInvalidToken",
msgi18n: true,
});
}
});
// TODO: better-auth
socket.on("login", async (data, callback) => {
const clientIP = await server.getClientIP(socket);
@@ -1412,6 +1366,7 @@ let needSetup = false;
}
});
// TODO: better-auth
socket.on("changePassword", async (password, callback) => {
try {
checkLogin(socket);
@@ -1838,24 +1793,6 @@ async function initDatabase(testMode = false) {
// Patch the database
await Database.patch(port, hostname);
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", ["jwtSecret"]);
if (!jwtSecretBean) {
log.info("server", "JWT secret is not found, generate one.");
jwtSecretBean = await initJWTSecret();
log.info("server", "Stored JWT secret into database");
} else {
log.debug("server", "Load JWT secret from database.");
}
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
if ((await R.knex("user").count("id as count").first()).count === 0) {
log.info("server", "No user, need setup");
needSetup = true;
}
server.jwtSecret = jwtSecretBean.value;
}
/**

View File

@@ -4,7 +4,7 @@ const fs = require("fs");
const http = require("http");
const { Server } = require("socket.io");
const { R } = require("redbean-node");
const { log, isDev } = require("../src/util");
const { log, isDev, devOriginList } = require("../src/util");
const Database = require("./database");
const util = require("util");
const { Settings } = require("./settings");
@@ -54,12 +54,6 @@ class UptimeKumaServer {
*/
static monitorTypeList = {};
/**
* Use for decode the auth object
* @type {null}
*/
jwtSecret = null;
/**
* Get the current instance of the server if it exists, otherwise
* create a new instance.
@@ -135,12 +129,15 @@ class UptimeKumaServer {
let cors = undefined;
if (isDev) {
cors = {
origin: "*",
origin: devOriginList,
credentials: true,
methods: ["GET", "POST"],
};
}
this.io = new Server(this.httpServer, {
cors,
cookie: true,
allowRequest: async (req, callback) => {
let transport;
// It should be always true, but just in case, because this property is not documented

View File

@@ -34,31 +34,6 @@ const { Kafka, SASLOptions } = require("kafkajs");
const crypto = require("crypto");
const isWindows = process.platform === /^win/.test(process.platform);
/**
* Init or reset JWT secret
* @returns {Promise<Bean>} JWT secret
*/
exports.initJWTSecret = async () => {
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", ["jwtSecret"]);
if (!jwtSecretBean) {
jwtSecretBean = R.dispense("setting");
jwtSecretBean.key = "jwtSecret";
}
jwtSecretBean.value = await passwordHash.generate(genSecret());
await R.store(jwtSecretBean);
return jwtSecretBean;
};
/**
* Decodes a jwt and returns the payload portion without verifying the jwt.
* @param {string} jwt The input jwt as a string
* @returns {object} Decoded jwt payload object
*/
exports.decodeJwt = (jwt) => {
return JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString());
};
/**
* Gets an Access Token from an oidc/oauth2 provider

View File

@@ -117,7 +117,9 @@ export default {
url = undefined;
}
socket = io(url);
socket = io(url, {
withCredentials: true,
});
socket.on("info", (info) => {
this.info = info;

View File

@@ -28,6 +28,13 @@ export const isNode = typeof process !== "undefined" && process?.versions?.node;
*/
const dayjs = isNode ? require("dayjs") : dayjsFrontend;
export const devOriginList = [
"http://127.0.0.1:3000",
"http://127.0.0.1:3001",
"http://localhost:3000",
"http://localhost:3001",
];
export const appName = "Uptime Kuma";
export const DOWN = 0;
export const UP = 1;