mirror of https://github.com/louislam/uptime-kuma
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>pull/2558/head
parent
1d4af39820
commit
b8720b46c3
|
@ -2,7 +2,7 @@ const basicAuth = require("express-basic-auth");
|
|||
const passwordHash = require("./password-hash");
|
||||
const { R } = require("redbean-node");
|
||||
const { setting } = require("./util-server");
|
||||
const { loginRateLimiter } = require("./rate-limiter");
|
||||
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||
const { Settings } = require("./settings");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
|
@ -37,10 +37,9 @@ exports.login = async function (username, password) {
|
|||
|
||||
/**
|
||||
* Validate a provided API key
|
||||
* @param {string} key API Key passed by client
|
||||
* @returns {Promise<bool>}
|
||||
* @param {string} key API key to verify
|
||||
*/
|
||||
async function validateAPIKey(key) {
|
||||
async function verifyAPIKey(key) {
|
||||
if (typeof key !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
@ -64,8 +63,8 @@ async function validateAPIKey(key) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Callback for myAuthorizer
|
||||
* @callback myAuthorizerCB
|
||||
* Callback for basic auth authorizers
|
||||
* @callback authCallback
|
||||
* @param {any} err Any error encountered
|
||||
* @param {boolean} authorized Is the client authorized?
|
||||
*/
|
||||
|
@ -74,9 +73,31 @@ async function validateAPIKey(key) {
|
|||
* Custom authorizer for express-basic-auth
|
||||
* @param {string} username
|
||||
* @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
|
||||
loginRateLimiter.pass(null, 0).then((pass) => {
|
||||
if (pass) {
|
||||
|
@ -101,7 +122,7 @@ function myAuthorizer(username, password, callback) {
|
|||
*/
|
||||
exports.basicAuth = async function (req, res, next) {
|
||||
const middleware = basicAuth({
|
||||
authorizer: myAuthorizer,
|
||||
authorizer: userAuthorizer,
|
||||
authorizeAsync: true,
|
||||
challenge: true,
|
||||
});
|
||||
|
@ -124,25 +145,21 @@ exports.basicAuth = async function (req, res, 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);
|
||||
}
|
||||
});
|
||||
let middleware;
|
||||
if (usingAPIKeys) {
|
||||
middleware = basicAuth({
|
||||
authorizer: apiAuthorizer,
|
||||
authorizeAsync: true,
|
||||
challenge: true,
|
||||
});
|
||||
} else {
|
||||
middleware = basicAuth({
|
||||
authorizer: userAuthorizer,
|
||||
authorizeAsync: true,
|
||||
challenge: true,
|
||||
});
|
||||
}
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
|
|
@ -54,6 +54,13 @@ const loginRateLimiter = new KumaRateLimiter({
|
|||
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({
|
||||
tokensPerInterval: 30,
|
||||
interval: "minute",
|
||||
|
@ -63,5 +70,6 @@ const twoFaRateLimiter = new KumaRateLimiter({
|
|||
|
||||
module.exports = {
|
||||
loginRateLimiter,
|
||||
apiRateLimiter,
|
||||
twoFaRateLimiter,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue