2018-04-26 15:54:53 -07:00
'use strict' ;
exports = module . exports = {
2021-01-06 21:57:23 -08:00
passwordAuth ,
tokenAuth ,
2018-08-03 17:21:24 -07:00
2021-01-06 21:57:23 -08:00
authorize ,
websocketAuth
2018-04-26 15:54:53 -07:00
} ;
2021-06-04 09:28:40 -07:00
const accesscontrol = require ( '../accesscontrol.js' ) ,
2018-04-26 15:54:53 -07:00
assert = require ( 'assert' ) ,
2019-10-22 21:16:00 -07:00
BoxError = require ( '../boxerror.js' ) ,
2019-11-20 19:48:40 +01:00
externalLdap = require ( '../externalldap.js' ) ,
2018-04-26 15:54:53 -07:00
HttpError = require ( 'connect-lastmile' ) . HttpError ,
2021-06-04 09:28:40 -07:00
safe = require ( 'safetydance' ) ,
speakeasy = require ( 'speakeasy' ) ,
users = require ( '../users.js' ) ;
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
async function passwordAuth ( req , res , next ) {
2020-02-06 14:50:12 +01:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if ( ! req . body . username || typeof req . body . username !== 'string' ) return next ( new HttpError ( 400 , 'A username must be non-empty string' ) ) ;
if ( ! req . body . password || typeof req . body . password !== 'string' ) return next ( new HttpError ( 400 , 'A password must be non-empty string' ) ) ;
2020-12-20 14:41:16 -08:00
if ( 'totpToken' in req . body && typeof req . body . totpToken !== 'string' ) return next ( new HttpError ( 400 , 'totpToken must be a string' ) ) ;
2020-02-06 14:50:12 +01:00
2020-12-20 14:41:16 -08:00
const { username , password , totpToken } = req . body ;
2020-02-06 14:50:12 +01:00
2021-07-15 09:50:11 -07:00
const verifyFunc = username . indexOf ( '@' ) === - 1 ? users . verifyWithUsername : users . verifyWithEmail ;
2020-02-06 15:36:14 +01:00
2021-07-15 09:50:11 -07:00
let [ error , user ] = await safe ( verifyFunc ( username , password , users . AP _WEBADMIN ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) {
[ error , user ] = await safe ( externalLdap . maybeCreateUser ( username . toLowerCase ( ) , password ) ) ;
if ( error ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
[ error ] = await safe ( externalLdap . verifyPassword ( user ) ) ;
if ( error ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
2020-02-06 15:36:14 +01:00
}
2021-07-15 09:50:11 -07:00
if ( error && error . reason === BoxError . INVALID _CREDENTIALS ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
if ( ! user ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
2020-02-06 15:36:14 +01:00
2021-07-15 09:50:11 -07:00
if ( ! user . ghost && ! user . appPassword && user . twoFactorAuthenticationEnabled ) {
if ( ! totpToken ) return next ( new HttpError ( 401 , 'A totpToken must be provided' ) ) ;
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
const verified = speakeasy . totp . verify ( { secret : user . twoFactorAuthenticationSecret , encoding : 'base32' , token : totpToken , window : 2 } ) ;
if ( ! verified ) return next ( new HttpError ( 401 , 'Invalid totpToken' ) ) ;
2020-02-06 14:50:12 +01:00
}
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
req . user = user ;
next ( ) ;
2018-06-15 17:31:28 -07:00
}
2021-06-04 09:28:40 -07:00
async function tokenAuth ( req , res , next ) {
let token ;
2020-02-06 14:50:12 +01:00
// this determines the priority
if ( req . body && req . body . access _token ) token = req . body . access _token ;
if ( req . query && req . query . access _token ) token = req . query . access _token ;
if ( req . headers && req . headers . authorization ) {
2021-06-04 09:28:40 -07:00
const parts = req . headers . authorization . split ( ' ' ) ;
2020-02-06 14:50:12 +01:00
if ( parts . length == 2 ) {
2021-06-04 09:28:40 -07:00
const [ scheme , credentials ] = parts ;
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if ( /^Bearer$/i . test ( scheme ) ) token = credentials ;
}
}
2021-06-05 21:26:43 -07:00
if ( ! token ) return next ( new HttpError ( 401 , 'Token required' ) ) ;
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
const [ error , user ] = await safe ( accesscontrol . verifyToken ( token ) ) ;
2021-06-05 21:26:43 -07:00
if ( error && error . reason === BoxError . INVALID _CREDENTIALS ) return next ( new HttpError ( 401 , error . message ) ) ;
2021-06-04 09:28:40 -07:00
if ( error ) return next ( new HttpError ( 500 , error . message ) ) ;
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
req . access _token = token ; // used in logout route
req . user = user ;
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
next ( ) ;
2018-06-15 17:31:28 -07:00
}
2020-02-21 12:17:06 -08:00
function authorize ( requiredRole ) {
assert . strictEqual ( typeof requiredRole , 'string' ) ;
2018-04-26 15:54:53 -07:00
2020-02-06 14:50:12 +01:00
return function ( req , res , next ) {
2020-02-06 17:29:45 +01:00
assert . strictEqual ( typeof req . user , 'object' ) ;
2018-06-17 15:25:41 -07:00
2020-02-21 12:17:06 -08:00
if ( users . compareRoles ( req . user . role , requiredRole ) < 0 ) return next ( new HttpError ( 403 , ` role ' ${ requiredRole } ' is required but user has only ' ${ req . user . role } ' ` ) ) ;
2018-04-26 15:54:53 -07:00
2020-02-06 14:50:12 +01:00
next ( ) ;
} ;
2018-04-26 15:54:53 -07:00
}
2021-06-04 09:28:40 -07:00
async function websocketAuth ( requiredRole , req , res , next ) {
2020-02-06 16:44:46 +01:00
assert . strictEqual ( typeof requiredRole , 'string' ) ;
2018-04-26 15:54:53 -07:00
2021-06-05 21:26:43 -07:00
if ( typeof req . query . access _token !== 'string' ) return next ( new HttpError ( 401 , 'access_token must be a string' ) ) ;
2018-04-26 15:54:53 -07:00
2021-06-04 09:28:40 -07:00
const [ error , user ] = await safe ( accesscontrol . verifyToken ( req . query . access _token ) ) ;
2021-06-05 21:26:43 -07:00
if ( error && error . reason === BoxError . INVALID _CREDENTIALS ) return next ( new HttpError ( 401 , error . message ) ) ;
2021-06-04 09:28:40 -07:00
if ( error ) return next ( new HttpError ( 500 , error . message ) ) ;
2018-04-26 15:54:53 -07:00
2021-06-04 09:28:40 -07:00
req . user = user ;
2018-04-26 15:54:53 -07:00
2021-06-04 09:28:40 -07:00
if ( users . compareRoles ( req . user . role , requiredRole ) < 0 ) return next ( new HttpError ( 403 , ` role ' ${ requiredRole } ' is required but user has only ' ${ user . role } ' ` ) ) ;
2018-04-26 15:54:53 -07:00
2021-06-04 09:28:40 -07:00
next ( ) ;
2018-04-26 15:54:53 -07:00
}