Skip to content

Commit

Permalink
API: add basic multiple admin users
Browse files Browse the repository at this point in the history
  • Loading branch information
M1CK431 committed Aug 14, 2023
1 parent 4035793 commit b4bb715
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 15 deletions.
74 changes: 64 additions & 10 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const { Settings } = require("./settings");
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
const apicache = require("./modules/apicache");
const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
const { sendUserList, getUser, saveUser } = require("./user");

app.use(express.json());

Expand Down Expand Up @@ -358,6 +359,7 @@ let needSetup = false;
callback({
ok: true,
token: jwt.sign({
userID: user.id,
username: data.username,
}, server.jwtSecret),
});
Expand Down Expand Up @@ -434,7 +436,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,
Expand Down Expand Up @@ -483,7 +485,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,
Expand Down Expand Up @@ -515,7 +517,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}`);
Expand All @@ -538,7 +540,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,
Expand Down Expand Up @@ -604,10 +606,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.count("user")) !== 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);
Expand All @@ -632,6 +630,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 {
Expand Down Expand Up @@ -1122,7 +1175,7 @@ let needSetup = false;
}
});

socket.on("changePassword", async (password, callback) => {
socket.on("changePassword", async (userID, password, callback) => {
try {
checkLogin(socket);

Expand All @@ -1134,7 +1187,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);

callback({
Expand Down Expand Up @@ -1668,6 +1721,7 @@ async function afterLogin(socket, user) {
sendProxyList(socket);
sendDockerHostList(socket);
sendAPIKeyList(socket);
sendUserList(socket);

await sleep(500);

Expand Down
78 changes: 78 additions & 0 deletions server/user.js
Original file line number Diff line number Diff line change
@@ -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
};
8 changes: 3 additions & 5 deletions server/util-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -810,18 +810,16 @@ 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
* @returns {Promise<Bean>}
*/
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");
Expand Down

0 comments on commit b4bb715

Please sign in to comment.