2013-11-26 00:31:18 +04:00
// # Custom Middleware
// The following custom middleware functions are all unit testable, and have accompanying unit tests in
// middleware_spec.js
2013-10-05 14:17:08 +04:00
2014-02-05 12:40:30 +04:00
var _ = require ( 'lodash' ) ,
2013-11-05 09:41:36 +04:00
express = require ( 'express' ) ,
2013-12-04 06:47:39 +04:00
busboy = require ( './ghost-busboy' ) ,
2013-11-20 17:58:52 +04:00
config = require ( '../config' ) ,
2013-11-05 09:41:36 +04:00
path = require ( 'path' ) ,
2013-12-06 12:51:35 +04:00
api = require ( '../api' ) ,
2014-06-30 16:58:10 +04:00
passport = require ( 'passport' ) ,
2014-07-17 16:22:07 +04:00
errors = require ( '../errors' ) ,
2014-07-28 17:19:49 +04:00
utils = require ( '../utils' ) ,
2013-12-31 03:13:25 +04:00
expressServer ,
2014-06-30 16:58:10 +04:00
oauthServer ,
2014-08-05 14:58:58 +04:00
loginSecurity = [ ] ,
forgottenSecurity = [ ] ;
2013-10-05 14:17:08 +04:00
function isBlackListedFileType ( file ) {
2013-10-25 02:55:51 +04:00
var blackListedFileTypes = [ '.hbs' , '.md' , '.json' ] ,
2013-10-05 14:17:08 +04:00
ext = path . extname ( file ) ;
return _ . contains ( blackListedFileTypes , ext ) ;
}
2013-12-06 18:13:15 +04:00
function cacheServer ( server ) {
expressServer = server ;
}
2014-06-30 16:58:10 +04:00
function cacheOauthServer ( server ) {
oauthServer = server ;
}
2013-10-05 14:17:08 +04:00
var middleware = {
2014-02-14 14:00:11 +04:00
// ### Authenticate Middleware
2014-02-25 14:20:32 +04:00
// authentication has to be done for /ghost/* routes with
2014-02-14 14:00:11 +04:00
// exceptions for signin, signout, signup, forgotten, reset only
// api and frontend use different authentication mechanisms atm
authenticate : function ( req , res , next ) {
2014-07-01 03:26:08 +04:00
var path ,
2014-02-25 14:20:32 +04:00
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
2014-07-17 18:33:21 +04:00
path = req . path . substring ( config . paths . subdir . length ) ;
2014-02-25 14:20:32 +04:00
/*jslint regexp:true, unparam:true*/
2014-06-20 13:15:01 +04:00
subPath = path . replace ( /^(\/.*?\/.*?\/)(.*)?/ , function ( match , a ) {
2014-02-25 14:20:32 +04:00
return a ;
} ) ;
2014-02-20 02:53:40 +04:00
if ( res . isAdmin ) {
2014-06-30 16:58:10 +04:00
if ( subPath . indexOf ( '/ghost/api/' ) === 0
2014-07-03 19:06:07 +04:00
&& path . indexOf ( '/ghost/api/v0.1/authentication/' ) !== 0 ) {
2014-06-30 16:58:10 +04:00
return passport . authenticate ( 'bearer' , { session : false , failWithError : true } ,
function ( err , user , info ) {
if ( err ) {
return next ( err ) ; // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if ( ! user ) {
var msg = {
type : 'error' ,
message : 'Please Sign In' ,
status : 'passive'
} ;
res . status ( 401 ) ;
return res . send ( msg ) ;
}
// TODO: figure out, why user & authInfo is lost
req . authInfo = info ;
req . user = user ;
return next ( null , user , info ) ;
}
) ( req , res , next ) ;
2014-02-20 02:53:40 +04:00
}
2014-02-14 14:00:11 +04:00
}
next ( ) ;
} ,
2013-12-31 03:13:25 +04:00
// ### CacheControl Middleware
// provide sensible cache control headers
cacheControl : function ( options ) {
2013-11-05 09:41:36 +04:00
/*jslint unparam:true*/
2013-12-31 03:13:25 +04:00
var profiles = {
'public' : 'public, max-age=0' ,
'private' : 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
} ,
output ;
if ( _ . isString ( options ) && profiles . hasOwnProperty ( options ) ) {
output = profiles [ options ] ;
}
2013-11-05 09:41:36 +04:00
2013-12-31 03:13:25 +04:00
return function cacheControlHeaders ( req , res , next ) {
if ( output ) {
res . set ( { 'Cache-Control' : output } ) ;
}
next ( ) ;
} ;
2013-11-05 09:41:36 +04:00
} ,
// ### whenEnabled Middleware
// Selectively use middleware
// From https://github.com/senchalabs/connect/issues/676#issuecomment-9569658
whenEnabled : function ( setting , fn ) {
return function settingEnabled ( req , res , next ) {
2013-12-06 18:13:15 +04:00
// Set from server/middleware/index.js for now
if ( expressServer . enabled ( setting ) ) {
2013-11-05 09:41:36 +04:00
fn ( req , res , next ) ;
} else {
next ( ) ;
}
} ;
} ,
2013-11-20 17:58:52 +04:00
staticTheme : function ( ) {
2013-10-05 14:17:08 +04:00
return function blackListStatic ( req , res , next ) {
if ( isBlackListedFileType ( req . url ) ) {
return next ( ) ;
}
2013-11-20 17:58:52 +04:00
return middleware . forwardToExpressStatic ( req , res , next ) ;
2013-10-05 14:17:08 +04:00
} ;
} ,
// to allow unit testing
2013-11-20 17:58:52 +04:00
forwardToExpressStatic : function ( req , res , next ) {
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
api . settings . read ( { context : { internal : true } , key : 'activeTheme' } ) . then ( function ( response ) {
2014-04-28 03:28:50 +04:00
var activeTheme = response . settings [ 0 ] ;
2014-05-22 07:56:25 +04:00
2014-07-28 17:19:49 +04:00
express [ 'static' ] ( path . join ( config . paths . themePath , activeTheme . value ) , { maxAge : utils . ONE _YEAR _MS } ) ( req , res , next ) ;
2013-12-06 12:51:35 +04:00
} ) ;
2013-11-26 13:38:54 +04:00
} ,
2014-07-17 16:22:07 +04:00
// ### Spam prevention Middleware
2014-08-05 14:58:58 +04:00
// limit signin requests to ten failed requests per IP per hour
spamSigninPrevention : function ( req , res , next ) {
2014-07-17 16:22:07 +04:00
var currentTime = process . hrtime ( ) [ 0 ] ,
remoteAddress = req . connection . remoteAddress ,
2014-08-01 02:58:32 +04:00
deniedRateLimit = '' ,
ipCount = '' ,
2014-08-05 14:58:58 +04:00
rateSigninPeriod = config . rateSigninPeriod || 3600 ,
rateSigninAttempts = config . rateSigninAttempts || 10 ;
2014-07-17 16:22:07 +04:00
2014-08-05 14:58:58 +04:00
if ( req . body . username ) {
loginSecurity . push ( { ip : remoteAddress , time : currentTime , email : req . body . username } ) ;
} else {
return next ( new errors . BadRequestError ( 'No username.' ) ) ;
}
// filter entries that are older than rateSigninPeriod
2014-07-17 16:22:07 +04:00
loginSecurity = _ . filter ( loginSecurity , function ( logTime ) {
2014-08-05 14:58:58 +04:00
return ( logTime . time + rateSigninPeriod > currentTime ) ;
2014-07-17 16:22:07 +04:00
} ) ;
2014-08-05 14:58:58 +04:00
// check number of tries per IP address
2014-08-01 02:58:32 +04:00
ipCount = _ . chain ( loginSecurity ) . countBy ( 'ip' ) . value ( ) ;
2014-08-05 14:58:58 +04:00
deniedRateLimit = ( ipCount [ remoteAddress ] > rateSigninAttempts ) ;
2014-07-17 16:22:07 +04:00
2014-08-05 14:58:58 +04:00
if ( deniedRateLimit ) {
return next ( new errors . UnauthorizedError ( 'Only ' + rateSigninAttempts + ' tries per IP address every ' + rateSigninPeriod + ' seconds.' ) ) ;
}
next ( ) ;
} ,
// ### Spam prevention Middleware
// limit forgotten password requests to five requests per IP per hour for different email addresses
// limit forgotten password requests to five requests per email address
spamForgottenPrevention : function ( req , res , next ) {
var currentTime = process . hrtime ( ) [ 0 ] ,
remoteAddress = req . connection . remoteAddress ,
rateForgottenPeriod = config . rateForgottenPeriod || 3600 ,
rateForgottenAttempts = config . rateForgottenAttempts || 5 ,
email = req . body . passwordreset [ 0 ] . email ,
ipCount = '' ,
deniedRateLimit = '' ,
deniedEmailRateLimit = '' ,
index = _ . findIndex ( forgottenSecurity , function ( logTime ) {
return ( logTime . ip === remoteAddress && logTime . email === email ) ;
} ) ;
if ( email ) {
if ( index !== - 1 ) {
forgottenSecurity [ index ] . count = forgottenSecurity [ index ] . count + 1 ;
2014-08-01 02:58:32 +04:00
} else {
2014-08-05 14:58:58 +04:00
forgottenSecurity . push ( { ip : remoteAddress , time : currentTime , email : email , count : 0 } ) ;
2014-08-01 02:58:32 +04:00
}
2014-08-05 14:58:58 +04:00
} else {
return next ( new errors . BadRequestError ( 'No email.' ) ) ;
}
// filter entries that are older than rateForgottenPeriod
forgottenSecurity = _ . filter ( forgottenSecurity , function ( logTime ) {
return ( logTime . time + rateForgottenPeriod > currentTime ) ;
} ) ;
// check number of tries with different email addresses per IP
ipCount = _ . chain ( forgottenSecurity ) . countBy ( 'ip' ) . value ( ) ;
deniedRateLimit = ( ipCount [ remoteAddress ] > rateForgottenAttempts ) ;
if ( index !== - 1 ) {
deniedEmailRateLimit = ( forgottenSecurity [ index ] . count > rateForgottenAttempts ) ;
}
if ( deniedEmailRateLimit ) {
return next ( new errors . UnauthorizedError ( 'Only ' + rateForgottenAttempts + ' forgotten password attempts per email every ' + rateForgottenPeriod + ' seconds.' ) ) ;
}
if ( deniedRateLimit ) {
return next ( new errors . UnauthorizedError ( 'Only ' + rateForgottenAttempts + ' tries per IP address every ' + rateForgottenPeriod + ' seconds.' ) ) ;
2014-07-17 16:22:07 +04:00
}
2014-08-05 14:58:58 +04:00
next ( ) ;
} ,
resetSpamCounter : function ( email ) {
loginSecurity = _ . filter ( loginSecurity , function ( logTime ) {
return ( logTime . email !== email ) ;
} ) ;
2014-07-17 16:22:07 +04:00
} ,
2014-06-30 16:58:10 +04:00
// work around to handle missing client_secret
// oauth2orize needs it, but untrusted clients don't have it
addClientSecret : function ( req , res , next ) {
if ( ! req . body . client _secret ) {
req . body . client _secret = 'not_available' ;
}
next ( ) ;
} ,
2014-07-17 16:22:07 +04:00
// ### Authenticate Client Middleware
// authenticate client that is asking for an access token
2014-06-30 16:58:10 +04:00
authenticateClient : function ( req , res , next ) {
return passport . authenticate ( [ 'oauth2-client-password' ] , { session : false } ) ( req , res , next ) ;
} ,
2014-07-17 16:22:07 +04:00
// ### Generate access token Middleware
// register the oauth2orize middleware for password and refresh token grants
2014-06-30 16:58:10 +04:00
generateAccessToken : function ( req , res , next ) {
return oauthServer . token ( ) ( req , res , next ) ;
} ,
2013-12-04 06:47:39 +04:00
busboy : busboy
2013-10-05 14:17:08 +04:00
} ;
module . exports = middleware ;
2014-05-22 07:56:25 +04:00
module . exports . cacheServer = cacheServer ;
2014-06-30 16:58:10 +04:00
module . exports . cacheOauthServer = cacheOauthServer ;