From 86ee98e0e8cae0dc11db56e43957fc77c4bcf313 Mon Sep 17 00:00:00 2001 From: Marc Hagen Date: Sun, 24 Apr 2022 13:21:56 +0200 Subject: [PATCH 1/2] [feat] Adding RemoteUser authentication --- server/auth.js | 38 ++++++--- server/model/user.js | 11 +++ server/server.js | 26 +++++- src/components/settings/Security.vue | 113 ++++++++++++++------------- src/layouts/Layout.vue | 2 +- src/mixins/socket.js | 32 +++++++- 6 files changed, 146 insertions(+), 76 deletions(-) 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 38158c546..f5e5914fb 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); } @@ -262,7 +264,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, @@ -1538,10 +1540,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 610681835..82e23ba16 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -69,7 +69,7 @@ -
  • +