mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-18 02:47:24 +00:00
Switched to using Authorization header
Prometheus doesn't support using custom headers for exporters, however it does support using the Authorisation header with basic auth. As such, we switched from using X-API-Key to Authorization with the basic scheme and an empty username field. Also added a rate limit for API endpoints of 60 requests in a minute Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
This commit is contained in:
parent
1d4af39820
commit
b8720b46c3
2 changed files with 53 additions and 28 deletions
|
@ -2,7 +2,7 @@ const basicAuth = require("express-basic-auth");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
const { loginRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
|
@ -37,10 +37,9 @@ exports.login = async function (username, password) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a provided API key
|
* Validate a provided API key
|
||||||
* @param {string} key API Key passed by client
|
* @param {string} key API key to verify
|
||||||
* @returns {Promise<bool>}
|
|
||||||
*/
|
*/
|
||||||
async function validateAPIKey(key) {
|
async function verifyAPIKey(key) {
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -64,8 +63,8 @@ async function validateAPIKey(key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for myAuthorizer
|
* Callback for basic auth authorizers
|
||||||
* @callback myAuthorizerCB
|
* @callback authCallback
|
||||||
* @param {any} err Any error encountered
|
* @param {any} err Any error encountered
|
||||||
* @param {boolean} authorized Is the client authorized?
|
* @param {boolean} authorized Is the client authorized?
|
||||||
*/
|
*/
|
||||||
|
@ -74,9 +73,31 @@ async function validateAPIKey(key) {
|
||||||
* Custom authorizer for express-basic-auth
|
* Custom authorizer for express-basic-auth
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {myAuthorizerCB} callback
|
* @param {authCallback} callback
|
||||||
*/
|
*/
|
||||||
function myAuthorizer(username, password, callback) {
|
function apiAuthorizer(username, password, callback) {
|
||||||
|
// API Rate Limit
|
||||||
|
apiRateLimiter.pass(null, 0).then((pass) => {
|
||||||
|
if (pass) {
|
||||||
|
verifyAPIKey(password).then((valid) => {
|
||||||
|
callback(null, valid);
|
||||||
|
// Only allow a set number of api requests per minute
|
||||||
|
// (currently set to 60)
|
||||||
|
apiRateLimiter.removeTokens(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom authorizer for express-basic-auth
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} password
|
||||||
|
* @param {authCallback} callback
|
||||||
|
*/
|
||||||
|
function userAuthorizer(username, password, callback) {
|
||||||
// Login Rate Limit
|
// Login Rate Limit
|
||||||
loginRateLimiter.pass(null, 0).then((pass) => {
|
loginRateLimiter.pass(null, 0).then((pass) => {
|
||||||
if (pass) {
|
if (pass) {
|
||||||
|
@ -101,7 +122,7 @@ function myAuthorizer(username, password, callback) {
|
||||||
*/
|
*/
|
||||||
exports.basicAuth = async function (req, res, next) {
|
exports.basicAuth = async function (req, res, next) {
|
||||||
const middleware = basicAuth({
|
const middleware = basicAuth({
|
||||||
authorizer: myAuthorizer,
|
authorizer: userAuthorizer,
|
||||||
authorizeAsync: true,
|
authorizeAsync: true,
|
||||||
challenge: true,
|
challenge: true,
|
||||||
});
|
});
|
||||||
|
@ -124,25 +145,21 @@ exports.basicAuth = async function (req, res, next) {
|
||||||
exports.apiAuth = async function (req, res, next) {
|
exports.apiAuth = async function (req, res, next) {
|
||||||
if (!await Settings.get("disableAuth")) {
|
if (!await Settings.get("disableAuth")) {
|
||||||
let usingAPIKeys = await Settings.get("apiKeysEnabled");
|
let usingAPIKeys = await Settings.get("apiKeysEnabled");
|
||||||
|
let middleware;
|
||||||
loginRateLimiter.pass(null, 0).then((pass) => {
|
|
||||||
if (usingAPIKeys) {
|
if (usingAPIKeys) {
|
||||||
let pwd = req.get("X-API-Key");
|
middleware = basicAuth({
|
||||||
if (pwd !== null && pwd !== undefined) {
|
authorizer: apiAuthorizer,
|
||||||
validateAPIKey(pwd).then((valid) => {
|
authorizeAsync: true,
|
||||||
if (valid) {
|
challenge: true,
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.status(401).send();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(401).send();
|
middleware = basicAuth({
|
||||||
}
|
authorizer: userAuthorizer,
|
||||||
} else {
|
authorizeAsync: true,
|
||||||
exports.basicAuth(req, res, next);
|
challenge: true,
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
middleware(req, res, next);
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,13 @@ const loginRateLimiter = new KumaRateLimiter({
|
||||||
errorMessage: "Too frequently, try again later."
|
errorMessage: "Too frequently, try again later."
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const apiRateLimiter = new KumaRateLimiter({
|
||||||
|
tokensPerInterval: 60,
|
||||||
|
interval: "minute",
|
||||||
|
fireImmediately: true,
|
||||||
|
errorMessage: "Too frequently, try again later."
|
||||||
|
});
|
||||||
|
|
||||||
const twoFaRateLimiter = new KumaRateLimiter({
|
const twoFaRateLimiter = new KumaRateLimiter({
|
||||||
tokensPerInterval: 30,
|
tokensPerInterval: 30,
|
||||||
interval: "minute",
|
interval: "minute",
|
||||||
|
@ -63,5 +70,6 @@ const twoFaRateLimiter = new KumaRateLimiter({
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loginRateLimiter,
|
loginRateLimiter,
|
||||||
|
apiRateLimiter,
|
||||||
twoFaRateLimiter,
|
twoFaRateLimiter,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue