New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

heroku-bouncer

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

heroku-bouncer - npm Package Compare versions

Comparing version 2.1.0 to 3.0.0

.travis.yml

12

index.js

@@ -21,5 +21,10 @@ 'use strict';

* @param {String} options.herokuOAuthSecret a secret for a Heroku OAuth client
* @param {Array} [options.ignoreRoutes=[]] an array of route regular expressions
* to match request routes again. If a request route matches one of these, it
* passes through the authentication stack instantly.
* @param {String} [options.herokaiOnlyRedirect='https://www.heroku.com'] a URL
* to redirect to when a user is not a Herokai and `herokaiOnly` is `true`
* @param {String} [options.sessionSyncNonce] if present, determines the name of
* a cookie shared across properties under the same domain in order to keep
* their sessions synchronized
* @param {Array} [options.ignoreRoutes=[]] an array of route regular
* expressions to match request routes again. If a request route matches one
* of these, it passes through the authentication stack instantly.
* @param {String} [options.herokuAuthURL='https://id.heroku.com'] the

@@ -69,3 +74,4 @@ * authentication URL used

options.herokuAuthURL = options.herokuAuthURL || 'https://id.heroku.com';
options.herokaiOnlyRedirect = options.herokaiOnlyRedirect || 'https://www.heroku.com';
options.herokaiOnly = options.herokaiOnly || false;
}
'use strict';
var encryptor = require('encryptor');
var request = require('request');

@@ -26,2 +27,3 @@ /**

return function(req, res, next) {
var currentSession = getCurrentSession(req, options.sessionSyncNonce);
var i, route;

@@ -32,3 +34,3 @@

if (!req.session.userSession && req.url.match(route)) {
if (!currentSession && req.url.match(route)) {
return next();

@@ -38,16 +40,12 @@ }

if (req.session.userSession || isOAuthPath(req.path)) {
if (req.session.userSession) {
var userSession = JSON.parse(cipher.decrypt(req.session.userSession));
var token = userSession.accessToken;
if (currentSession || isOAuthPath(req.path)) {
if (currentSession) {
var userSession = JSON.parse(cipher.decrypt(currentSession));
var isHerokai = /@heroku\.com$/.test(userSession.user.email);
var isJSON = /json/.test(req.get('content-type'));
if (options.herokaiOnly === true && !isHerokai) {
if (isJSON || req.method !== 'GET') {
res.statusCode = 401;
return res.json({ id: 'unauthorized', message: 'This app is limited to Herokai only.' });
} else {
return res.redirect('https://www.heroku.com');
}
return reauthenticate(req, res, {
redirectTo: options.herokaiOnlyRedirect,
message : 'This app is limited to Herokai only.'
});
} else if (typeof options.herokaiOnly === 'function' && !isHerokai) {

@@ -57,18 +55,99 @@ return options.herokaiOnly(req, res, next);

req['heroku-bouncer'] = {
token: token,
email: userSession.user.email,
name : userSession.user.name,
id : userSession.user.id
};
ensureValidToken(userSession, function(err) {
if (err) {
return reauthenticate(req, res);
}
req.session.userSession = cipher.encrypt(JSON.stringify(userSession));
req['heroku-bouncer'] = {
token: userSession.accessToken,
email: userSession.user.email,
name : userSession.user.name,
id : userSession.user.id
};
next();
});
} else {
next();
}
} else {
reauthenticate(req, res);
}
};
next();
function ensureValidToken(userSession, cb) {
var then = new Date(userSession.createdAt);
var now = new Date();
var remaining = (now - then) / 1000; // Remaining until token expires.
remaining += 600; // Add 10 minutes
if (remaining > userSession.expiresIn) {
request.post({
url : options.herokuAuthURL + '/oauth/token',
json: true,
form: {
grant_type : 'refresh_token',
refresh_token: userSession.refreshToken,
client_secret: options.herokuOAuthSecret
}
}, function(err, res, body) {
if (err) {
return cb(err);
}
if (res.statusCode === 200) {
userSession.accessToken = body.access_token;
userSession.refreshToken = body.refresh_token;
userSession.createdAt = (new Date()).toISOString();
userSession.expiresIn = body.expires_in;
cb();
} else {
cb(new Error('Expected 200 from Heroku Identity, got ' + res.statusCode));
}
});
} else {
req.session.redirectPath = req.url;
res.redirect('/auth/heroku');
cb();
}
};
}
function reauthenticate(req, res, options) {
var isJSON = /json/.test(req.get('accept'));
options = options || {};
var redirectTo = options.redirectTo || '/auth/heroku';
var message = options.message || 'Please authenticate.';
req.session.reset();
if (req.method.toLowerCase() === 'get' && !isJSON) {
if (redirectTo === '/auth/heroku') {
req.session.redirectPath = req.url;
}
res.redirect(redirectTo);
} else {
res.statusCode = 401;
res.json({ id: 'unauthorized', message: message });
}
}
};
function getCurrentSession(req, checkNonce) {
var session = req.session.userSession;
if (checkNonce) {
return nonceMatch(req, checkNonce) ? session : null;
} else {
return session;
}
}
function nonceMatch(req, checkNonce) {
return req.session.herokuBouncerSessionNonce === req.cookies[checkNonce];
}
function isOAuthPath(path) {

@@ -75,0 +154,0 @@ return [

@@ -11,3 +11,2 @@ 'use strict';

var heroku = require('heroku-client');
var router = new express.Router();

@@ -26,2 +25,3 @@ /**

var oauth = getOAuth();
var router = new express.Router();

@@ -33,3 +33,3 @@ router.get('/auth/heroku', function(req, res) {

router.get('/auth/heroku/callback', function(req, res) {
oauth.getOAuthAccessToken(req.query.code, null, function(err, accessToken) {
oauth.getOAuthAccessToken(req.query.code, null, function(err, accessToken, refreshToken, results) {
if (err) throw err;

@@ -46,3 +46,6 @@

var userSession = JSON.stringify({
accessToken: accessToken,
accessToken : accessToken,
refreshToken: refreshToken,
createdAt : (new Date()).toISOString(),
expiresIn : results.expires_in,

@@ -58,2 +61,7 @@ user: {

if (options.sessionSyncNonce) {
var nonceName = options.sessionSyncNonce;
req.session.herokuBouncerSessionNonce = req.cookies[nonceName];
}
req.session.userSession = cipher.encrypt(userSession);

@@ -74,3 +82,3 @@

router.get('/auth/heroku/logout', function(req, res) {
req.session = {};
req.session.reset();
res.redirect(options.herokuAuthURL + '/logout');

@@ -77,0 +85,0 @@ });

{
"name": "heroku-bouncer",
"version": "2.1.0",
"version": "3.0.0",
"description": "heroku bouncer middleware for express",

@@ -23,3 +23,5 @@ "main": "index.js",

"express": "~4.1.1",
"heroku-client": "^1.2.0"
"heroku-client": "^1.2.0",
"bluebird": "^2.2.2",
"request": "^2.37.0"
},

@@ -30,32 +32,35 @@ "devDependencies": {

"mocha": "~1.18.2",
"request": "~2.34.0",
"should": "~3.2.0-beta1"
"should": "~3.2.0-beta1",
"tough-cookie": "^0.12.1",
"bluebird": "^2.2.2",
"body-parser": "^1.4.3",
"node-uuid": "^1.4.1"
},
"jshintConfig": {
"eqeqeq" : true,
"forin" : true,
"eqeqeq": true,
"forin": true,
"globalstrict": true,
"immed" : true,
"indent" : 2,
"latedef" : "nofunc",
"newcap" : true,
"noarg" : true,
"quotmark" : "single",
"strict" : true,
"trailing" : true,
"undef" : true,
"unused" : true,
"globals" : {
"afterEach" : false,
"before" : false,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"quotmark": "single",
"strict": true,
"trailing": true,
"undef": true,
"unused": true,
"globals": {
"afterEach": false,
"before": false,
"beforeEach": false,
"context" : false,
"describe" : false,
"exports" : false,
"it" : false,
"module" : false,
"process" : false,
"require" : false
"context": false,
"describe": false,
"exports": false,
"it": false,
"module": false,
"process": false,
"require": false
}
}
}

@@ -1,2 +0,2 @@

# node-heroku-bouncer
# node-heroku-bouncer [![Build Status](https://travis-ci.org/jclem/node-heroku-bouncer.svg?branch=master)](https://travis-ci.org/jclem/node-heroku-bouncer)

@@ -14,6 +14,6 @@ node-heroku-bouncer is an easy-to-use module for adding Heroku OAuth

node-heroku-bouncer assumes you've already added the express
[cookieParser][cookieParser] and [cookieSession][cookieSession] middlewares to
your app. To set it up, pass it your OAuth client ID and secret and another
secret used to encrypt your user's OAuth session data.
node-heroku-bouncer assumes you've already added [cookie-parser][cookieParser]
and [client-sessions][clientSessions] middlewares to your app. To set it up,
pass it your OAuth client ID and secret and another secret used to encrypt your
user's OAuth session data.

@@ -28,4 +28,4 @@ Use the `bouncer.middleware` object to set up middleware that will ensure that

app.use(express.cookieParser('your cookie secret'));
app.use(express.cookieSession({
app.use(require('cookie-parser')('your cookie secret'));
app.use(require('client-sessions')({
secret: 'your session secret',

@@ -50,8 +50,33 @@ cookie: {

app.get('/', function(req, res) {
res.end('you must be logged in!');
res.end('you are clearly logged in!');
});
```
After requests pass through `bouncer.middleware`, they'll have a
`heroku-bouncer` property on them:
```javascript
{
token: 'user-api-token',
id : 'user-id',
name : 'user-name',
email: 'user-email'
}
```
To log a user out, send them to `/auth/heroku/logout`.
### Options
| Options | Required? | Default | Description |
|---------|-----------|---------|-------------|
| `herokuOAuthID` | Yes | n/a | The ID of your Heroku OAuth client |
| `herokuOAuthSecret` | Yes | n/a | The secret of your Heroku OAuth client |
| `herokuBouncerSecret` | Yes | n/a | A random string used to encrypt your user session data |
| `sessionSyncNonce` | No | `null` | The name of a nonce cookie to validate sessions against |
| `ignoreRoutes` | No | `[]` | An array of regular expressions to match routes to be ignored |
| `herokuAuthURL` | No | `"https://id.heroku.com"` | The location of the Heroku OAuth server |
| `herokaiOnly` | No | `false` | Whether or not to restrict the app to Herokai only |
| `herokaiOnlyRedirect` | No | `"https://www.heroku.com"` | Where to redirect non-Herokai to when using `herokaiOnly` |
## Test

@@ -63,3 +88,3 @@

[cookieParser]: http://expressjs.com/3x/api.html#cookieParser
[cookieSession]: http://expressjs.com/3x/api.html#cookieSession
[cookieParser]: https://github.com/expressjs/cookie-parser
[clientSessions]: https://github.com/mozilla/node-client-sessions
'use strict';
var Promise = require('bluebird');
var request = require('request');
var get = Promise.promisify(request.get);
exports.shouldNotRedirect = function(client, done) {
exports.shouldNotRedirect = function() {
var jar = request.jar();
request({
return get({
jar: jar,
url: 'http://localhost:' + client.address().port
}, function(err) {
if (err) throw err;
request({
jar: jar,
url: 'http://localhost:' + client.address().port + '/hello',
url: this.url
}).then(function() {
return get({
jar : jar,
url : this.url + '/hello',
followRedirect: false
}, function(err, res) {
if (err) throw err;
res.body.should.eql('hello world');
done();
});
}.bind(this)).spread(function(res, body) {
body.should.eql('hello world');
});
};
exports.shouldRedirect = function(client, done) {
exports.shouldRedirect = function() {
var jar = request.jar();
request({
return get({
jar: jar,
url: 'http://localhost:' + client.address().port
}, function(err) {
if (err) throw err;
request({
jar: jar,
url: 'http://localhost:' + client.address().port + '/helloo',
url: this.url
}).then(function() {
return get({
jar : jar,
url : this.url + '/hello',
followRedirect: false
}, function(err, res) {
if (err) throw err;
res.headers.location.should.eql('https://www.heroku.com');
done();
});
}.bind(this)).spread(function(res) {
res.headers.location.should.eql('https://www.heroku.com');
});
};

@@ -0,1 +1,3 @@

// jshint -W068
'use strict';

@@ -5,19 +7,15 @@

it('throws an error when not given a `herokuBouncerSecret`', function() {
function requireIndex() {
(function() {
require('../index')();
}
requireIndex.should.throw('No `herokuBouncerSecret` provided to heroku-bouncer');
}).should.throw('No `herokuBouncerSecret` provided to heroku-bouncer');
});
it('throws an error when not given a `herokuOAuthID`', function() {
function requireIndex() {
(function() {
require('../index')({ herokuBouncerSecret: 'foo' });
}
requireIndex.should.throw('No `herokuOAuthID` provided to heroku-bouncer');
}).should.throw('No `herokuOAuthID` provided to heroku-bouncer');
});
it('throws an error when not given a `herokuOAuthSecret`', function() {
function requireIndex() {
(function() {
require('../index')({

@@ -27,6 +25,4 @@ herokuBouncerSecret: 'foo',

});
}
requireIndex.should.throw('No `herokuOAuthSecret` provided to heroku-bouncer');
}).should.throw('No `herokuOAuthSecret` provided to heroku-bouncer');
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc