Skip to content

Commit

Permalink
Refactor core auth (nightscout#6596)
Browse files Browse the repository at this point in the history
* Auth resolve now supports async/await
* Read only tokens can be used for authentication and the UI shows privileges for these accounts correctly
* Failed attempt at authenticating an API_SECRET or token delays subsequent auth attempt by 5000 ms
  • Loading branch information
sulkaharo authored and arnaudlimbourg committed Jul 4, 2021
1 parent 6c90c0b commit 6cea5b9
Show file tree
Hide file tree
Showing 18 changed files with 430 additions and 262 deletions.
6 changes: 5 additions & 1 deletion lib/api/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ function configure (app, wares, env, ctx) {

var authToken = req.query.token || req.query.secret || '';

function getRemoteIP (req) {
return req.headers['x-forwarded-for'] || req.connection.remoteAddress;
}

var date = new Date();
var info = { status: 'ok'
, name: app.get('name')
Expand All @@ -31,7 +35,7 @@ function configure (app, wares, env, ctx) {
, boluscalcEnabled: app.enabled('api') && env.settings.enable.indexOf('boluscalc') > -1
, settings: settings
, extendedSettings: extended
, authorized: ctx.authorization.authorize(authToken)
, authorized: ctx.authorization.authorize(authToken, getRemoteIP(req))
, runtimeState: ctx.runtimeState
};

Expand Down
21 changes: 15 additions & 6 deletions lib/api/verifyauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,24 @@ function configure (ctx) {

api.get('/verifyauth', function(req, res) {
ctx.authorization.resolveWithRequest(req, function resolved (err, result) {

// this is used to see if req has api-secret equivalent authorization
var authorized = !err &&
ctx.authorization.checkMultiple('*:*:create,update,delete', result.shiros) && //can write to everything
ctx.authorization.checkMultiple('admin:*:*:*', result.shiros); //full admin permissions too
var canRead = !err &&
ctx.authorization.checkMultiple('*:*:read', result.shiros);
var canWrite = !err &&
ctx.authorization.checkMultiple('*:*:write', result.shiros);
var isAdmin = !err &&
ctx.authorization.checkMultiple('*:*:admin', result.shiros);
var authorized = canRead && !result.defaults;

var response = {
canRead,
canWrite,
isAdmin,
message: authorized ? 'OK' : 'UNAUTHORIZED',
rolefound: result.subject ? 'FOUND' : 'NOTFOUND'
}
rolefound: result.subject ? 'FOUND' : 'NOTFOUND',
permissions: result.defaults ? 'DEFAULT' : 'ROLE'
};


res.sendJSONStatus(res, consts.HTTP_OK, response);
});
Expand Down
6 changes: 5 additions & 1 deletion lib/api3/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const moment = require('moment')
;


function getRemoteIP (req) {
return req.headers['x-forwarded-for'] || req.connection.remoteAddress;
}

/**
* Check if Date header in HTTP request (or 'now' query parameter) is present and valid (with error response sending)
*/
Expand Down Expand Up @@ -68,7 +72,7 @@ function authenticate (opCtx) {
opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN));
}

ctx.authorization.resolve({ token }, function resolveFinish (err, result) {
ctx.authorization.resolve({ token, ip: getRemoteIP(req) }, function resolveFinish (err, result) {
if (err) {
return reject(
opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_BAD_TOKEN));
Expand Down
58 changes: 58 additions & 0 deletions lib/authorization/delaylist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

function init () {

const ipDelayList = {};

const DELAY_ON_FAIL = 5000;
const FAIL_AGE = 60000;

const sleep = require('util').promisify(setTimeout);

ipDelayList.addFailedRequest = function addFailedRequest (ip) {
const ipString = String(ip);
let entry = ipDelayList[ipString];
const now = Date.now();
if (!entry) {
ipDelayList[ipString] = now + DELAY_ON_FAIL;
return;
}
if (now >= entry) { entry = now; }
ipDelayList[ipString] = entry + DELAY_ON_FAIL;
};

ipDelayList.shouldDelayRequest = function shouldDelayRequest (ip) {
const ipString = String(ip);
const entry = ipDelayList[ipString];
let now = Date.now();
if (entry) {
if (now < entry) {
return entry - now;
}
}
return false;
};

ipDelayList.requestSucceeded = function requestSucceeded (ip) {
const ipString = String(ip);
if (ipDelayList[ipString]) {
delete ipDelayList[ipString];
}
};

// Clear items older than a minute

setTimeout(function clearList () {
for (var key in ipDelayList) {
if (ipDelayList.hasOwnProperty(key)) {
if (Date.now() > ipDelayList[key] + FAIL_AGE) {
delete ipDelayList[key];
}
}
}
}, 30000);

return ipDelayList;
}

module.exports = init;
Loading

0 comments on commit 6cea5b9

Please sign in to comment.