mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-03-04 00:15:57 +00:00
API: add basic multiple admin users
This commit is contained in:
parent
d5db40f40d
commit
51f94d6cf5
4 changed files with 145 additions and 15 deletions
|
@ -43,6 +43,7 @@ class User extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static createJWT(user, jwtSecret) {
|
static createJWT(user, jwtSecret) {
|
||||||
return jwt.sign({
|
return jwt.sign({
|
||||||
|
userID: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
h: shake256(user.password, SHAKE256_LENGTH),
|
h: shake256(user.password, SHAKE256_LENGTH),
|
||||||
}, jwtSecret);
|
}, jwtSecret);
|
||||||
|
|
|
@ -150,6 +150,7 @@ const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
|
||||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||||
const { SetupDatabase } = require("./setup-database");
|
const { SetupDatabase } = require("./setup-database");
|
||||||
const { chartSocketHandler } = require("./socket-handlers/chart-socket-handler");
|
const { chartSocketHandler } = require("./socket-handlers/chart-socket-handler");
|
||||||
|
const { sendUserList, getUser, saveUser } = require("./user");
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
@ -494,7 +495,7 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket.userID, currentPassword);
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -544,7 +545,7 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket.userID, currentPassword);
|
||||||
|
|
||||||
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -577,7 +578,7 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket.userID, currentPassword);
|
||||||
await TwoFA.disable2FA(socket.userID);
|
await TwoFA.disable2FA(socket.userID);
|
||||||
|
|
||||||
log.info("auth", `Disabled 2FA token. IP=${clientIP}`);
|
log.info("auth", `Disabled 2FA token. IP=${clientIP}`);
|
||||||
|
@ -601,7 +602,7 @@ let needSetup = false;
|
||||||
socket.on("verifyToken", async (token, currentPassword, callback) => {
|
socket.on("verifyToken", async (token, currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket.userID, currentPassword);
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -668,10 +669,6 @@ let needSetup = false;
|
||||||
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((await R.knex("user").count("id as count").first()).count !== 0) {
|
|
||||||
throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = R.dispense("user");
|
let user = R.dispense("user");
|
||||||
user.username = username;
|
user.username = username;
|
||||||
user.password = passwordHash.generate(password);
|
user.password = passwordHash.generate(password);
|
||||||
|
@ -697,6 +694,61 @@ let needSetup = false;
|
||||||
// Auth Only API
|
// Auth Only API
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|
||||||
|
socket.on("getUsers", async callback => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
const users = await sendUserList(socket);
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
users
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("getUser", async (userID, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
const user = await getUser(userID);
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
user
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("saveUser", async (user, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
await saveUser(socket, user);
|
||||||
|
await sendUserList(socket);
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
msg: "Saved Successfully.",
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add a new monitor
|
// Add a new monitor
|
||||||
socket.on("add", async (monitor, callback) => {
|
socket.on("add", async (monitor, callback) => {
|
||||||
try {
|
try {
|
||||||
|
@ -1282,7 +1334,7 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("changePassword", async (password, callback) => {
|
socket.on("changePassword", async (userID, password, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
@ -1294,7 +1346,7 @@ let needSetup = false;
|
||||||
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
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 doubleCheckPassword(socket, password.currentPassword);
|
let user = await doubleCheckPassword(userID, password.currentPassword);
|
||||||
await user.resetPassword(password.newPassword);
|
await user.resetPassword(password.newPassword);
|
||||||
|
|
||||||
server.disconnectAllSocketClients(user.id, socket.id);
|
server.disconnectAllSocketClients(user.id, socket.id);
|
||||||
|
@ -1649,6 +1701,7 @@ async function afterLogin(socket, user) {
|
||||||
sendAPIKeyList(socket),
|
sendAPIKeyList(socket),
|
||||||
sendRemoteBrowserList(socket),
|
sendRemoteBrowserList(socket),
|
||||||
sendMonitorTypeList(socket),
|
sendMonitorTypeList(socket),
|
||||||
|
sendUserList(socket),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await StatusPage.sendStatusPageList(io, socket);
|
await StatusPage.sendStatusPageList(io, socket);
|
||||||
|
|
78
server/user.js
Normal file
78
server/user.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
const { TimeLogger } = require("../src/util");
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
|
const server = UptimeKumaServer.getInstance();
|
||||||
|
const io = server.io;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send list of users to client
|
||||||
|
* @param {Socket} socket Socket.io socket instance
|
||||||
|
* @returns {Promise<Bean[]>} list of users
|
||||||
|
*/
|
||||||
|
async function sendUserList(socket) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
const userList = await R.getAll("SELECT id, username, active FROM user");
|
||||||
|
|
||||||
|
io.to(socket.userID).emit("userList", userList);
|
||||||
|
timeLogger.print("Send User List");
|
||||||
|
|
||||||
|
return userList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch specified user
|
||||||
|
* @param {number} userID ID of user to retrieve
|
||||||
|
* @returns {Promise<Bean[]>} User
|
||||||
|
*/
|
||||||
|
async function getUser(userID) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
|
const user = await R.getRow(
|
||||||
|
"SELECT id, username, active FROM user WHERE id = ? ",
|
||||||
|
[ userID ]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLogger.print(`Get user ${userID}`);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves and updates given user entity
|
||||||
|
* @param {Socket} socket Socket.io socket instance
|
||||||
|
* @param {object} user user to update
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function saveUser(socket, user) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
const { id, username, active } = user;
|
||||||
|
|
||||||
|
const bean = await R.findOne("user", " id = ? ", [ id ]);
|
||||||
|
|
||||||
|
if (!bean) {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
bean.username = username;
|
||||||
|
}
|
||||||
|
if (active !== undefined) {
|
||||||
|
bean.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
await R.store(bean);
|
||||||
|
|
||||||
|
io.to(socket.userID).emit("saveUser", bean);
|
||||||
|
|
||||||
|
timeLogger.print(`Save user ${user.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendUserList,
|
||||||
|
getUser,
|
||||||
|
saveUser
|
||||||
|
};
|
|
@ -765,20 +765,18 @@ exports.checkLogin = (socket) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For logged-in users, double-check the password
|
* For logged-in users, double-check the password
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param {number} userID ID of user to check
|
||||||
* @param {string} currentPassword Password to validate
|
* @param {string} currentPassword Password to validate
|
||||||
* @returns {Promise<Bean>} User
|
* @returns {Promise<Bean>} User
|
||||||
* @throws The current password is not a string
|
* @throws The current password is not a string
|
||||||
* @throws The provided password is not correct
|
* @throws The provided password is not correct
|
||||||
*/
|
*/
|
||||||
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
exports.doubleCheckPassword = async (userID, currentPassword) => {
|
||||||
if (typeof currentPassword !== "string") {
|
if (typeof currentPassword !== "string") {
|
||||||
throw new Error("Wrong data type?");
|
throw new Error("Wrong data type?");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await R.findOne("user", " id = ? ", [ userID ]);
|
||||||
socket.userID,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
||||||
throw new Error("Incorrect current password");
|
throw new Error("Incorrect current password");
|
||||||
|
|
Loading…
Add table
Reference in a new issue