Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wip/bewest/express slow down #7469

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/example-template.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ LANGUAGE=en
INSECURE_USE_HTTP=true
PORT=1337
NODE_ENV=development
AUTH_FAIL_DELAY=50
UNAUTHORIZED_DELAY_MS=50
3 changes: 2 additions & 1 deletion lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ function create (env, ctx) {

app.set('title', [app.get('name'), 'API', app.get('version')].join(' '));

app.use(wares.speedLimit);

// Start setting up routes
if (app.enabled('api')) {
// experiments
app.use('/experiments', require('./experiments/')(app, wares, ctx));
}


app.use(wares.extensions([
'json', 'svg', 'csv', 'txt', 'png', 'html', 'tsv'
]));
Expand Down
12 changes: 8 additions & 4 deletions lib/api/verifyauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function configure (ctx) {
var express = require('express'),
api = express.Router( );

api.get('/verifyauth', function(req, res) {
api.get('/verifyauth', function(req, res, next) {
ctx.authorization.resolveWithRequest(req, function resolved (err, result) {
// this is used to see if req has api-secret equivalent authorization
var canRead = !err &&
Expand All @@ -22,13 +22,17 @@ function configure (ctx) {
canWrite,
isAdmin,
message: authorized ? 'OK' : 'UNAUTHORIZED',
required: true,
is_permitted: authorized,
rolefound: result.subject ? 'FOUND' : 'NOTFOUND',
permissions: result.defaults ? 'DEFAULT' : 'ROLE'
};


res.sendJSONStatus(res, consts.HTTP_OK, response);
req.authInfo = response;
next( );
});
}, ctx.authorization.speedLimit, function verifyauth_json (req, res, next) {
var response = req.authInfo;
res.sendJSONStatus(res, consts.HTTP_OK, response);
});

return api;
Expand Down
58 changes: 0 additions & 58 deletions lib/authorization/delaylist.js

This file was deleted.

4 changes: 4 additions & 0 deletions lib/authorization/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var _ = require('lodash');
var express = require('express');
var slowDown = require('express-slow-down');

var consts = require('./../constants');

Expand All @@ -18,6 +19,9 @@ function init (env, authorization) {
// also support url-encoded content-type
endpoints.use(wares.bodyParser.urlencoded({ extended: true }));

var speedLimit = wares.speedLimit;
endpoints.use(speedLimit);

endpoints.get('/request/:accessToken', function requestAuthorize (req, res) {
var authorized = authorization.authorize(req.params.accessToken);

Expand Down
48 changes: 31 additions & 17 deletions lib/authorization/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const shiroTrie = require('shiro-trie');
const consts = require('./../constants');
const sleep = require('util').promisify(setTimeout);
const forwarded = require('forwarded-for');
const slowDown = require('express-slow-down');

function getRemoteIP (req) {
const address = forwarded(req, req.headers);
Expand All @@ -15,15 +16,25 @@ function getRemoteIP (req) {

function init (env, ctx) {

const ipdelaylist = require('./delaylist')(env, ctx);
const addFailedRequest = ipdelaylist.addFailedRequest;
const shouldDelayRequest = ipdelaylist.shouldDelayRequest;
const requestSucceeded = ipdelaylist.requestSucceeded;

var authorization = {};
var storage = authorization.storage = require('./storage')(env, ctx);
var defaultRoles = (env.settings.authDefaultRoles || '').split(/[, :]/);
const SLOWDOWN_WINDOW_MS = _.get(env, 'settings.unauthorizedDelayWindowMs');
const SLOWDOWN_DELAY_AFTER = _.get(env, 'settings.unauthorizedDelayAfter');
const SLOWDOWN_DELAY_MS = _.get(env, 'settings.unauthorizedDelayMs');

function skip_request (req, res) {
var required = req.authInfo && req.authInfo.required;
return !required || (req.authInfo && req.authInfo.is_permitted);
}

var speedLimit = authorization.speedLimit = slowDown({
windowMS: SLOWDOWN_WINDOW_MS,
delayAfter: SLOWDOWN_DELAY_AFTER,
delayMs: SLOWDOWN_DELAY_MS,
keyGenerator: getRemoteIP,
skip: skip_request
});
/**
* Loads JWT from request
*
Expand Down Expand Up @@ -154,12 +165,6 @@ function init (env, ctx) {
data.api_secret = null;
}

const requestDelay = shouldDelayRequest(data.ip);

if (requestDelay) {
await sleep(requestDelay);
}

const authAttempted = (data.api_secret || data.token) ? true : false;
const defaultShiros = storage.rolesToShiros(defaultRoles);

Expand All @@ -173,7 +178,6 @@ function init (env, ctx) {
// Check for API_SECRET first as that allows bailing out fast

if (data.api_secret && authorizeAdminSecret(data.api_secret)) {
requestSucceeded(data.ip);
var admin = shiroTrie.new();
admin.add(['*']);
const result = { shiros: [admin] };
Expand All @@ -200,15 +204,12 @@ function init (env, ctx) {
}

if (token) {
requestSucceeded(data.ip);
const results = authorization.resolveAccessToken(token, null, defaultShiros);
if (callback) { callback(null, results); }
return results;
}

console.error('Resolving secret/token to permissions failed');
addFailedRequest(data.ip);

ctx.bus.emit('admin-notify', {
title: ctx.language.translate('Failed authentication')
, message: ctx.language.translate('A device at IP address %1 attempted authenticating with Nightscout with wrong credentials. Check if you have an uploader setup with wrong API_SECRET or token?', data.ip)
Expand Down Expand Up @@ -264,15 +265,28 @@ function init (env, ctx) {
const permissions = await authorization.resolve(data);
const permitted = authorization.checkMultiple(permission, permissions.shiros);

if (permitted) {
var response = {
required: permission,
found: permissions,
is_permitted: permitted,
message: permitted ? 'OK' : 'UNAUTHORIZED',
};
req.authInfo = response;
next( );
}

function enforce (req, res, next) {

if (permission && req.authInfo.is_permitted) {
next();
return;
}

res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'Invalid/Missing');
}

return check;
// inspect the results and conditionally skip.
return [ check, speedLimit, enforce ];

};

Expand Down
23 changes: 22 additions & 1 deletion lib/middleware/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var _ = require('lodash');
const forwarded = require('forwarded-for');
const slowDown = require('express-slow-down');

function getRemoteIP (req) {
const address = forwarded(req, req.headers);
return address.ip;
}

var wares = {
sendJSONStatus : require('./send-json-status'),
bodyParser : require('body-parser'),
Expand All @@ -12,6 +21,17 @@ function extensions (list) {
}

function configure (env) {
const SLOWDOWN_WINDOW_MS = _.get(env, 'settings.slowdownWindowMs');
const SLOWDOWN_DELAY_AFTER = _.get(env, 'settings.slowdownDelayAfter');
const SLOWDOWN_DELAY_MS = _.get(env, 'settings.slowdownDelayMs');
var speedLimit = slowDown({
windowMS: SLOWDOWN_WINDOW_MS,
delayAfter: SLOWDOWN_DELAY_AFTER,
delayMs: SLOWDOWN_DELAY_MS,
keyGenerator: getRemoteIP,
skipSuccessfulRequests: true
});

return {
sendJSONStatus: wares.sendJSONStatus( ),
bodyParser: wares.bodyParser,
Expand All @@ -28,7 +48,8 @@ function configure (env) {
}),
compression: wares.compression,
extensions: extensions,
obscure_device: wares.obscureDeviceProvenance(env)
obscure_device: wares.obscureDeviceProvenance(env),
speedLimit: speedLimit
};
}

Expand Down
14 changes: 12 additions & 2 deletions lib/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ function init () {
, frameName6: ''
, frameName7: ''
, frameName8: ''
, authFailDelay: 5000
, slowdownWindowMs: 30000
, slowdownDelayAfter: 100
, slowdownDelayMs: 500
, unauthorizedDelayWindowMs: 30000
, unauthorizedDelayAfter: 1
, unauthorizedDelayMs: 5000
, adminNotifiesEnabled: true
, obscured: ''
, obscureDeviceProvenance: ''
Expand Down Expand Up @@ -109,7 +114,12 @@ function init () {
, bgLow: mapNumber
, bgTargetTop: mapNumber
, bgTargetBottom: mapNumber
, authFailDelay: mapNumber
, slowdownWindowMs: mapNumber
, slowdownDelayAfter: mapNumber
, slowdownDelayMs: mapNumber
, unauthorizedDelayWindowMs: mapNumber
, unauthorizedDelayAfter: mapNumber
, unauthorizedDelayMs: mapNumber
, adminNotifiesEnabled: mapTruthy
};

Expand Down
Loading