Skip to content

Commit

Permalink
Merge pull request #5566 from halfdan/refactor-middleware
Browse files Browse the repository at this point in the history
Middleware Refactor
  • Loading branch information
ErisDS committed Aug 9, 2015
2 parents 4d43c84 + a993f80 commit 0f954f3
Show file tree
Hide file tree
Showing 19 changed files with 1,461 additions and 1,001 deletions.
120 changes: 62 additions & 58 deletions core/server/middleware/auth-strategies.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
var passport = require('passport'),
BearerStrategy = require('passport-http-bearer').Strategy,
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
models = require('../models');
var BearerStrategy = require('passport-http-bearer').Strategy,
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
models = require('../models'),
strategies;

/**
* ClientPasswordStrategy
*
* This strategy is used to authenticate registered OAuth clients. It is
* employed to protect the `token` endpoint, which consumers use to obtain
* access tokens. The OAuth 2.0 specification suggests that clients use the
* HTTP Basic scheme to authenticate (not implemented yet).
strategies = {

* Use of the client password strategy is implemented to support ember-simple-auth.
*/
passport.use(new ClientPasswordStrategy(
function strategy(clientId, clientSecret, done) {
models.Client.forge({slug: clientId})
.fetch()
.then(function then(model) {
if (model) {
var client = model.toJSON();
if (client.secret === clientSecret) {
return done(null, client);
/**
* ClientPasswordStrategy
*
* This strategy is used to authenticate registered OAuth clients. It is
* employed to protect the `token` endpoint, which consumers use to obtain
* access tokens. The OAuth 2.0 specification suggests that clients use the
* HTTP Basic scheme to authenticate (not implemented yet).
* Use of the client password strategy is implemented to support ember-simple-auth.
*/
clientPasswordStrategy: new ClientPasswordStrategy(
function strategy(clientId, clientSecret, done) {
models.Client.forge({slug: clientId})
.fetch()
.then(function then(model) {
if (model) {
var client = model.toJSON();
if (client.secret === clientSecret) {
return done(null, client);
}
}
}
return done(null, false);
});
}
));
return done(null, false);
});
}
),

/**
* BearerStrategy
*
* This strategy is used to authenticate users based on an access token (aka a
* bearer token). The user must have previously authorized a client
* application, which is issued an access token to make requests on behalf of
* the authorizing user.
*/
passport.use(new BearerStrategy(
function strategy(accessToken, done) {
models.Accesstoken.forge({token: accessToken})
.fetch()
.then(function then(model) {
if (model) {
var token = model.toJSON();
if (token.expires > Date.now()) {
models.User.forge({id: token.user_id})
.fetch()
.then(function then(model) {
if (model) {
var user = model.toJSON(),
info = {scope: '*'};
return done(null, {id: user.id}, info);
}
/**
* BearerStrategy
*
* This strategy is used to authenticate users based on an access token (aka a
* bearer token). The user must have previously authorized a client
* application, which is issued an access token to make requests on behalf of
* the authorizing user.
*/
bearerStrategy: new BearerStrategy(
function strategy(accessToken, done) {
models.Accesstoken.forge({token: accessToken})
.fetch()
.then(function then(model) {
if (model) {
var token = model.toJSON();
if (token.expires > Date.now()) {
models.User.forge({id: token.user_id})
.fetch()
.then(function then(model) {
if (model) {
var user = model.toJSON(),
info = {scope: '*'};
return done(null, {id: user.id}, info);
}
return done(null, false);
});
} else {
return done(null, false);
});
}
} else {
return done(null, false);
}
} else {
return done(null, false);
}
});
}
));
});
}
)
};

module.exports = strategies;
48 changes: 48 additions & 0 deletions core/server/middleware/authenticate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
var passport = require('passport'),
apiErrorHandlers = require('./api-error-handlers');

// ### Authenticate Middleware
// authentication has to be done for /ghost/* routes with
// exceptions for signin, signout, signup, forgotten, reset only
// api and frontend use different authentication mechanisms atm
function authenticate(req, res, next) {
var path,
subPath;

// SubPath is the url path starting after any default subdirectories
// it is stripped of anything after the two levels `/ghost/.*?/` as the reset link has an argument
path = req.path;
/*jslint regexp:true, unparam:true*/
subPath = path.replace(/^(\/.*?\/.*?\/)(.*)?/, function replace(match, a) {
return a;
});

if (subPath.indexOf('/ghost/api/') === 0
&& (path.indexOf('/ghost/api/v0.1/authentication/') !== 0
|| (path.indexOf('/ghost/api/v0.1/authentication/setup/') === 0 && req.method === 'PUT'))) {
return passport.authenticate('bearer', {session: false, failWithError: true},
function authenticate(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (!user) {
var error = {
code: 401,
errorType: 'NoPermissionError',
message: 'Please Sign In'
};

return apiErrorHandlers.errorHandler(error, req, res, next);
}
// TODO: figure out, why user & authInfo is lost
req.authInfo = info;
req.user = user;
return next(null, user, info);
}
)(req, res, next);
}
next();
}

module.exports = authenticate;
72 changes: 72 additions & 0 deletions core/server/middleware/check-ssl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
var config = require('../config'),
url = require('url');

function isSSLrequired(isAdmin, configUrl, forceAdminSSL) {
var forceSSL = url.parse(configUrl).protocol === 'https:' ? true : false;
if (forceSSL || (isAdmin && forceAdminSSL)) {
return true;
}
return false;
}

// The guts of checkSSL. Indicate forbidden or redirect according to configuration.
// Required args: forceAdminSSL, url and urlSSL should be passed from config. reqURL from req.url
function sslForbiddenOrRedirect(opt) {
var forceAdminSSL = opt.forceAdminSSL,
reqUrl = opt.reqUrl, // expected to be relative-to-root
baseUrl = url.parse(opt.configUrlSSL || opt.configUrl),
response = {
// Check if forceAdminSSL: { redirect: false } is set, which means
// we should just deny non-SSL access rather than redirect
isForbidden: (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect),

// Append the request path to the base configuration path, trimming out a double "//"
redirectPathname: function redirectPathname() {
var pathname = baseUrl.path;
if (reqUrl[0] === '/' && pathname[pathname.length - 1] === '/') {
pathname += reqUrl.slice(1);
} else {
pathname += reqUrl;
}
return pathname;
},
redirectUrl: function redirectUrl(query) {
return url.format({
protocol: 'https:',
hostname: baseUrl.hostname,
port: baseUrl.port,
pathname: this.redirectPathname(),
query: query
});
}
};

return response;
}

// Check to see if we should use SSL
// and redirect if needed
function checkSSL(req, res, next) {
if (isSSLrequired(res.isAdmin, config.url, config.forceAdminSSL)) {
if (!req.secure) {
var response = sslForbiddenOrRedirect({
forceAdminSSL: config.forceAdminSSL,
configUrlSSL: config.urlSSL,
configUrl: config.url,
reqUrl: req.url
});

if (response.isForbidden) {
return res.sendStatus(403);
} else {
return res.redirect(301, response.redirectUrl(req.query));
}
}
}
next();
}

module.exports = checkSSL;
// SSL helper functions are exported primarily for unit testing.
module.exports.isSSLrequired = isSSLrequired;
module.exports.sslForbiddenOrRedirect = sslForbiddenOrRedirect;
Loading

0 comments on commit 0f954f3

Please sign in to comment.