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,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