2014-02-17 15:19:19 -08:00
schema = require ' ./user_schema '
crypto = require ' crypto '
request = require ' request '
User = require ' ./User '
Handler = require ' ../commons/Handler '
2014-01-03 10:32:13 -08:00
mongoose = require ' mongoose '
2014-01-03 14:28:00 -08:00
config = require ' ../../server_config '
2014-02-17 15:19:19 -08:00
errors = require ' ../commons/errors '
async = require ' async '
2014-01-03 10:32:13 -08:00
serverProperties = [ ' passwordHash ' , ' emailLower ' , ' nameLower ' , ' passwordReset ' ]
2014-03-14 02:08:31 +01:00
privateProperties = [ ' permissions ' , ' email ' , ' firstName ' , ' lastName ' , ' gender ' , ' facebookID ' , ' music ' , ' volume ' , ' aceConfig ' ]
2014-01-03 10:32:13 -08:00
UserHandler = class UserHandler extends Handler
modelClass: User
editableProperties: [
' name ' , ' photoURL ' , ' password ' , ' anonymous ' , ' wizardColor1 ' , ' volume ' ,
' firstName ' , ' lastName ' , ' gender ' , ' facebookID ' , ' emailSubscriptions ' ,
2014-01-12 11:54:50 -08:00
' testGroupNumber ' , ' music ' , ' hourOfCode ' , ' hourOfCodeComplete ' , ' preferredLanguage ' ,
2014-03-14 02:08:31 +01:00
' wizard ' , ' aceConfig '
2014-01-03 10:32:13 -08:00
]
jsonSchema: schema
2014-02-01 08:22:26 -08:00
2014-01-03 14:28:00 -08:00
constructor: ->
super ( arguments . . . )
@ editableProperties . push ( ' permissions ' ) unless config . isProduction
2014-01-03 10:32:13 -08:00
formatEntity: (req, document) ->
return null unless document ?
obj = document . toObject ( )
delete obj [ prop ] for prop in serverProperties
2014-02-24 20:27:38 -08:00
includePrivates = req . user and ( req . user ? . isAdmin ( ) or req . user ? . _id . equals ( document . _id ) )
2014-01-03 10:32:13 -08:00
delete obj [ prop ] for prop in privateProperties unless includePrivates
# emailHash is used by gravatar
hash = crypto . createHash ( ' md5 ' )
if document . get ( ' email ' )
hash . update ( _ . trim ( document . get ( ' email ' ) ) . toLowerCase ( ) )
else
hash . update ( @ _id + ' ' )
obj.emailHash = hash . digest ( ' hex ' )
return obj
waterfallFunctions: [
# FB access token checking
# Check the email is the same as FB reports
(req, user, callback) ->
fbID = req . query . facebookID
fbAT = req . query . facebookAccessToken
return callback ( null , req , user ) unless fbID and fbAT
url = " https://graph.facebook.com/me?access_token= #{ fbAT } "
2014-01-08 20:27:13 +01:00
request ( url , (error, response, body) ->
2014-01-03 10:32:13 -08:00
body = JSON . parse ( body )
emailsMatch = req . body . email is body . email
return callback ( res : ' Invalid Facebook Access Token. ' , code : 422 ) unless emailsMatch
callback ( null , req , user )
)
# GPlus access token checking
(req, user, callback) ->
gpID = req . query . gplusID
gpAT = req . query . gplusAccessToken
return callback ( null , req , user ) unless gpID and gpAT
url = " https://www.googleapis.com/oauth2/v2/userinfo?access_token= #{ gpAT } "
2014-01-08 20:27:13 +01:00
request ( url , (error, response, body) ->
2014-01-03 10:32:13 -08:00
body = JSON . parse ( body )
emailsMatch = req . body . email is body . email
return callback ( res : ' Invalid G+ Access Token. ' , code : 422 ) unless emailsMatch
callback ( null , req , user )
)
# Email setting
(req, user, callback) ->
return callback ( null , req , user ) unless req . body . email ?
emailLower = req . body . email . toLowerCase ( )
return callback ( null , req , user ) if emailLower is user . get ( ' emailLower ' )
User . findOne ( { emailLower : emailLower } ) . exec (err, otherUser) ->
return callback ( res : ' Database error. ' , code : 500 ) if err
if ( req . query . gplusID or req . query . facebookID ) and otherUser
# special case, log in as that user
return req . logIn ( otherUser , (err) ->
return callback ( res : ' Facebook user login error. ' , code : 500 ) if err
return callback ( null , req , otherUser )
)
r = { message : ' is already used by another account ' , property : ' email ' }
return callback ( { res : r , code : 409 } ) if otherUser
user . set ( ' email ' , req . body . email )
callback ( null , req , user )
# Name setting
(req, user, callback) ->
return callback ( null , req , user ) unless req . body . name
nameLower = req . body . name ? . toLowerCase ( )
return callback ( null , req , user ) if nameLower is user . get ( ' nameLower ' )
User . findOne ( { nameLower : nameLower } ) . exec (err, otherUser) ->
return callback ( res : ' Database error. ' , code : 500 ) if err
r = { message : ' is already used by another account ' , property : ' name ' }
return callback ( { res : r , code : 409 } ) if otherUser
user . set ( ' name ' , req . body . name )
callback ( null , req , user )
]
getById: (req, res, id) ->
2014-02-24 20:27:38 -08:00
if req . user ? . _id . equals ( id )
2014-01-03 10:32:13 -08:00
return @ sendSuccess ( res , @ formatEntity ( req , req . user ) )
super ( req , res , id )
2014-02-17 15:19:19 -08:00
getNamesByIds: (req, res) ->
ids = req . query . ids or req . body . ids
ids = ids . split ( ' , ' ) if _ . isString ids
ids = _ . uniq ids
2014-03-03 10:21:51 -08:00
# TODO: Extend and repurpose this handler to return other public info about a user more flexibly,
# say by a query parameter that lists public properties to return.
returnWizard = req . query . wizard or req . body . wizard
query = if returnWizard then { name : 1 , wizard : 1 } else { name : 1 }
2014-02-17 15:19:19 -08:00
makeFunc = (id) ->
(callback) ->
2014-03-03 10:21:51 -08:00
User . findById ( id , query ) . exec (err, document) ->
2014-02-17 15:19:19 -08:00
return done ( err ) if err
2014-03-03 10:21:51 -08:00
if document and returnWizard
callback ( null , { name : document . get ( ' name ' ) , wizard : document . get ( ' wizard ' ) or { } } )
else
callback ( null , document ? . get ( ' name ' ) or ' ' )
2014-02-17 15:19:19 -08:00
funcs = { }
for id in ids
return errors . badInput ( res , " Given an invalid id: #{ id } " ) unless Handler . isID ( id )
funcs [ id ] = makeFunc ( id )
async . parallel funcs , (err, results) ->
return errors . serverError err if err
res . send results
res . end ( )
2014-01-03 10:32:13 -08:00
2014-02-27 14:07:11 -08:00
nameToID: (req, res, name) ->
2014-02-27 14:42:11 -08:00
User . findOne ( { nameLower : name . toLowerCase ( ) } ) . exec (err, otherUser) ->
res . send ( if otherUser then otherUser . _id else JSON . stringify ( ' ' ) )
2014-02-27 14:07:11 -08:00
res . end ( )
2014-01-03 10:32:13 -08:00
post: (req, res) ->
return @ sendBadInputError ( res , ' No input. ' ) if _ . isEmpty ( req . body )
2014-02-24 20:27:38 -08:00
return @ sendBadInputError ( res , ' Must have an anonymous user to post with. ' ) unless req . user
2014-01-03 10:32:13 -08:00
return @ sendBadInputError ( res , ' Existing users cannot create new ones. ' ) unless req . user . get ( ' anonymous ' )
req.body._id = req . user . _id if req . user . get ( ' anonymous ' )
@ put ( req , res )
hasAccessToDocument: (req, document) ->
if req . route . method in [ ' put ' , ' post ' , ' patch ' ]
2014-02-24 20:27:38 -08:00
return true if req . user ? . isAdmin ( )
return req . user ? . _id . equals ( document . _id )
2014-01-03 10:32:13 -08:00
return true
getByRelationship: (req, res, args...) ->
return @ agreeToCLA ( req , res ) if args [ 1 ] is ' agreeToCLA '
2014-02-07 16:11:07 -08:00
return @ avatar ( req , res , args [ 0 ] ) if args [ 1 ] is ' avatar '
2014-02-17 15:19:19 -08:00
return @ getNamesByIds ( req , res ) if args [ 1 ] is ' names '
2014-02-27 14:07:11 -08:00
return @ nameToID ( req , res , args [ 0 ] ) if args [ 1 ] is ' nameToID '
2014-01-03 10:32:13 -08:00
return @ sendNotFoundError ( res )
2014-02-01 08:22:26 -08:00
2014-01-03 10:32:13 -08:00
agreeToCLA: (req, res) ->
2014-02-24 20:27:38 -08:00
return @ sendUnauthorizedError ( res ) unless req . user
2014-01-03 10:32:13 -08:00
doc =
user: req . user . _id + ' '
email: req . user . get ' email '
name: req . user . get ' name '
githubUsername: req . body . githubUsername
2014-01-05 16:02:50 -08:00
created: new Date ( ) + ' '
2014-01-18 10:16:55 -08:00
collection = mongoose . connection . db . collection ' cla.submissions ' , (err, collection) =>
2014-01-03 10:32:13 -08:00
return @ sendDatabaseError ( res , err ) if err
2014-01-18 10:16:55 -08:00
collection . insert doc , (err) =>
2014-01-03 10:32:13 -08:00
return @ sendDatabaseError ( res , err ) if err
req . user . set ( ' signedCLA ' , doc . created )
2014-01-18 10:16:55 -08:00
req . user . save (err) =>
2014-01-03 10:32:13 -08:00
return @ sendDatabaseError ( res , err ) if err
2014-01-14 23:13:47 +01:00
@ sendSuccess ( res , { result : ' success ' } )
2014-01-03 10:32:13 -08:00
2014-02-07 16:11:07 -08:00
avatar: (req, res, id) ->
@ modelClass . findById ( id ) . exec (err, document) ->
return @ sendDatabaseError ( res , err ) if err
res . redirect ( document ? . get ( ' photoURL ' ) or ' /images/generic-wizard-icon.png ' )
res . end ( )
2014-03-16 19:11:55 +04:00
module.exports = new UserHandler ( )