2015-07-20 00:09:47 -07:00
'use strict' ;
2016-04-30 23:16:37 -07:00
var appdb = require ( '../appdb' ) ,
2015-10-15 16:49:13 +02:00
apps = require ( '../apps' ) ,
2016-04-30 23:16:37 -07:00
assert = require ( 'assert' ) ,
2015-07-20 00:09:47 -07:00
authcodedb = require ( '../authcodedb' ) ,
2016-06-03 11:09:48 +02:00
clients = require ( '../clients' ) ,
2016-06-13 14:43:56 +02:00
ClientsError = clients . ClientsError ,
2015-07-20 00:09:47 -07:00
config = require ( '../config.js' ) ,
2016-08-01 10:14:45 +02:00
constants = require ( '../constants' ) ,
2015-07-20 00:09:47 -07:00
DatabaseError = require ( '../databaseerror' ) ,
debug = require ( 'debug' ) ( 'box:routes/oauth2' ) ,
2016-04-30 23:16:37 -07:00
eventlog = require ( '../eventlog.js' ) ,
hat = require ( 'hat' ) ,
2015-07-20 00:09:47 -07:00
HttpError = require ( 'connect-lastmile' ) . HttpError ,
middleware = require ( '../middleware/index.js' ) ,
oauth2orize = require ( 'oauth2orize' ) ,
passport = require ( 'passport' ) ,
querystring = require ( 'querystring' ) ,
session = require ( 'connect-ensure-login' ) ,
tokendb = require ( '../tokendb' ) ,
url = require ( 'url' ) ,
user = require ( '../user.js' ) ,
UserError = user . UserError ,
2016-06-02 23:53:06 -07:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2015-07-20 00:09:47 -07:00
2016-05-02 09:32:39 -07:00
function auditSource ( req , appId ) {
2016-05-01 20:09:31 -07:00
var ip = req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddress || null ;
2016-05-02 09:32:39 -07:00
return { authType : 'oauth' , ip : ip , appId : appId } ;
2016-05-01 20:09:31 -07:00
}
2015-07-20 00:09:47 -07:00
// create OAuth 2.0 server
var gServer = oauth2orize . createServer ( ) ;
2015-10-15 16:33:05 +02:00
// Register serialialization and deserialization functions.
//
// The client id is stored in the session and can thus be retrieved for each
// step in the oauth flow transaction, which involves multiple http requests.
gServer . serializeClient ( function ( client , callback ) {
return callback ( null , client . id ) ;
} ) ;
gServer . deserializeClient ( function ( id , callback ) {
2016-06-03 14:38:58 +02:00
clients . get ( id , callback ) ;
2015-10-15 16:33:05 +02:00
} ) ;
2015-07-20 00:09:47 -07:00
// Register supported grant types.
// Grant authorization codes. The callback takes the `client` requesting
// authorization, the `redirectURI` (which is used as a verifier in the
// subsequent exchange), the authenticated `user` granting access, and
// their response, which contains approved scope, duration, etc. as parsed by
// the application. The application issues a code, which is bound to these
// values, and will be exchanged for an access token.
gServer . grant ( oauth2orize . grant . code ( { scopeSeparator : ',' } , function ( client , redirectURI , user , ares , callback ) {
2015-10-15 16:55:48 +02:00
debug ( 'grant code:' , client . id , redirectURI , user . id , ares ) ;
2015-07-20 00:09:47 -07:00
var code = hat ( 256 ) ;
var expiresAt = Date . now ( ) + 60 * 60000 ; // 1 hour
2016-04-13 10:45:11 +02:00
authcodedb . add ( code , client . id , user . id , expiresAt , function ( error ) {
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( error ) ;
2015-10-15 16:55:48 +02:00
debug ( 'grant code: new auth code for client %s code %s' , client . id , code ) ;
2015-07-20 00:09:47 -07:00
callback ( null , code ) ;
} ) ;
} ) ) ;
gServer . grant ( oauth2orize . grant . token ( { scopeSeparator : ',' } , function ( client , user , ares , callback ) {
debug ( 'grant token:' , client . id , user . id , ares ) ;
var token = tokendb . generateToken ( ) ;
2016-08-01 10:14:45 +02:00
var expires = Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION ;
2015-07-20 00:09:47 -07:00
2016-06-03 13:01:05 +02:00
tokendb . add ( token , user . id , client . id , expires , client . scope , function ( error ) {
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( error ) ;
2015-10-15 16:55:48 +02:00
debug ( 'grant token: new access token for client %s token %s' , client . id , token ) ;
2015-07-20 00:09:47 -07:00
callback ( null , token ) ;
} ) ;
} ) ) ;
// Exchange authorization codes for access tokens. The callback accepts the
// `client`, which is exchanging `code` and any `redirectURI` from the
// authorization request for verification. If these values are validated, the
// application issues an access token on behalf of the user who authorized the
// code.
gServer . exchange ( oauth2orize . exchange . code ( function ( client , code , redirectURI , callback ) {
debug ( 'exchange:' , client , code , redirectURI ) ;
authcodedb . get ( code , function ( error , authCode ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( null , false ) ;
if ( error ) return callback ( error ) ;
if ( client . id !== authCode . clientId ) return callback ( null , false ) ;
authcodedb . del ( code , function ( error ) {
if ( error ) return callback ( error ) ;
var token = tokendb . generateToken ( ) ;
2016-08-01 10:14:45 +02:00
var expires = Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION ;
2015-07-20 00:09:47 -07:00
2016-06-03 13:01:05 +02:00
tokendb . add ( token , authCode . userId , authCode . clientId , expires , client . scope , function ( error ) {
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( error ) ;
2015-10-15 16:55:48 +02:00
debug ( 'exchange: new access token for client %s token %s' , client . id , token ) ;
2015-07-20 00:09:47 -07:00
callback ( null , token ) ;
} ) ;
} ) ;
} ) ;
} ) ) ;
// overwrite the session.ensureLoggedIn to not use res.redirect() due to a chrome bug not sending cookies on redirects
session . ensureLoggedIn = function ( redirectTo ) {
assert . strictEqual ( typeof redirectTo , 'string' ) ;
return function ( req , res , next ) {
if ( ! req . isAuthenticated || ! req . isAuthenticated ( ) ) {
if ( req . session ) {
req . session . returnTo = req . originalUrl || req . url ;
}
res . status ( 200 ) . send ( util . format ( '<script>window.location.href = "%s";</script>' , redirectTo ) ) ;
} else {
next ( ) ;
}
} ;
} ;
2015-10-12 19:50:09 +02:00
function renderTemplate ( res , template , data ) {
assert . strictEqual ( typeof res , 'object' ) ;
assert . strictEqual ( typeof template , 'string' ) ;
assert . strictEqual ( typeof data , 'object' ) ;
2015-10-20 12:31:02 +02:00
res . render ( template , data ) ;
2015-10-12 19:50:09 +02:00
}
2015-07-20 00:09:47 -07:00
function sendErrorPageOrRedirect ( req , res , message ) {
assert . strictEqual ( typeof req , 'object' ) ;
assert . strictEqual ( typeof res , 'object' ) ;
assert . strictEqual ( typeof message , 'string' ) ;
2015-10-15 16:55:48 +02:00
debug ( 'sendErrorPageOrRedirect: returnTo %s.' , req . query . returnTo , message ) ;
2015-07-20 00:09:47 -07:00
if ( typeof req . query . returnTo !== 'string' ) {
2015-10-12 19:50:09 +02:00
renderTemplate ( res , 'error' , {
2015-07-20 00:09:47 -07:00
adminOrigin : config . adminOrigin ( ) ,
2016-01-21 16:19:38 +01:00
message : message ,
title : 'Cloudron Error'
2015-07-20 00:09:47 -07:00
} ) ;
} else {
var u = url . parse ( req . query . returnTo ) ;
2015-10-12 19:50:09 +02:00
if ( ! u . protocol || ! u . host ) {
2015-10-14 15:30:10 +02:00
return renderTemplate ( res , 'error' , {
2015-10-12 19:50:09 +02:00
adminOrigin : config . adminOrigin ( ) ,
2016-01-21 16:19:38 +01:00
message : 'Invalid request. returnTo query is not a valid URI. ' + message ,
title : 'Cloudron Error'
2015-10-12 19:50:09 +02:00
} ) ;
}
2015-07-20 00:09:47 -07:00
res . redirect ( util . format ( '%s//%s' , u . protocol , u . host ) ) ;
}
}
2015-08-18 16:53:29 +02:00
// use this instead of sendErrorPageOrRedirect(), in case we have a returnTo provided in the query, to avoid login loops
// This usually happens when the OAuth client ID is wrong
2015-08-18 16:49:53 +02:00
function sendError ( req , res , message ) {
assert . strictEqual ( typeof req , 'object' ) ;
assert . strictEqual ( typeof res , 'object' ) ;
assert . strictEqual ( typeof message , 'string' ) ;
2015-10-12 19:50:09 +02:00
renderTemplate ( res , 'error' , {
2015-08-18 16:49:53 +02:00
adminOrigin : config . adminOrigin ( ) ,
2016-01-21 16:19:38 +01:00
message : message ,
title : 'Cloudron Error'
2015-08-18 16:49:53 +02:00
} ) ;
}
2015-10-14 16:31:55 +02:00
// -> GET /api/v1/session/login
2015-07-20 00:09:47 -07:00
function loginForm ( req , res ) {
if ( typeof req . session . returnTo !== 'string' ) return sendErrorPageOrRedirect ( req , res , 'Invalid login request. No returnTo provided.' ) ;
var u = url . parse ( req . session . returnTo , true ) ;
if ( ! u . query . client _id ) return sendErrorPageOrRedirect ( req , res , 'Invalid login request. No client_id provided.' ) ;
2015-08-10 14:57:05 +02:00
function render ( applicationName , applicationLogo ) {
2015-10-12 19:50:09 +02:00
renderTemplate ( res , 'login' , {
2015-07-20 00:09:47 -07:00
adminOrigin : config . adminOrigin ( ) ,
csrf : req . csrfToken ( ) ,
applicationName : applicationName ,
2015-08-10 14:57:05 +02:00
applicationLogo : applicationLogo ,
2016-01-21 16:19:38 +01:00
error : req . query . error || null ,
title : applicationName + ' Login'
2015-07-20 00:09:47 -07:00
} ) ;
}
2016-06-03 14:38:58 +02:00
clients . get ( u . query . client _id , function ( error , result ) {
2015-10-12 19:50:09 +02:00
if ( error ) return sendError ( req , res , 'Unknown OAuth client' ) ;
2015-10-15 16:31:45 -07:00
switch ( result . type ) {
2016-06-09 15:35:00 +02:00
case clients . TYPE _BUILT _IN : return render ( result . appId , '/api/v1/cloudron/avatar' ) ;
2016-06-08 14:09:06 +02:00
case clients . TYPE _EXTERNAL : return render ( result . appId , '/api/v1/cloudron/avatar' ) ;
2016-06-03 15:05:00 +02:00
case clients . TYPE _SIMPLE _AUTH : return sendError ( req , res , 'Unknown OAuth client' ) ;
2015-10-16 09:19:05 +02:00
default : break ;
2015-10-12 19:50:09 +02:00
}
2015-07-20 00:09:47 -07:00
2015-10-15 16:31:45 -07:00
appdb . get ( result . appId , function ( error , result ) {
2015-10-12 19:50:09 +02:00
if ( error ) return sendErrorPageOrRedirect ( req , res , 'Unknown Application for those OAuth credentials' ) ;
2015-07-20 00:09:47 -07:00
2015-10-12 19:50:09 +02:00
var applicationName = result . location || config . fqdn ( ) ;
render ( applicationName , '/api/v1/apps/' + result . id + '/icon' ) ;
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
2015-10-14 16:31:55 +02:00
// -> POST /api/v1/session/login
2015-07-20 00:09:47 -07:00
function login ( req , res ) {
var returnTo = req . session . returnTo || req . query . returnTo ;
var failureQuery = querystring . stringify ( { error : 'Invalid username or password' , returnTo : returnTo } ) ;
passport . authenticate ( 'local' , {
failureRedirect : '/api/v1/session/login?' + failureQuery
} ) ( req , res , function ( ) {
res . redirect ( returnTo ) ;
} ) ;
}
2015-10-14 16:31:55 +02:00
// -> GET /api/v1/session/logout
2015-07-20 00:09:47 -07:00
function logout ( req , res ) {
req . logout ( ) ;
if ( req . query && req . query . redirect ) res . redirect ( req . query . redirect ) ;
else res . redirect ( '/' ) ;
}
// Form to enter email address to send a password reset request mail
// -> GET /api/v1/session/password/resetRequest.html
function passwordResetRequestSite ( req , res ) {
2016-01-21 16:19:38 +01:00
renderTemplate ( res , 'password_reset_request' , { adminOrigin : config . adminOrigin ( ) , csrf : req . csrfToken ( ) , title : 'Cloudron Password Reset' } ) ;
2015-07-20 00:09:47 -07:00
}
// This route is used for above form submission
// -> POST /api/v1/session/password/resetRequest
function passwordResetRequest ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( typeof req . body . identifier !== 'string' ) return next ( new HttpError ( 400 , 'Missing identifier' ) ) ;
debug ( 'passwordResetRequest: email or username %s.' , req . body . identifier ) ;
user . resetPasswordByIdentifier ( req . body . identifier , function ( error ) {
if ( error && error . reason !== UserError . NOT _FOUND ) {
console . error ( error ) ;
return sendErrorPageOrRedirect ( req , res , 'User not found' ) ;
}
res . redirect ( '/api/v1/session/password/sent.html' ) ;
} ) ;
}
// -> GET /api/v1/session/password/sent.html
function passwordSentSite ( req , res ) {
2016-01-21 16:19:38 +01:00
renderTemplate ( res , 'password_reset_sent' , { adminOrigin : config . adminOrigin ( ) , title : 'Cloudron Password Reset' } ) ;
2015-07-20 00:09:47 -07:00
}
2016-04-04 13:00:23 +02:00
function renderAccountSetupSite ( res , req , userObject , error ) {
renderTemplate ( res , 'account_setup' , {
adminOrigin : config . adminOrigin ( ) ,
user : userObject ,
error : error ,
csrf : req . csrfToken ( ) ,
2016-04-04 13:54:38 +02:00
resetToken : req . query . reset _token || req . body . resetToken ,
2016-04-04 13:00:23 +02:00
title : 'Cloudron Password Setup'
} ) ;
}
2015-07-20 00:09:47 -07:00
2016-04-04 13:00:23 +02:00
// -> GET /api/v1/session/account/setup.html
function accountSetupSite ( req , res ) {
2016-04-04 14:26:56 +02:00
if ( ! req . query . reset _token ) return sendError ( req , res , 'Missing Reset Token' ) ;
2015-07-20 00:09:47 -07:00
2016-04-04 13:00:23 +02:00
user . getByResetToken ( req . query . reset _token , function ( error , userObject ) {
2016-04-04 14:26:56 +02:00
if ( error ) return sendError ( req , res , 'Invalid Reset Token' ) ;
2016-04-04 13:45:34 +02:00
2016-04-04 13:00:23 +02:00
renderAccountSetupSite ( res , req , userObject , '' ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2016-04-01 16:35:22 +02:00
// -> POST /api/v1/session/account/setup
function accountSetup ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( typeof req . body . resetToken !== 'string' ) return next ( new HttpError ( 400 , 'Missing resetToken' ) ) ;
if ( typeof req . body . password !== 'string' ) return next ( new HttpError ( 400 , 'Missing password' ) ) ;
if ( typeof req . body . username !== 'string' ) return next ( new HttpError ( 400 , 'Missing username' ) ) ;
if ( typeof req . body . displayName !== 'string' ) return next ( new HttpError ( 400 , 'Missing displayName' ) ) ;
debug ( 'acountSetup: with token %s.' , req . body . resetToken ) ;
user . getByResetToken ( req . body . resetToken , function ( error , userObject ) {
2016-04-04 14:26:56 +02:00
if ( error ) return sendError ( req , res , 'Invalid Reset Token' ) ;
2016-04-01 16:35:22 +02:00
2016-06-02 23:53:06 -07:00
var data = _ . pick ( req . body , 'username' , 'displayName' ) ;
user . update ( userObject . id , data , auditSource ( req ) , function ( error ) {
2016-04-04 13:00:23 +02:00
if ( error && error . reason === UserError . ALREADY _EXISTS ) return renderAccountSetupSite ( res , req , userObject , 'Username already exists' ) ;
2016-06-02 00:06:54 -07:00
if ( error && error . reason === UserError . BAD _FIELD ) return renderAccountSetupSite ( res , req , userObject , error . message ) ;
2016-05-18 09:57:17 -07:00
if ( error && error . reason === UserError . NOT _FOUND ) return renderAccountSetupSite ( res , req , userObject , 'No such user' ) ;
2016-04-01 16:35:22 +02:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
2016-06-02 21:11:43 +02:00
userObject . username = req . body . username ;
userObject . displayName = req . body . displayName ;
2016-04-01 16:35:22 +02:00
// setPassword clears the resetToken
user . setPassword ( userObject . id , req . body . password , function ( error , result ) {
2016-06-02 00:06:54 -07:00
if ( error && error . reason === UserError . BAD _FIELD ) return renderAccountSetupSite ( res , req , userObject , error . message ) ;
2016-05-18 09:57:17 -07:00
2016-04-01 16:35:22 +02:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
res . redirect ( util . format ( '%s?accessToken=%s&expiresAt=%s' , config . adminOrigin ( ) , result . token , result . expiresAt ) ) ;
} ) ;
} ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
// -> GET /api/v1/session/password/reset.html
function passwordResetSite ( req , res , next ) {
if ( ! req . query . reset _token ) return next ( new HttpError ( 400 , 'Missing reset_token' ) ) ;
user . getByResetToken ( req . query . reset _token , function ( error , user ) {
if ( error ) return next ( new HttpError ( 401 , 'Invalid reset_token' ) ) ;
2015-10-12 19:50:09 +02:00
renderTemplate ( res , 'password_reset' , {
adminOrigin : config . adminOrigin ( ) ,
user : user ,
csrf : req . csrfToken ( ) ,
2016-01-21 16:19:38 +01:00
resetToken : req . query . reset _token ,
title : 'Cloudron Password Reset'
2015-10-12 19:50:09 +02:00
} ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
// -> POST /api/v1/session/password/reset
function passwordReset ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( typeof req . body . resetToken !== 'string' ) return next ( new HttpError ( 400 , 'Missing resetToken' ) ) ;
if ( typeof req . body . password !== 'string' ) return next ( new HttpError ( 400 , 'Missing password' ) ) ;
debug ( 'passwordReset: with token %s.' , req . body . resetToken ) ;
user . getByResetToken ( req . body . resetToken , function ( error , userObject ) {
if ( error ) return next ( new HttpError ( 401 , 'Invalid resetToken' ) ) ;
2016-04-04 13:54:22 +02:00
if ( ! userObject . username ) return next ( new HttpError ( 401 , 'No username set' ) ) ;
2016-04-01 16:35:22 +02:00
// setPassword clears the resetToken
user . setPassword ( userObject . id , req . body . password , function ( error , result ) {
2016-06-02 00:06:54 -07:00
if ( error && error . reason === UserError . BAD _FIELD ) return next ( new HttpError ( 406 , error . message ) ) ;
2015-07-20 00:09:47 -07:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
2016-04-01 16:35:22 +02:00
res . redirect ( util . format ( '%s?accessToken=%s&expiresAt=%s' , config . adminOrigin ( ) , result . token , result . expiresAt ) ) ;
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
2015-10-14 16:31:55 +02:00
// The callback page takes the redirectURI and the authCode and redirects the browser accordingly
//
// -> GET /api/v1/session/callback
2015-07-20 00:09:47 -07:00
var callback = [
session . ensureLoggedIn ( '/api/v1/session/login' ) ,
function ( req , res ) {
2015-10-12 19:50:09 +02:00
renderTemplate ( res , 'callback' , { adminOrigin : config . adminOrigin ( ) , callbackServer : req . query . redirectURI } ) ;
2015-07-20 00:09:47 -07:00
}
] ;
2015-10-14 16:31:55 +02:00
// The authorization endpoint is the entry point for an OAuth login.
//
// Each app would start OAuth by redirecting the user to:
//
// /api/v1/oauth/dialog/authorize?response_type=code&client_id=<clientId>&redirect_uri=<callbackURL>&scope=<ignored>
//
// - First, this will ensure the user is logged in.
// - Then it will redirect the browser to the given <callbackURL> containing the authcode in the query
//
// -> GET /api/v1/oauth/dialog/authorize
2015-07-20 00:09:47 -07:00
var authorization = [
function ( req , res , next ) {
if ( ! req . query . redirect _uri ) return sendErrorPageOrRedirect ( req , res , 'Invalid request. redirect_uri query param is not set.' ) ;
if ( ! req . query . client _id ) return sendErrorPageOrRedirect ( req , res , 'Invalid request. client_id query param is not set.' ) ;
if ( ! req . query . response _type ) return sendErrorPageOrRedirect ( req , res , 'Invalid request. response_type query param is not set.' ) ;
if ( req . query . response _type !== 'code' && req . query . response _type !== 'token' ) return sendErrorPageOrRedirect ( req , res , 'Invalid request. Only token and code response types are supported.' ) ;
session . ensureLoggedIn ( '/api/v1/session/login?returnTo=' + req . query . redirect _uri ) ( req , res , next ) ;
} ,
2015-10-14 16:16:37 +02:00
gServer . authorization ( { } , function ( clientId , redirectURI , callback ) {
2015-10-14 16:15:51 +02:00
debug ( 'authorization: client %s with callback to %s.' , clientId , redirectURI ) ;
2015-07-20 00:09:47 -07:00
2016-06-03 14:38:58 +02:00
clients . get ( clientId , function ( error , client ) {
2016-06-13 14:43:56 +02:00
if ( error && error . reason === ClientsError . NOT _FOUND ) return callback ( null , false ) ;
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( error ) ;
// ignore the origin passed into form the client, but use the one from the clientdb
var redirectPath = url . parse ( redirectURI ) . path ;
var redirectOrigin = client . redirectURI ;
2015-11-25 17:45:18 +01:00
callback ( null , client , '/api/v1/session/callback?redirectURI=' + encodeURIComponent ( url . resolve ( redirectOrigin , redirectPath ) ) ) ;
2015-07-20 00:09:47 -07:00
} ) ;
2015-10-15 16:49:13 +02:00
} ) ,
function ( req , res , next ) {
2015-10-15 17:57:07 +02:00
// Handle our different types of oauth clients
2015-10-16 11:22:16 +02:00
var type = req . oauth2 . client . type ;
2015-10-15 17:57:07 +02:00
2016-06-09 15:35:00 +02:00
if ( type === clients . TYPE _EXTERNAL || type === clients . TYPE _BUILT _IN ) {
2016-06-08 14:09:06 +02:00
eventlog . add ( eventlog . ACTION _USER _LOGIN , auditSource ( req , req . oauth2 . client . appId ) , { userId : req . oauth2 . user . id } ) ;
2016-06-07 11:59:54 +02:00
return next ( ) ;
} else if ( type === clients . TYPE _SIMPLE _AUTH ) {
return sendError ( req , res , 'Unknown OAuth client.' ) ;
2016-04-30 23:16:37 -07:00
}
2015-10-15 17:57:07 +02:00
2015-10-16 11:22:16 +02:00
appdb . get ( req . oauth2 . client . appId , function ( error , appObject ) {
2015-10-15 16:49:13 +02:00
if ( error ) return sendErrorPageOrRedirect ( req , res , 'Invalid request. Unknown app for this client_id.' ) ;
2016-02-09 12:48:21 -08:00
apps . hasAccessTo ( appObject , req . oauth2 . user , function ( error , access ) {
if ( error ) return sendError ( req , res , 'Internal error' ) ;
if ( ! access ) return sendErrorPageOrRedirect ( req , res , 'No access to this app.' ) ;
2015-10-15 16:49:13 +02:00
2016-05-02 09:32:39 -07:00
eventlog . add ( eventlog . ACTION _USER _LOGIN , auditSource ( req , appObject . id ) , { userId : req . oauth2 . user . id } ) ;
2016-04-30 23:16:37 -07:00
2016-02-09 12:48:21 -08:00
next ( ) ;
} ) ;
2015-10-15 16:49:13 +02:00
} ) ;
} ,
gServer . decision ( { loadTransaction : false } )
2015-07-20 00:09:47 -07:00
] ;
2015-10-14 16:31:55 +02:00
// The token endpoint allows an OAuth client to exchange an authcode with an accesstoken.
//
// Authcodes are obtained using the authorization endpoint. The route is authenticated by
// providing a Basic auth with clientID as username and clientSecret as password.
// An authcode is only good for one such exchange to an accesstoken.
//
// -> POST /api/v1/oauth/token
2015-07-20 00:09:47 -07:00
var token = [
passport . authenticate ( [ 'basic' , 'oauth2-client-password' ] , { session : false } ) ,
gServer . token ( ) ,
gServer . errorHandler ( )
] ;
2016-06-03 10:10:58 +02:00
// tests if all requestedScopes are attached to the request
2016-06-03 10:17:52 +02:00
function validateRequestedScopes ( req , requestedScopes ) {
2016-06-03 10:10:58 +02:00
assert . strictEqual ( typeof req , 'object' ) ;
assert ( Array . isArray ( requestedScopes ) ) ;
if ( ! req . authInfo || ! req . authInfo . scope ) return new Error ( 'No scope found' ) ;
var scopes = req . authInfo . scope . split ( ',' ) ;
2016-06-03 11:09:48 +02:00
// check for roles separately
if ( requestedScopes . indexOf ( clients . SCOPE _ROLE _SDK ) !== - 1 && scopes . indexOf ( clients . SCOPE _ROLE _SDK ) === - 1 ) {
return new Error ( 'Missing required scope role "' + clients . SCOPE _ROLE _SDK + '"' ) ;
}
if ( scopes . indexOf ( '*' ) !== - 1 ) return null ;
2016-06-03 10:10:58 +02:00
for ( var i = 0 ; i < requestedScopes . length ; ++ i ) {
if ( scopes . indexOf ( requestedScopes [ i ] ) === - 1 ) {
debug ( 'scope: missing scope "%s".' , requestedScopes [ i ] ) ;
return new Error ( 'Missing required scope "' + requestedScopes [ i ] + '"' ) ;
}
}
return null ;
}
2015-07-20 00:09:47 -07:00
2015-10-14 16:31:55 +02:00
// The scope middleware provides an auth middleware for routes.
//
// It is used for API routes, which are authenticated using accesstokens.
// Those accesstokens carry OAuth scopes and the middleware takes the required
// scope as an argument and will verify the accesstoken against it.
//
// See server.js:
// var profileScope = routes.oauth2.scope('profile');
//
2015-07-20 00:09:47 -07:00
function scope ( requestedScope ) {
assert . strictEqual ( typeof requestedScope , 'string' ) ;
var requestedScopes = requestedScope . split ( ',' ) ;
debug ( 'scope: add routes with requested scopes' , requestedScopes ) ;
return [
passport . authenticate ( [ 'bearer' ] , { session : false } ) ,
function ( req , res , next ) {
2016-06-03 10:17:52 +02:00
var error = validateRequestedScopes ( req , requestedScopes ) ;
2016-06-03 10:10:58 +02:00
if ( error ) return next ( new HttpError ( 401 , error . message ) ) ;
2015-07-20 00:09:47 -07:00
next ( ) ;
}
] ;
}
// Cross-site request forgery protection middleware for login form
var csrf = [
middleware . csrf ( ) ,
function ( err , req , res , next ) {
if ( err . code !== 'EBADCSRFTOKEN' ) return next ( err ) ;
sendErrorPageOrRedirect ( req , res , 'Form expired' ) ;
}
] ;
exports = module . exports = {
loginForm : loginForm ,
login : login ,
logout : logout ,
callback : callback ,
passwordResetRequestSite : passwordResetRequestSite ,
passwordResetRequest : passwordResetRequest ,
passwordSentSite : passwordSentSite ,
passwordResetSite : passwordResetSite ,
passwordReset : passwordReset ,
2016-04-01 16:35:22 +02:00
accountSetupSite : accountSetupSite ,
accountSetup : accountSetup ,
2015-07-20 00:09:47 -07:00
authorization : authorization ,
token : token ,
2016-06-03 10:17:52 +02:00
validateRequestedScopes : validateRequestedScopes ,
2015-07-20 00:09:47 -07:00
scope : scope ,
csrf : csrf
} ;