2016-02-11 20:01:14 -08:00
// These methods handle the User-related routes.
2016-11-24 15:47:41 -05:00
import Parse from 'parse/node' ;
2016-05-25 16:48:18 -07:00
import Config from '../Config' ;
2016-09-02 17:00:47 -07:00
import AccountLockout from '../AccountLockout' ;
2016-02-16 23:43:09 -08:00
import ClassesRouter from './ClassesRouter' ;
import rest from '../rest' ;
import Auth from '../Auth' ;
2016-02-11 20:01:14 -08:00
import passwordCrypto from '../password' ;
2016-02-16 23:43:09 -08:00
import RestWrite from '../RestWrite' ;
2016-12-07 15:17:05 -08:00
const cryptoUtils = require ( '../cryptoUtils' ) ;
2016-02-11 20:01:14 -08:00
export class UsersRouter extends ClassesRouter {
2017-09-05 17:51:11 -04:00
className ( ) {
return '_User' ;
2016-02-11 20:01:14 -08:00
}
handleMe ( req ) {
if ( ! req . info || ! req . info . sessionToken ) {
2016-02-15 10:12:53 -05:00
throw new Parse . Error ( Parse . Error . INVALID _SESSION _TOKEN , 'invalid session token' ) ;
2016-02-11 20:01:14 -08:00
}
2016-12-07 15:17:05 -08:00
const sessionToken = req . info . sessionToken ;
2016-02-11 20:01:14 -08:00
return rest . find ( req . config , Auth . master ( req . config ) , '_Session' ,
2016-04-25 20:42:19 -07:00
{ sessionToken } ,
2016-07-12 10:06:13 -04:00
{ include : 'user' } , req . info . clientSDK )
2016-02-11 20:01:14 -08:00
. then ( ( response ) => {
if ( ! response . results ||
response . results . length == 0 ||
! response . results [ 0 ] . user ) {
2016-02-15 10:12:53 -05:00
throw new Parse . Error ( Parse . Error . INVALID _SESSION _TOKEN , 'invalid session token' ) ;
2016-02-11 20:01:14 -08:00
} else {
2016-12-07 15:17:05 -08:00
const user = response . results [ 0 ] . user ;
2016-02-24 12:51:06 -08:00
// Send token back on the login, because SDKs expect that.
user . sessionToken = sessionToken ;
2017-05-11 23:14:58 +09:30
// Remove hidden properties.
for ( var key in user ) {
if ( user . hasOwnProperty ( key ) ) {
// Regexp comes from Parse.Object.prototype.validate
if ( key !== "__type" && ! ( /^[A-Za-z][0-9A-Za-z_]*$/ ) . test ( key ) ) {
delete user [ key ] ;
}
}
}
2016-02-11 20:01:14 -08:00
return { response : user } ;
}
} ) ;
}
handleLogIn ( req ) {
// Use query parameters instead if provided in url
if ( ! req . body . username && req . query . username ) {
req . body = req . query ;
}
// TODO: use the right error codes / descriptions.
if ( ! req . body . username ) {
throw new Parse . Error ( Parse . Error . USERNAME _MISSING , 'username is required.' ) ;
}
if ( ! req . body . password ) {
throw new Parse . Error ( Parse . Error . PASSWORD _MISSING , 'password is required.' ) ;
}
2016-09-10 00:24:33 +09:00
if ( typeof req . body . username !== 'string' || typeof req . body . password !== 'string' ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , 'Invalid username/password.' ) ;
}
2016-02-11 20:01:14 -08:00
let user ;
2016-09-02 17:00:47 -07:00
let isValidPassword = false ;
2016-02-26 21:17:04 -08:00
return req . config . database . find ( '_User' , { username : req . body . username } )
2016-02-11 20:01:14 -08:00
. then ( ( results ) => {
if ( ! results . length ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , 'Invalid username/password.' ) ;
}
user = results [ 0 ] ;
2016-07-04 12:56:35 -05:00
if ( req . config . verifyUserEmails && req . config . preventLoginWithUnverifiedEmail && ! user . emailVerified ) {
throw new Parse . Error ( Parse . Error . EMAIL _NOT _FOUND , 'User email is not verified.' ) ;
}
2016-02-11 20:01:14 -08:00
return passwordCrypto . compare ( req . body . password , user . password ) ;
2016-09-02 17:00:47 -07:00
} )
. then ( ( correct ) => {
isValidPassword = correct ;
2016-12-07 15:17:05 -08:00
const accountLockoutPolicy = new AccountLockout ( user , req . config ) ;
2016-09-02 17:00:47 -07:00
return accountLockoutPolicy . handleLoginAttempt ( isValidPassword ) ;
} )
. then ( ( ) => {
if ( ! isValidPassword ) {
2016-02-11 20:01:14 -08:00
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , 'Invalid username/password.' ) ;
}
2016-11-21 21:16:38 +05:30
// handle password expiry policy
if ( req . config . passwordPolicy && req . config . passwordPolicy . maxPasswordAge ) {
let changedAt = user . _password _changed _at ;
if ( ! changedAt ) {
// password was created before expiry policy was enabled.
// simply update _User object so that it will start enforcing from now
changedAt = new Date ( ) ;
req . config . database . update ( '_User' , { username : user . username } ,
{ _password _changed _at : Parse . _encode ( changedAt ) } ) ;
} else {
// check whether the password has expired
if ( changedAt . _ _type == 'Date' ) {
changedAt = new Date ( changedAt . iso ) ;
}
// Calculate the expiry time.
const expiresAt = new Date ( changedAt . getTime ( ) + 86400000 * req . config . passwordPolicy . maxPasswordAge ) ;
if ( expiresAt < new Date ( ) ) // fail of current time is past password expiry time
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , 'Your password has expired. Please reset your password.' ) ;
}
}
2016-12-07 15:17:05 -08:00
const token = 'r:' + cryptoUtils . newToken ( ) ;
2016-02-11 20:01:14 -08:00
user . sessionToken = token ;
delete user . password ;
2016-03-20 10:50:34 -04:00
2016-03-11 09:33:09 -05:00
// Sometimes the authData still has null on that keys
2016-03-20 10:50:34 -04:00
// https://github.com/ParsePlatform/parse-server/issues/935
2016-03-11 09:33:09 -05:00
if ( user . authData ) {
Object . keys ( user . authData ) . forEach ( ( provider ) => {
if ( user . authData [ provider ] === null ) {
delete user . authData [ provider ] ;
}
} ) ;
if ( Object . keys ( user . authData ) . length == 0 ) {
delete user . authData ;
}
}
2016-03-20 10:50:34 -04:00
2016-02-11 20:01:14 -08:00
req . config . filesController . expandFilesInObject ( req . config , user ) ;
2016-12-07 15:17:05 -08:00
const expiresAt = req . config . generateSessionExpiresAt ( ) ;
const sessionData = {
2016-02-11 20:01:14 -08:00
sessionToken : token ,
user : {
_ _type : 'Pointer' ,
className : '_User' ,
objectId : user . objectId
} ,
createdWith : {
'action' : 'login' ,
'authProvider' : 'password'
} ,
restricted : false ,
expiresAt : Parse . _encode ( expiresAt )
} ;
if ( req . info . installationId ) {
sessionData . installationId = req . info . installationId
}
2016-12-07 15:17:05 -08:00
const create = new RestWrite ( req . config , Auth . master ( req . config ) , '_Session' , null , sessionData ) ;
2016-02-11 20:01:14 -08:00
return create . execute ( ) ;
} ) . then ( ( ) => {
return { response : user } ;
} ) ;
}
handleLogOut ( req ) {
2016-12-07 15:17:05 -08:00
const success = { response : { } } ;
2016-02-11 20:01:14 -08:00
if ( req . info && req . info . sessionToken ) {
return rest . find ( req . config , Auth . master ( req . config ) , '_Session' ,
2016-07-12 10:06:13 -04:00
{ sessionToken : req . info . sessionToken } , undefined , req . info . clientSDK
2016-02-11 20:01:14 -08:00
) . then ( ( records ) => {
if ( records . results && records . results . length ) {
return rest . del ( req . config , Auth . master ( req . config ) , '_Session' ,
records . results [ 0 ] . objectId
) . then ( ( ) => {
return Promise . resolve ( success ) ;
} ) ;
}
return Promise . resolve ( success ) ;
} ) ;
}
return Promise . resolve ( success ) ;
}
2016-03-20 10:50:34 -04:00
2017-03-04 13:30:52 -08:00
_throwOnBadEmailConfig ( req ) {
2016-05-25 16:48:18 -07:00
try {
Config . validateEmailConfiguration ( {
2016-07-05 12:08:46 -07:00
emailAdapter : req . config . userController . adapter ,
2016-05-25 16:48:18 -07:00
appName : req . config . appName ,
publicServerURL : req . config . publicServerURL ,
2016-07-19 01:10:36 -05:00
emailVerifyTokenValidityDuration : req . config . emailVerifyTokenValidityDuration
2016-05-25 16:48:18 -07:00
} ) ;
} catch ( e ) {
if ( typeof e === 'string' ) {
// Maybe we need a Bad Configuration error, but the SDKs won't understand it. For now, Internal Server Error.
2017-03-04 13:30:52 -08:00
throw new Parse . Error ( Parse . Error . INTERNAL _SERVER _ERROR , 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' ) ;
2016-05-25 16:48:18 -07:00
} else {
throw e ;
}
}
2017-03-04 13:30:52 -08:00
}
handleResetRequest ( req ) {
this . _throwOnBadEmailConfig ( req ) ;
2016-12-07 15:17:05 -08:00
const { email } = req . body ;
2016-05-25 16:48:18 -07:00
if ( ! email ) {
throw new Parse . Error ( Parse . Error . EMAIL _MISSING , "you must provide an email" ) ;
}
2016-09-10 00:24:33 +09:00
if ( typeof email !== 'string' ) {
throw new Parse . Error ( Parse . Error . INVALID _EMAIL _ADDRESS , 'you must provide a valid email string' ) ;
}
2016-12-07 15:17:05 -08:00
const userController = req . config . userController ;
2016-11-24 15:47:41 -05:00
return userController . sendPasswordResetEmail ( email ) . then ( ( ) => {
return Promise . resolve ( {
response : { }
} ) ;
2016-05-25 16:48:18 -07:00
} , err => {
if ( err . code === Parse . Error . OBJECT _NOT _FOUND ) {
throw new Parse . Error ( Parse . Error . EMAIL _NOT _FOUND , ` No user found with email ${ email } . ` ) ;
} else {
throw err ;
}
} ) ;
2016-02-25 19:04:27 -05:00
}
2016-03-20 10:50:34 -04:00
2017-03-04 13:30:52 -08:00
handleVerificationEmailRequest ( req ) {
this . _throwOnBadEmailConfig ( req ) ;
const { email } = req . body ;
if ( ! email ) {
throw new Parse . Error ( Parse . Error . EMAIL _MISSING , 'you must provide an email' ) ;
}
if ( typeof email !== 'string' ) {
throw new Parse . Error ( Parse . Error . INVALID _EMAIL _ADDRESS , 'you must provide a valid email string' ) ;
}
return req . config . database . find ( '_User' , { email : email } ) . then ( ( results ) => {
if ( ! results . length || results . length < 1 ) {
throw new Parse . Error ( Parse . Error . EMAIL _NOT _FOUND , ` No user found with email ${ email } ` ) ;
}
const user = results [ 0 ] ;
if ( user . emailVerified ) {
throw new Parse . Error ( Parse . Error . OTHER _CAUSE , ` Email ${ email } is already verified. ` ) ;
}
const userController = req . config . userController ;
userController . sendVerificationEmail ( user ) ;
return { response : { } } ;
} ) ;
}
2016-02-11 20:01:14 -08:00
2016-02-19 23:47:44 -05:00
mountRoutes ( ) {
this . route ( 'GET' , '/users' , req => { return this . handleFind ( req ) ; } ) ;
this . route ( 'POST' , '/users' , req => { return this . handleCreate ( req ) ; } ) ;
2016-09-09 17:28:41 -04:00
this . route ( 'GET' , '/users/me' , req => { return this . handleMe ( req ) ; } ) ;
2016-02-19 23:47:44 -05:00
this . route ( 'GET' , '/users/:objectId' , req => { return this . handleGet ( req ) ; } ) ;
this . route ( 'PUT' , '/users/:objectId' , req => { return this . handleUpdate ( req ) ; } ) ;
this . route ( 'DELETE' , '/users/:objectId' , req => { return this . handleDelete ( req ) ; } ) ;
this . route ( 'GET' , '/login' , req => { return this . handleLogIn ( req ) ; } ) ;
this . route ( 'POST' , '/logout' , req => { return this . handleLogOut ( req ) ; } ) ;
2017-03-04 13:30:52 -08:00
this . route ( 'POST' , '/requestPasswordReset' , req => { return this . handleResetRequest ( req ) ; } ) ;
this . route ( 'POST' , '/verificationEmailRequest' , req => { return this . handleVerificationEmailRequest ( req ) ; } ) ;
2016-02-11 20:01:14 -08:00
}
}
export default UsersRouter ;