diff --git a/extra/change-username.js b/extra/change-username.js new file mode 100644 index 000000000..dfa91577a --- /dev/null +++ b/extra/change-username.js @@ -0,0 +1,65 @@ +console.log("== Uptime Kuma Change Username Tool =="); + +const Database = require("../server/database"); +const { R } = require("redbean-node"); +const readline = require("readline"); +const { initJWTSecret } = require("../server/util-server"); +const User = require("../server/model/user"); +const args = require("args-parser")(process.argv); +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +const main = async () => { + console.log("Connecting the database"); + Database.init(args); + await Database.connect(false, false, true); + + try { + // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. + if (!process.env.TEST_BACKEND) { + const user = await R.findOne("user"); + if (! user) { + throw new Error("user not found, have you installed?"); + } + + console.log("Found user: " + user.username); + let newUsername = await question("New username: "); + await User.updateUsername(user.id, newUsername); + + // Reset all sessions by reset jwt secret + await initJWTSecret(); + + console.log("Username change successfully."); + } + } catch (e) { + console.error("Error: " + e.message); + } + + await Database.close(); + rl.close(); + + console.log("Finished."); +}; + +/** + * Ask question of user + * @param {string} question Question to ask + * @returns {Promise} Users response + */ +function question(question) { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer); + }); + }); +} + +if (!process.env.TEST_BACKEND) { + main(); +} + +module.exports = { + main, +}; diff --git a/package.json b/package.json index 5186cafcf..3367c4190 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", + "change-username": "node extra/change-username.js", "reset-password": "node extra/reset-password.js", "remove-2fa": "node extra/remove-2fa.js", "simple-dns-server": "node extra/simple-dns-server.js", diff --git a/server/auth.js b/server/auth.js index 597cf3d75..9a71390e1 100644 --- a/server/auth.js +++ b/server/auth.js @@ -7,6 +7,9 @@ const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter"); const { Settings } = require("./settings"); const dayjs = require("dayjs"); +const remoteAuthEnabled = process.env.REMOTE_AUTH_ENABLED || false; +const remoteAuthHeader = process.env.REMOTE_AUTH_HEADER || "Remote-User"; + /** * Login to web app * @param {string} username Username to login with @@ -133,29 +136,40 @@ function userAuthorizer(username, password, callback) { * @returns {Promise} */ exports.basicAuth = async function (req, res, next) { - const middleware = basicAuth({ - authorizer: userAuthorizer, - authorizeAsync: true, - challenge: true, - }); - const disabledAuth = await setting("disableAuth"); - if (!disabledAuth) { - middleware(req, res, next); - } else { - next(); + if (remoteAuthEnabled) { + const remoteUser = req.headers[remoteAuthHeader.toLowerCase()]; + if (remoteUser !== undefined) { + let user = await R.findOne("user", " username = ? AND active = 1 ", [ remoteUser ]); + if (user) { + next(); + return; + } + } } + + if (!disabledAuth) { + const middleware = basicAuth({ + authorizer: userAuthorizer, + authorizeAsync: true, + challenge: true, + }); + middleware(req, res, next); + return; + } + + next(); }; /** - * Use use API Key if API keys enabled, else use basic auth + * Use API Key if API keys enabled, else use basic auth * @param {express.Request} req Express request object * @param {express.Response} res Express response object * @param {express.NextFunction} next Next handler in chain * @returns {Promise} */ -exports.apiAuth = async function (req, res, next) { +exports.authMiddleware = async function (req, res, next) { if (!await Settings.get("disableAuth")) { let usingAPIKeys = await Settings.get("apiKeysEnabled"); let middleware; diff --git a/server/model/user.js b/server/model/user.js index 329402ff5..c177d787d 100644 --- a/server/model/user.js +++ b/server/model/user.js @@ -48,6 +48,17 @@ class User extends BeanModel { }, jwtSecret); } + /** + * @param {number} userID ID of user to update + * @param {string} newUsername Users new username + * @returns {Promise} + */ + static async updateUsername(userID, newUsername) { + await R.exec("UPDATE `user` SET username = ? WHERE id = ? ", [ + newUsername, + userID + ]); + } } module.exports = User; diff --git a/server/server.js b/server/server.js index c88daca88..0d54a76cc 100644 --- a/server/server.js +++ b/server/server.js @@ -104,12 +104,14 @@ log.debug("server", "Importing Background Jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); -const { apiAuth } = require("./auth"); +const { authMiddleware } = require("./auth"); const { login } = require("./auth"); const passwordHash = require("./password-hash"); -const hostname = config.hostname; +const remoteAuthEnabled = process.env.REMOTE_AUTH_ENABLED || false; +const remoteAuthHeader = process.env.REMOTE_AUTH_HEADER || "Remote-User"; +const hostname = config.hostname; if (hostname) { log.info("server", "Custom hostname: " + hostname); } @@ -292,7 +294,7 @@ let needSetup = false; // Prometheus API metrics /metrics // With Basic Auth using the first user's username/password - app.get("/metrics", apiAuth, prometheusAPIMetrics()); + app.get("/metrics", authMiddleware, prometheusAPIMetrics()); app.use("/", expressStaticGzip("dist", { enableBrotli: true, @@ -1583,10 +1585,26 @@ let needSetup = false; // *************************** log.debug("auth", "check auto login"); - if (await setting("disableAuth")) { + if (await Settings.get("disableAuth")) { log.info("auth", "Disabled Auth: auto login to admin"); await afterLogin(socket, await R.findOne("user")); socket.emit("autoLogin"); + } else if (remoteAuthEnabled) { + log.debug("auth", socket.handshake.headers); + const remoteUser = socket.handshake.headers[remoteAuthHeader.toLowerCase()]; + if (remoteUser !== undefined) { + const user = await R.findOne("user", " username = ? AND active = 1 ", [ remoteUser ]); + if (user) { + log.info("auth", `Login by remote-user header. IP=${await server.getClientIP(socket)}`); + log.debug("auth", `Remote user ${remoteUser} exists, found user ${user.username}`); + afterLogin(socket, user); + socket.emit("autoLoginRemoteHeader", user.username); + } else { + log.debug("auth", `Remote user ${remoteUser} doesn't exist`); + } + } else { + log.debug("auth", "Remote user header set but not found in headers"); + } } else { socket.emit("loginRequired"); log.debug("auth", "need auth"); diff --git a/src/components/settings/Security.vue b/src/components/settings/Security.vue index 5d8aed85b..0c03bf82f 100644 --- a/src/components/settings/Security.vue +++ b/src/components/settings/Security.vue @@ -5,66 +5,67 @@ -
+
{{ $t("Two Factor Authentication") }}
@@ -85,7 +86,7 @@
- +
diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 9faedf589..e8566c0d2 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -69,7 +69,7 @@ -
  • +