diff --git a/server/auth.js b/server/auth.js index fd19b0e44..f2c59d66f 100644 --- a/server/auth.js +++ b/server/auth.js @@ -3,6 +3,8 @@ const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); const { loginRateLimiter } = require("./rate-limiter"); +const { Settings } = require("./settings"); +const dayjs = require("dayjs"); /** * Login to web app @@ -33,6 +35,32 @@ exports.login = async function (username, password) { return null; }; +/** + * Validate a provided API key + * @param {string} key API Key passed by client + * @returns {Promise} + */ +async function validateAPIKey(key) { + if (typeof key !== "string") { + return false; + } + + let index = key.substring(0, key.indexOf("-")); + let clear = key.substring(key.indexOf("-") + 1, key.length); + console.log(index); + console.log(clear); + + let hash = await R.findOne("api_key", " id=? ", [ index ]); + + let current = dayjs(); + let expiry = dayjs(hash.expires); + if (expiry.diff(current) < 0, !hash.active) { + return false; + } + + return hash && passwordHash.verify(clear, hash.key); +} + /** * Callback for myAuthorizer * @callback myAuthorizerCB @@ -84,3 +112,36 @@ exports.basicAuth = async function (req, res, next) { next(); } }; + +/** + * Use X-API-Key header 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 + */ +exports.apiAuth = async function (req, res, next) { + if (!await Settings.get("disableAuth")) { + let usingAPIKeys = await Settings.get("apiKeysEnabled"); + + loginRateLimiter.pass(null, 0).then((pass) => { + if (usingAPIKeys) { + let pwd = req.get("X-API-Key"); + if (pwd !== null && pwd !== undefined) { + validateAPIKey(pwd).then((valid) => { + if (valid) { + next(); + } else { + res.status(401).send(); + } + }); + } else { + res.status(401).send(); + } + } else { + exports.basicAuth(req, res, next); + } + }); + } else { + next(); + } +}; diff --git a/server/server.js b/server/server.js index 183a5bb38..8696aa709 100644 --- a/server/server.js +++ b/server/server.js @@ -87,7 +87,7 @@ log.debug("server", "Importing Background Jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); -const { basicAuth } = require("./auth"); +const { apiAuth } = require("./auth"); const { login } = require("./auth"); const passwordHash = require("./password-hash"); @@ -230,7 +230,7 @@ let needSetup = false; // Prometheus API metrics /metrics // With Basic Auth using the first user's username/password - app.get("/metrics", basicAuth, prometheusAPIMetrics()); + app.get("/metrics", apiAuth, prometheusAPIMetrics()); app.use("/", expressStaticGzip("dist", { enableBrotli: true, diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js index a80dca830..cf124cad3 100644 --- a/server/socket-handlers/api-key-socket-handler.js +++ b/server/socket-handlers/api-key-socket-handler.js @@ -5,6 +5,7 @@ const crypto = require("crypto"); const passwordHash = require("../password-hash"); const apicache = require("../modules/apicache"); const APIKey = require("../model/api_key"); +const { Settings } = require("../settings"); const { sendAPIKeyList } = require("../client"); /** @@ -29,6 +30,10 @@ module.exports.apiKeySocketHandler = (socket) => { let formattedKey = bean.id + "-" + clearKey; await sendAPIKeyList(socket); + // Enable API auth if the user creates a key, otherwise only basic + // auth will be used for API. + await Settings.set("apiKeysEnabled", true); + callback({ ok: true, msg: "Added Successfully.",