2015-12-16 20:09:22 -05:00
# Middleware for both authentication and authorization
errors = require ' ../commons/errors '
2016-03-03 17:22:50 -05:00
wrap = require ' co-express '
Promise = require ' bluebird '
2016-02-25 18:24:16 -05:00
parse = require ' ../commons/parse '
request = require ' request '
2016-04-06 13:56:06 -04:00
User = require ' ../models/User '
2016-02-25 18:24:16 -05:00
utils = require ' ../lib/utils '
2016-03-03 17:22:50 -05:00
mongoose = require ' mongoose '
2016-04-11 19:51:51 -04:00
authentication = require ' passport '
sendwithus = require ' ../sendwithus '
LevelSession = require ' ../models/LevelSession '
2016-09-13 18:18:19 -04:00
config = require ' ../../server_config '
2015-12-16 20:09:22 -05:00
2016-02-25 18:24:16 -05:00
module.exports =
2015-12-16 20:09:22 -05:00
checkDocumentPermissions: (req, res, next) ->
return next ( ) if req . user ? . isAdmin ( )
if not req . doc . hasPermissionsForMethod ( req . user , req . method )
if req . user
return next new errors . Forbidden ( ' You do not have permissions necessary. ' )
return next new errors . Unauthorized ( ' You must be logged in. ' )
next ( )
checkLoggedIn: ->
return (req, res, next) ->
2016-03-30 19:20:37 -04:00
if ( not req . user ) or ( req . user . isAnonymous ( ) )
2015-12-16 20:09:22 -05:00
return next new errors . Unauthorized ( ' You must be logged in. ' )
next ( )
checkHasPermission: (permissions) ->
if _ . isString ( permissions )
permissions = [ permissions ]
return (req, res, next) ->
if not req . user
return next new errors . Unauthorized ( ' You must be logged in. ' )
if not _ . size ( _ . intersection ( req . user . get ( ' permissions ' ) , permissions ) )
return next new errors . Forbidden ( ' You do not have permissions necessary. ' )
next ( )
2016-02-08 17:24:08 -05:00
checkHasUser: ->
return (req, res, next) ->
if not req . user
return next new errors . Unauthorized ( ' No user associated with this request. ' )
next ( )
2016-04-11 19:51:51 -04:00
whoAmI: wrap (req, res) ->
if not req . user
user = User . makeNew ( req )
yield user . save ( )
req.logInAsync = Promise . promisify ( req . logIn )
yield req . logInAsync ( user )
if req . query . callback
2016-06-06 19:53:05 -04:00
res . jsonp ( req . user . toObject ( { req , publicOnly: true } ) )
2016-04-11 19:51:51 -04:00
else
res . send ( req . user . toObject ( { req , publicOnly: false } ) )
res . end ( )
afterLogin: wrap (req, res, next) ->
activity = req . user . trackActivity ' login ' , 1
yield req . user . update { activity: activity }
res . status ( 200 ) . send ( req . user . toObject ( { req: req } ) )
2015-12-16 20:09:22 -05:00
2016-09-13 18:18:19 -04:00
redirectHome: wrap (req, res, next) ->
activity = req . user . trackActivity ' login ' , 1
yield req . user . update { activity: activity }
res . redirect ' / '
2016-04-11 19:51:51 -04:00
loginByGPlus: wrap (req, res, next) ->
2016-02-25 18:24:16 -05:00
gpID = req . body . gplusID
gpAT = req . body . gplusAccessToken
throw new errors . UnprocessableEntity ( ' gplusID and gplusAccessToken required. ' ) unless gpID and gpAT
url = " https://www.googleapis.com/oauth2/v2/userinfo?access_token= #{ gpAT } "
[ googleRes , body ] = yield request . getAsync ( url , { json: true } )
idsMatch = gpID is body . id
throw new errors . UnprocessableEntity ( ' Invalid G+ Access Token. ' ) unless idsMatch
user = yield User . findOne ( { gplusID: gpID } )
throw new errors . NotFound ( ' No user with that G+ ID ' ) unless user
req.logInAsync = Promise . promisify ( req . logIn )
yield req . logInAsync ( user )
2016-04-11 19:51:51 -04:00
next ( )
2016-02-25 18:24:16 -05:00
2016-09-13 18:18:19 -04:00
loginByClever: wrap (req, res, next) ->
throw new errors . UnprocessableEntity ( ' Clever integration not configured. ' ) unless config . clever . client_id and config . clever . client_secret
code = req . query . code
scope = req . query . scope
throw new errors . UnprocessableEntity ( ' code and scope required. ' ) unless code and scope
[ cleverRes , auth ] = yield request . postAsync
json: true
url: " https://clever.com/oauth/tokens "
form:
code: code
grant_type: ' authorization_code '
2016-09-13 18:33:40 -04:00
redirect_uri: config . clever . redirect_uri
2016-09-13 18:18:19 -04:00
auth:
user: config . clever . client_id
password: config . clever . client_secret
sendImmediately: true
throw new errors . UnprocessableEntity ( ' Invalid Clever OAuth Code. ' ) unless auth . access_token
[ re2 , userInfo ] = yield request . getAsync
json : true
url: ' https://api.clever.com/me '
auth:
bearer: auth . access_token
[ lookupRes , lookup ] = yield request . getAsync
url: " https://api.clever.com/v1.1/ #{ userInfo . data . type } s/ #{ userInfo . data . id } "
json: true
auth:
bearer: auth . access_token
unless lookupRes . statusCode is 200
throw new errors . Forbidden ( " Couldn ' t look up user. Is data sharing enabled in clever? " )
user = yield User . findOne ( { cleverID: userInfo . data . id } )
unless user
user = new User
anonymous: false
role: if userInfo . data . type is ' student ' then ' student ' else ' teacher '
cleverID: userInfo . data . id
emailVerified: true
email: lookup . data . email
user . set ' testGroupNumber ' , Math . floor ( Math . random ( ) * 256 ) # also in app/core/auth
if lookup . data . name
user . set ' firstName ' , lookup . data . name . first
user . set ' lastName ' , lookup . data . name . last
yield user . save ( )
#console.log JSON.stringify
# userInfo: userInfo
# lookup: lookup
#,null,' '
req.logInAsync = Promise . promisify ( req . logIn )
yield req . logInAsync ( user )
next ( )
2016-04-11 19:51:51 -04:00
loginByFacebook: wrap (req, res, next) ->
2016-02-25 18:24:16 -05:00
fbID = req . body . facebookID
fbAT = req . body . facebookAccessToken
throw new errors . UnprocessableEntity ( ' facebookID and facebookAccessToken required. ' ) unless fbID and fbAT
url = " https://graph.facebook.com/me?access_token= #{ fbAT } "
[ facebookRes , body ] = yield request . getAsync ( url , { json: true } )
idsMatch = fbID is body . id
throw new errors . UnprocessableEntity ( ' Invalid Facebook Access Token. ' ) unless idsMatch
user = yield User . findOne ( { facebookID: fbID } )
throw new errors . NotFound ( ' No user with that Facebook ID ' ) unless user
req.logInAsync = Promise . promisify ( req . logIn )
yield req . logInAsync ( user )
2016-04-11 19:51:51 -04:00
next ( )
2016-02-25 18:24:16 -05:00
2016-03-03 17:22:50 -05:00
spy: wrap (req, res) ->
throw new errors . Unauthorized ( ' You must be logged in to enter espionage mode ' ) unless req . user
throw new errors . Forbidden ( ' You must be an admin to enter espionage mode ' ) unless req . user . isAdmin ( )
user = req . body . user
throw new errors . UnprocessableEntity ( ' Specify an id, username or email to espionage. ' ) unless user
2016-05-09 18:16:54 -04:00
user = yield User . search ( user )
2016-03-03 17:22:50 -05:00
amActually = req . user
throw new errors . NotFound ( ) unless user
req.loginAsync = Promise . promisify ( req . login )
yield req . loginAsync user
req.session.amActually = amActually . id
res . status ( 200 ) . send ( user . toObject ( { req: req } ) )
stopSpying: wrap (req, res) ->
throw new errors . Unauthorized ( ' You must be logged in to leave espionage mode ' ) unless req . user
throw new errors . Forbidden ( ' You must be in espionage mode to leave it ' ) unless req . session . amActually
user = yield User . findById ( req . session . amActually )
delete req . session . amActually
throw new errors . NotFound ( ) unless user
req.loginAsync = Promise . promisify ( req . login )
yield req . loginAsync user
res . status ( 200 ) . send ( user . toObject ( { req: req } ) )
2016-04-11 19:51:51 -04:00
logout: (req, res) ->
req . logout ( )
res . send ( { } )
reset: wrap (req, res) ->
unless req . body . email
throw new errors . UnprocessableEntity ( ' Need an email specified. ' , { property: ' email ' } )
user = yield User . findOne ( { emailLower: req . body . email . toLowerCase ( ) } )
if not user
throw new errors . NotFound ( ' not found ' , { property: ' email ' } )
user . set ( ' passwordReset ' , utils . getCodeCamel ( ) )
yield user . save ( )
context =
2016-06-06 19:53:05 -04:00
email_id: sendwithus . templates . password_reset
2016-04-11 19:51:51 -04:00
recipient:
address: req . body . email
email_data:
2016-04-12 16:28:38 -04:00
tempPassword: user . get ( ' passwordReset ' )
2016-04-11 19:51:51 -04:00
sendwithus.api.sendAsync = Promise . promisify ( sendwithus . api . send )
yield sendwithus . api . sendAsync ( context )
res . end ( )
unsubscribe: wrap (req, res) ->
2016-05-18 19:49:57 -04:00
# need to grab email directly from url, in case it has "+" in it
queryString = req . url . split ( ' ? ' ) [ 1 ] or ' '
queryParts = queryString . split ( ' & ' )
email = null
for part in queryParts
[ name , value ] = part . split ( ' = ' )
if name is ' email '
email = value
break
2016-04-11 19:51:51 -04:00
unless email
throw new errors . UnprocessableEntity ' No email provided to unsubscribe. '
email = decodeURIComponent ( email )
if req . query . session
# Unsubscribe from just one session's notifications instead.
session = yield LevelSession . findOne ( { _id: req . query . session } )
if not session
throw new errors . NotFound " Level session not found "
session . set ' unsubscribed ' , true
yield session . save ( )
res . send " Unsubscribed #{ email } from CodeCombat emails for #{ session . get ( ' levelName ' ) } #{ session . get ( ' team ' ) } ladder updates. Sorry to see you go! <p><a href= ' /play/ladder/ #{ session . levelID } # my-matches ' >Ladder preferences</a></p> "
res . end ( )
return
user = yield User . findOne ( { emailLower: email . toLowerCase ( ) } )
if not user
throw new errors . NotFound " No user found with email ' #{ email } ' "
emails = _ . clone ( user . get ( ' emails ' ) ) or { }
msg = ' '
if req . query . recruitNotes
emails . recruitNotes ? = { }
emails.recruitNotes.enabled = false
msg = " Unsubscribed #{ email } from recruiting emails. "
else if req . query . employerNotes
emails . employerNotes ? = { }
emails.employerNotes.enabled = false
msg = " Unsubscribed #{ email } from employer emails. "
else
msg = " Unsubscribed #{ email } from all CodeCombat emails. Sorry to see you go! "
emailSettings.enabled = false for emailSettings in _ . values ( emails )
emails . generalNews ? = { }
emails.generalNews.enabled = false
emails . anyNotes ? = { }
emails.anyNotes.enabled = false
yield user . update { $set: { emails: emails } }
res . send msg + ' <p><a href= " /account/settings " >Account settings</a></p> '
res . end ( )
name: wrap (req, res) ->
if not req . params . name
throw new errors . UnprocessableEntity ' No name provided. '
2016-06-30 18:32:58 -04:00
givenName = req . params . name
2016-04-11 19:51:51 -04:00
User.unconflictNameAsync = Promise . promisify ( User . unconflictName )
2016-06-30 18:32:58 -04:00
suggestedName = yield User . unconflictNameAsync givenName
response = {
givenName
suggestedName
conflicts: givenName isnt suggestedName
}
res . send 200 , response
email: wrap (req, res) ->
{ email } = req . params
if not email
throw new errors . UnprocessableEntity ' No email provided. '
user = yield User . findByEmail ( email )
res . send 200 , { exists: user ? }