2016-08-26 18:50:37 +03:00
'use strict' ;
2019-12-16 08:44:55 -05:00
const nconf = require ( 'nconf' ) ;
2020-10-01 10:52:05 -04:00
const winston = require ( 'winston' ) ;
const passport = require ( 'passport' ) ;
2020-10-01 16:22:19 -04:00
const util = require ( 'util' ) ;
2016-11-15 12:45:00 +03:00
2019-12-16 08:44:55 -05:00
const meta = require ( '../meta' ) ;
const user = require ( '../user' ) ;
const privileges = require ( '../privileges' ) ;
const plugins = require ( '../plugins' ) ;
2020-06-03 20:18:42 -04:00
const helpers = require ( './helpers' ) ;
2019-12-16 08:44:55 -05:00
const auth = require ( '../routes/authentication' ) ;
2018-09-04 15:43:33 +02:00
2019-12-16 08:44:55 -05:00
const controllers = {
2017-02-17 19:31:21 -07:00
helpers : require ( '../controllers/helpers' ) ,
2020-10-01 10:52:05 -04:00
authentication : require ( '../controllers/authentication' ) ,
2016-08-26 18:50:37 +03:00
} ;
2020-10-01 13:30:00 -04:00
const passportAuthenticateAsync = function ( req , res ) {
return new Promise ( ( resolve , reject ) => {
2020-10-29 12:51:36 -04:00
passport . authenticate ( 'core.api' , { session : false } , ( err , user ) => {
2020-10-01 13:30:00 -04:00
if ( err ) {
reject ( err ) ;
} else {
resolve ( user ) ;
}
} ) ( req , res ) ;
} ) ;
} ;
2016-10-13 11:43:39 +02:00
module . exports = function ( middleware ) {
2020-06-03 20:18:42 -04:00
async function authenticate ( req , res ) {
2020-10-01 16:22:19 -04:00
const loginAsync = util . promisify ( req . login ) . bind ( req ) ;
2018-01-31 15:20:17 -05:00
if ( req . loggedIn ) {
2020-12-07 15:44:34 -05:00
// If authenticated via cookie (express-session), protect routes with CSRF checking
2020-10-13 16:58:44 -04:00
if ( res . locals . isAPI ) {
await middleware . applyCSRFasync ( req , res ) ;
}
2020-06-03 20:18:42 -04:00
return true ;
2020-10-01 10:52:05 -04:00
} else if ( req . headers . hasOwnProperty ( 'authorization' ) ) {
2020-10-01 13:30:00 -04:00
const user = await passportAuthenticateAsync ( req , res ) ;
if ( ! user ) { return true ; }
// If the token received was a master token, a _uid must also be present for all calls
if ( user . hasOwnProperty ( 'uid' ) ) {
2020-10-01 16:22:19 -04:00
await loginAsync ( user ) ;
await controllers . authentication . onSuccessfulLogin ( req , user . uid ) ;
req . uid = user . uid ;
req . loggedIn = req . uid > 0 ;
return true ;
2020-10-01 13:30:00 -04:00
} else if ( user . hasOwnProperty ( 'master' ) && user . master === true ) {
if ( req . body . hasOwnProperty ( '_uid' ) || req . query . hasOwnProperty ( '_uid' ) ) {
user . uid = req . body . _uid || req . query . _uid ;
delete user . master ;
2020-10-01 10:52:05 -04:00
2020-10-01 16:22:19 -04:00
await loginAsync ( user ) ;
await controllers . authentication . onSuccessfulLogin ( req , user . uid ) ;
req . uid = user . uid ;
req . loggedIn = req . uid > 0 ;
return true ;
2020-10-01 10:52:05 -04:00
}
2020-10-01 16:22:19 -04:00
throw new Error ( 'A master token was received without a corresponding `_uid` in the request body' ) ;
2020-10-01 13:30:00 -04:00
} else {
winston . warn ( '[api/authenticate] Unable to find user after verifying token' ) ;
return true ;
}
2017-05-12 17:53:23 -04:00
}
2020-04-24 11:49:56 -04:00
2020-11-20 16:06:26 -05:00
await plugins . hooks . fire ( 'response:middleware.authenticate' , {
2020-04-24 11:49:56 -04:00
req : req ,
res : res ,
next : function ( ) { } , // no-op for backwards compatibility
} ) ;
if ( ! res . headersSent ) {
2020-06-03 20:18:42 -04:00
auth . setAuthVars ( req ) ;
2017-05-12 17:53:23 -04:00
}
2020-06-03 20:18:42 -04:00
return ! res . headersSent ;
2018-12-07 13:31:31 -05:00
}
2021-02-04 00:01:39 -07:00
middleware . authenticate = helpers . try ( async ( req , res , next ) => {
2021-03-08 14:03:22 -05:00
winston . warn ( ` [middleware] middleware.authenticate has been deprecated, page and API routes are now automatically authenticated via setup(Page|API)Route. Use middleware.authenticateRequest (if not using route helper) and middleware.ensureLoggedIn instead. (request path: ${ req . path } ) ` ) ;
2020-06-03 20:18:42 -04:00
if ( ! await authenticate ( req , res ) ) {
return ;
}
if ( ! req . loggedIn ) {
return controllers . helpers . notAllowed ( req , res ) ;
}
next ( ) ;
} ) ;
2019-12-16 08:44:55 -05:00
2021-03-08 14:03:22 -05:00
middleware . authenticateRequest = helpers . try ( async ( req , res , next ) => {
2020-06-03 20:18:42 -04:00
if ( ! await authenticate ( req , res ) ) {
return ;
}
next ( ) ;
} ) ;
2017-05-12 17:53:23 -04:00
2021-03-08 14:52:49 -05:00
// TODO: Remove in v1.18.0
middleware . authenticateOrGuest = ( req , res , next ) => {
winston . warn ( ` [middleware] middleware.authenticateOrGuest has been renamed, use middleware.authenticateRequest instead. (request path: ${ req . path } ) ` ) ;
middleware . authenticateRequest ( req , res , next ) ;
} ;
2021-02-04 00:01:39 -07:00
middleware . ensureSelfOrGlobalPrivilege = helpers . try ( async ( req , res , next ) => {
2020-06-03 20:18:42 -04:00
await ensureSelfOrMethod ( user . isAdminOrGlobalMod , req , res , next ) ;
} ) ;
2017-05-12 17:53:23 -04:00
2021-02-04 00:01:39 -07:00
middleware . ensureSelfOrPrivileged = helpers . try ( async ( req , res , next ) => {
2020-06-03 20:18:42 -04:00
await ensureSelfOrMethod ( user . isPrivileged , req , res , next ) ;
} ) ;
2017-05-12 17:53:23 -04:00
2019-12-16 08:44:55 -05:00
async function ensureSelfOrMethod ( method , req , res , next ) {
2017-05-12 17:53:23 -04:00
/ *
The "self" part of this middleware hinges on you having used
middleware . exposeUid prior to invoking this middleware .
* /
2019-12-16 08:44:55 -05:00
if ( ! req . loggedIn ) {
return controllers . helpers . notAllowed ( req , res ) ;
}
if ( req . uid === parseInt ( res . locals . uid , 10 ) ) {
2020-06-03 20:18:42 -04:00
return next ( ) ;
2019-12-16 08:44:55 -05:00
}
const allowed = await method ( req . uid ) ;
if ( ! allowed ) {
return controllers . helpers . notAllowed ( req , res ) ;
}
2020-02-26 12:26:52 -05:00
return next ( ) ;
2017-05-12 17:53:23 -04:00
}
2021-02-04 00:01:39 -07:00
middleware . canViewUsers = helpers . try ( async ( req , res , next ) => {
2019-03-26 12:24:28 -04:00
if ( parseInt ( res . locals . uid , 10 ) === req . uid ) {
return next ( ) ;
}
2019-12-16 08:44:55 -05:00
const canView = await privileges . global . can ( 'view:users' , req . uid ) ;
if ( canView ) {
return next ( ) ;
}
controllers . helpers . notAllowed ( req , res ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2021-02-04 00:01:39 -07:00
middleware . canViewGroups = helpers . try ( async ( req , res , next ) => {
2019-12-16 08:44:55 -05:00
const canView = await privileges . global . can ( 'view:groups' , req . uid ) ;
if ( canView ) {
return next ( ) ;
}
controllers . helpers . notAllowed ( req , res ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2021-02-04 00:01:39 -07:00
middleware . checkAccountPermissions = helpers . try ( async ( req , res , next ) => {
2016-08-26 18:50:37 +03:00
// This middleware ensures that only the requested user and admins can pass
2021-03-08 14:47:33 -05:00
// This check if left behind for legacy purposes. Older plugins may call this middleware without ensureLoggedIn
2020-06-03 20:18:42 -04:00
if ( ! req . loggedIn ) {
return controllers . helpers . notAllowed ( req , res ) ;
}
2021-03-08 14:47:33 -05:00
2019-12-16 08:44:55 -05:00
const uid = await user . getUidByUserslug ( req . params . userslug ) ;
let allowed = await privileges . users . canEdit ( req . uid , uid ) ;
if ( allowed ) {
return next ( ) ;
}
if ( /user\/.+\/info$/ . test ( req . path ) ) {
allowed = await privileges . global . can ( 'view:users:info' , req . uid ) ;
}
if ( allowed ) {
return next ( ) ;
}
controllers . helpers . notAllowed ( req , res ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2021-02-04 00:01:39 -07:00
middleware . redirectToAccountIfLoggedIn = helpers . try ( async ( req , res , next ) => {
2018-12-17 16:03:01 -05:00
if ( req . session . forceLogin || req . uid <= 0 ) {
2016-08-26 18:50:37 +03:00
return next ( ) ;
}
2019-12-16 08:44:55 -05:00
const userslug = await user . getUserField ( req . uid , 'userslug' ) ;
2021-02-03 23:59:08 -07:00
controllers . helpers . redirect ( res , ` /user/ ${ userslug } ` ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2021-02-04 00:01:39 -07:00
middleware . redirectUidToUserslug = helpers . try ( async ( req , res , next ) => {
2019-12-16 08:44:55 -05:00
const uid = parseInt ( req . params . uid , 10 ) ;
2018-11-17 22:31:39 -05:00
if ( uid <= 0 ) {
2016-08-26 18:50:37 +03:00
return next ( ) ;
}
2019-12-16 08:44:55 -05:00
const userslug = await user . getUserField ( uid , 'userslug' ) ;
if ( ! userslug ) {
return next ( ) ;
}
const path = req . path . replace ( /^\/api/ , '' )
. replace ( 'uid' , 'user' )
2021-02-04 00:01:39 -07:00
. replace ( uid , ( ) => userslug ) ;
2019-12-16 08:44:55 -05:00
controllers . helpers . redirect ( res , path ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2021-02-04 00:01:39 -07:00
middleware . redirectMeToUserslug = helpers . try ( async ( req , res ) => {
2019-12-16 08:44:55 -05:00
const userslug = await user . getUserField ( req . uid , 'userslug' ) ;
if ( ! userslug ) {
return controllers . helpers . notAllowed ( req , res ) ;
}
2021-02-03 23:59:08 -07:00
const path = req . path . replace ( /^(\/api)?\/me/ , ` /user/ ${ userslug } ` ) ;
2019-12-16 08:44:55 -05:00
controllers . helpers . redirect ( res , path ) ;
2020-06-03 20:18:42 -04:00
} ) ;
2017-11-16 15:38:26 -07:00
2021-02-04 00:01:39 -07:00
middleware . isAdmin = helpers . try ( async ( req , res , next ) => {
2020-10-30 12:30:58 -04:00
// TODO: Remove in v1.16.0
winston . warn ( '[middleware] middleware.isAdmin deprecated, use middleware.admin.checkPrivileges instead' ) ;
2019-11-15 14:24:24 -05:00
const isAdmin = await user . isAdministrator ( req . uid ) ;
2020-10-29 07:56:28 -04:00
2019-11-15 14:24:24 -05:00
if ( ! isAdmin ) {
2019-11-15 14:27:45 -05:00
return controllers . helpers . notAllowed ( req , res ) ;
2019-11-15 14:24:24 -05:00
}
const hasPassword = await user . hasPassword ( req . uid ) ;
if ( ! hasPassword ) {
2019-11-15 14:27:45 -05:00
return next ( ) ;
2019-11-15 14:24:24 -05:00
}
2016-08-26 18:50:37 +03:00
2019-11-15 14:24:24 -05:00
const loginTime = req . session . meta ? req . session . meta . datetime : 0 ;
const adminReloginDuration = meta . config . adminReloginDuration * 60000 ;
const disabled = meta . config . adminReloginDuration === 0 ;
if ( disabled || ( loginTime && parseInt ( loginTime , 10 ) > Date . now ( ) - adminReloginDuration ) ) {
const timeLeft = parseInt ( loginTime , 10 ) - ( Date . now ( ) - adminReloginDuration ) ;
if ( req . session . meta && timeLeft < Math . min ( 300000 , adminReloginDuration ) ) {
req . session . meta . datetime += Math . min ( 300000 , adminReloginDuration ) ;
}
2016-08-26 18:50:37 +03:00
2019-11-15 14:27:45 -05:00
return next ( ) ;
2019-11-15 14:24:24 -05:00
}
2016-08-26 18:50:37 +03:00
2019-11-15 14:24:24 -05:00
let returnTo = req . path ;
if ( nconf . get ( 'relative_path' ) ) {
2021-02-03 23:59:08 -07:00
returnTo = req . path . replace ( new RegExp ( ` ^ ${ nconf . get ( 'relative_path' ) } ` ) , '' ) ;
2019-11-15 14:24:24 -05:00
}
returnTo = returnTo . replace ( /^\/api/ , '' ) ;
2017-08-01 13:17:03 -04:00
2019-11-15 14:24:24 -05:00
req . session . returnTo = returnTo ;
req . session . forceLogin = 1 ;
if ( res . locals . isAPI ) {
2020-10-01 10:52:05 -04:00
controllers . helpers . formatApiResponse ( 401 , res ) ;
2019-11-15 14:24:24 -05:00
} else {
2021-02-03 23:59:08 -07:00
res . redirect ( ` ${ nconf . get ( 'relative_path' ) } /login?local=1 ` ) ;
2019-11-15 14:24:24 -05:00
}
2020-06-03 20:18:42 -04:00
} ) ;
2016-08-26 18:50:37 +03:00
2016-10-13 11:43:39 +02:00
middleware . requireUser = function ( req , res , next ) {
2018-01-31 15:20:17 -05:00
if ( req . loggedIn ) {
2016-08-26 18:50:37 +03:00
return next ( ) ;
}
2017-02-18 12:30:49 -07:00
res . status ( 403 ) . render ( '403' , { title : '[[global:403.title]]' } ) ;
2016-08-26 18:50:37 +03:00
} ;
2021-01-25 16:01:10 -05:00
middleware . registrationComplete = async function registrationComplete ( req , res , next ) {
2016-08-26 18:50:37 +03:00
// If the user's session contains registration data, redirect the user to complete registration
if ( ! req . session . hasOwnProperty ( 'registration' ) ) {
2018-11-12 00:20:44 -05:00
return setImmediate ( next ) ;
2017-02-18 14:27:26 -07:00
}
2021-01-25 16:01:10 -05:00
const path = req . path . startsWith ( '/api/' ) ? req . path . replace ( '/api' , '' ) : req . path ;
const { allowed } = await plugins . hooks . fire ( 'filter:middleware.registrationComplete' , {
allowed : [ '/register/complete' ] ,
} ) ;
if ( ! allowed . includes ( path ) ) {
2018-05-02 13:02:07 -04:00
// Append user data if present
req . session . registration . uid = req . uid ;
2017-02-18 14:27:26 -07:00
controllers . helpers . redirect ( res , '/register/complete' ) ;
2016-08-26 18:50:37 +03:00
} else {
2018-12-17 16:03:01 -05:00
setImmediate ( next ) ;
2016-08-26 18:50:37 +03:00
}
} ;
} ;