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) {
|
||||
return jwt.sign({
|
||||
userID: user.id,
|
||||
username: user.username,
|
||||
h: shake256(user.password, SHAKE256_LENGTH),
|
||||
}, jwtSecret);
|
||||
|
|
|
@ -150,6 +150,7 @@ const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
|
|||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||
const { SetupDatabase } = require("./setup-database");
|
||||
const { chartSocketHandler } = require("./socket-handlers/chart-socket-handler");
|
||||
const { sendUserList, getUser, saveUser } = require("./user");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
|
@ -494,7 +495,7 @@ let needSetup = false;
|
|||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
await doubleCheckPassword(socket.userID, currentPassword);
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
|
@ -544,7 +545,7 @@ let needSetup = false;
|
|||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
await doubleCheckPassword(socket.userID, currentPassword);
|
||||
|
||||
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
||||
socket.userID,
|
||||
|
@ -577,7 +578,7 @@ let needSetup = false;
|
|||
}
|
||||
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
await doubleCheckPassword(socket.userID, currentPassword);
|
||||
await TwoFA.disable2FA(socket.userID);
|
||||
|
||||
log.info("auth", `Disabled 2FA token. IP=${clientIP}`);
|
||||
|
@ -601,7 +602,7 @@ let needSetup = false;
|
|||
socket.on("verifyToken", async (token, currentPassword, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
await doubleCheckPassword(socket.userID, currentPassword);
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
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.");
|
||||
}
|
||||
|
||||
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");
|
||||
user.username = username;
|
||||
user.password = passwordHash.generate(password);
|
||||
|
@ -697,6 +694,61 @@ let needSetup = false;
|
|||
// 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
|
||||
socket.on("add", async (monitor, callback) => {
|
||||
try {
|
||||
|
@ -1282,7 +1334,7 @@ let needSetup = false;
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("changePassword", async (password, callback) => {
|
||||
socket.on("changePassword", async (userID, password, callback) => {
|
||||
try {
|
||||
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.");
|
||||
}
|
||||
|
||||
let user = await doubleCheckPassword(socket, password.currentPassword);
|
||||
let user = await doubleCheckPassword(userID, password.currentPassword);
|
||||
await user.resetPassword(password.newPassword);
|
||||
|
||||
server.disconnectAllSocketClients(user.id, socket.id);
|
||||
|
@ -1649,6 +1701,7 @@ async function afterLogin(socket, user) {
|
|||
sendAPIKeyList(socket),
|
||||
sendRemoteBrowserList(socket),
|
||||
sendMonitorTypeList(socket),
|
||||
sendUserList(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
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {number} userID ID of user to check
|
||||
* @param {string} currentPassword Password to validate
|
||||
* @returns {Promise<Bean>} User
|
||||
* @throws The current password is not a string
|
||||
* @throws The provided password is not correct
|
||||
*/
|
||||
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
||||
exports.doubleCheckPassword = async (userID, currentPassword) => {
|
||||
if (typeof currentPassword !== "string") {
|
||||
throw new Error("Wrong data type?");
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||
socket.userID,
|
||||
]);
|
||||
let user = await R.findOne("user", " id = ? ", [ userID ]);
|
||||
|
||||
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
||||
throw new Error("Incorrect current password");
|
||||
|
|
Loading…
Add table
Reference in a new issue