mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-02 03:26:31 -04:00
Merge branch 'master' into cloudflared
This commit is contained in:
commit
a6b52b7ba6
11 changed files with 264 additions and 82 deletions
|
@ -12,6 +12,10 @@ const { loginRateLimiter } = require("./rate-limiter");
|
|||
* @returns {Promise<Bean|null>}
|
||||
*/
|
||||
exports.login = async function (username, password) {
|
||||
if (typeof username !== "string" || typeof password !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||
username,
|
||||
]);
|
||||
|
|
|
@ -477,6 +477,12 @@ class Monitor extends BeanModel {
|
|||
stop() {
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
this.isStop = true;
|
||||
|
||||
this.prometheus().remove();
|
||||
}
|
||||
|
||||
prometheus() {
|
||||
return new Prometheus(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -86,6 +86,16 @@ class Prometheus {
|
|||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
try {
|
||||
monitor_cert_days_remaining.remove(this.monitorLabelValues);
|
||||
monitor_cert_is_valid.remove(this.monitorLabelValues);
|
||||
monitor_response_time.remove(this.monitorLabelValues);
|
||||
monitor_status.remove(this.monitorLabelValues);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -34,6 +34,14 @@ const loginRateLimiter = new KumaRateLimiter({
|
|||
errorMessage: "Too frequently, try again later."
|
||||
});
|
||||
|
||||
const twoFaRateLimiter = new KumaRateLimiter({
|
||||
tokensPerInterval: 30,
|
||||
interval: "minute",
|
||||
fireImmediately: true,
|
||||
errorMessage: "Too frequently, try again later."
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
loginRateLimiter
|
||||
loginRateLimiter,
|
||||
twoFaRateLimiter,
|
||||
};
|
||||
|
|
123
server/server.js
123
server/server.js
|
@ -52,7 +52,7 @@ console.log("Importing this project modules");
|
|||
debug("Importing Monitor");
|
||||
const Monitor = require("./model/monitor");
|
||||
debug("Importing Settings");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog } = require("./util-server");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server");
|
||||
|
||||
debug("Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
|
@ -63,7 +63,7 @@ const Database = require("./database");
|
|||
|
||||
debug("Importing Background Jobs");
|
||||
const { initBackgroundJobs } = require("./jobs");
|
||||
const { loginRateLimiter } = require("./rate-limiter");
|
||||
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
|
||||
|
||||
const { basicAuth } = require("./auth");
|
||||
const { login } = require("./auth");
|
||||
|
@ -306,6 +306,15 @@ exports.entryPage = "dashboard";
|
|||
socket.on("login", async (data, callback) => {
|
||||
console.log("Login");
|
||||
|
||||
// Checking
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Login Rate Limit
|
||||
if (! await loginRateLimiter.pass(callback)) {
|
||||
return;
|
||||
|
@ -364,14 +373,27 @@ exports.entryPage = "dashboard";
|
|||
});
|
||||
|
||||
socket.on("logout", async (callback) => {
|
||||
// Rate Limit
|
||||
if (! await loginRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.leave(socket.userID);
|
||||
socket.userID = null;
|
||||
callback();
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("prepare2FA", async (callback) => {
|
||||
socket.on("prepare2FA", async (currentPassword, callback) => {
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
|
@ -406,14 +428,19 @@ exports.entryPage = "dashboard";
|
|||
} catch (error) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Error while trying to prepare 2FA.",
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("save2FA", async (callback) => {
|
||||
socket.on("save2FA", async (currentPassword, callback) => {
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
|
||||
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
||||
socket.userID,
|
||||
|
@ -426,14 +453,19 @@ exports.entryPage = "dashboard";
|
|||
} catch (error) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Error while trying to change 2FA.",
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disable2FA", async (callback) => {
|
||||
socket.on("disable2FA", async (currentPassword, callback) => {
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
await TwoFA.disable2FA(socket.userID);
|
||||
|
||||
callback({
|
||||
|
@ -443,36 +475,47 @@ exports.entryPage = "dashboard";
|
|||
} catch (error) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Error while trying to change 2FA.",
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("verifyToken", async (token, callback) => {
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
socket.on("verifyToken", async (token, currentPassword, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
|
||||
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
if (user.twofa_last_token !== token && verify) {
|
||||
callback({
|
||||
ok: true,
|
||||
valid: true,
|
||||
});
|
||||
} else {
|
||||
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
|
||||
|
||||
if (user.twofa_last_token !== token && verify) {
|
||||
callback({
|
||||
ok: true,
|
||||
valid: true,
|
||||
});
|
||||
} else {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid Token.",
|
||||
valid: false,
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid Token.",
|
||||
valid: false,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("twoFAStatus", async (callback) => {
|
||||
checkLogin(socket);
|
||||
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
|
@ -489,9 +532,10 @@ exports.entryPage = "dashboard";
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Error while trying to get 2FA status.",
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -580,6 +624,9 @@ exports.entryPage = "dashboard";
|
|||
throw new Error("Permission denied.");
|
||||
}
|
||||
|
||||
// Reset Prometheus labels
|
||||
monitorList[monitor.id]?.prometheus()?.remove();
|
||||
|
||||
bean.name = monitor.name;
|
||||
bean.type = monitor.type;
|
||||
bean.url = monitor.url;
|
||||
|
@ -937,21 +984,13 @@ exports.entryPage = "dashboard";
|
|||
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
let user = await doubleCheckPassword(socket, password.currentPassword);
|
||||
await user.resetPassword(password.newPassword);
|
||||
|
||||
if (user && passwordHash.verify(password.currentPassword, user.password)) {
|
||||
|
||||
user.resetPassword(password.newPassword);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Password has been updated successfully.",
|
||||
});
|
||||
} else {
|
||||
throw new Error("Incorrect current password");
|
||||
}
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Password has been updated successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
|
@ -978,10 +1017,14 @@ exports.entryPage = "dashboard";
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("setSettings", async (data, callback) => {
|
||||
socket.on("setSettings", async (data, currentPassword, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
if (data.disableAuth) {
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
}
|
||||
|
||||
await setSettings("general", data);
|
||||
exports.entryPage = data.entryPage;
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
const tcpp = require("tcp-ping");
|
||||
const Ping = require("./ping-lite");
|
||||
const { R } = require("redbean-node");
|
||||
const { debug } = require("../src/util");
|
||||
const { debug, genSecret } = require("../src/util");
|
||||
const passwordHash = require("./password-hash");
|
||||
const dayjs = require("dayjs");
|
||||
const { Resolver } = require("dns");
|
||||
const child_process = require("child_process");
|
||||
const iconv = require("iconv-lite");
|
||||
|
@ -32,7 +31,7 @@ exports.initJWTSecret = async () => {
|
|||
jwtSecretBean.key = "jwtSecret";
|
||||
}
|
||||
|
||||
jwtSecretBean.value = passwordHash.generate(dayjs() + "");
|
||||
jwtSecretBean.value = passwordHash.generate(genSecret());
|
||||
await R.store(jwtSecretBean);
|
||||
return jwtSecretBean;
|
||||
};
|
||||
|
@ -321,6 +320,28 @@ exports.checkLogin = (socket) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For logged-in users, double-check the password
|
||||
* @param socket
|
||||
* @param currentPassword
|
||||
* @returns {Promise<Bean>}
|
||||
*/
|
||||
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
||||
if (typeof currentPassword !== "string") {
|
||||
throw new Error("Wrong data type?");
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
||||
throw new Error("Incorrect current password");
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
exports.startUnitTest = async () => {
|
||||
console.log("Starting unit test...");
|
||||
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue