codecombat/server/middleware/users.coffee

214 lines
8.5 KiB
CoffeeScript
Raw Normal View History

Stuff Partially fix ActivateLicensesModal.spec [IN PROGRESS] Don't display deleted users Move userID to classroom.deletedMembers on user delete (not retroactive) Fix PDF links for course guides, remove old PDFs from repo Remove deprecated SalesView Remove underline for not-yet-linked student names Only show class select when there's more than one Ignore case when sorting student names Use student.broadName instead of name for display and sorting Fix initial load not showing progress after joining a course (hacky) Fix text entry for enrollment number input Fix enrollment statistics Fix enrollment stats completely (and add back in per-class unenrolled count) Add deletedMembers to classroom schema More fixes to enrollment stats (don't count nonmember prepaids) Don't use 0 as implicit false for openSpots Update suggested number of credit to buy automatically Fix classroom edit form ignoring cleared values Add alert text when more users selected than enrollments available Alert user when trying to assign course to unenrolled students Alert user when assigning course to nobody Add some tests for TeacherClassView bulk assign alerts Fix TeacherClassView tests failing without demos Use model/collection.fakeRequests :D Remove unused comment Fix handling of improperly sorted deleted users on clientside Add test for moving deleted users to deletedMembers Add script for moving all deleted classroom members to classroom.deletedMembers Completely rewrite tallying up enrollment statistics Fix some tests to not be dependent on logged-in user Address PR comments Fix default number of enrollments to buy Fix i18n for not enough enrollments Use custom error message for classroom name length
2016-04-07 17:55:42 -04:00
_ = require 'lodash'
co = require 'co'
countryList = require('country-list')()
errors = require '../commons/errors'
geoip = require 'geoip-lite'
wrap = require 'co-express'
Promise = require 'bluebird'
parse = require '../commons/parse'
request = require 'request'
Stuff Partially fix ActivateLicensesModal.spec [IN PROGRESS] Don't display deleted users Move userID to classroom.deletedMembers on user delete (not retroactive) Fix PDF links for course guides, remove old PDFs from repo Remove deprecated SalesView Remove underline for not-yet-linked student names Only show class select when there's more than one Ignore case when sorting student names Use student.broadName instead of name for display and sorting Fix initial load not showing progress after joining a course (hacky) Fix text entry for enrollment number input Fix enrollment statistics Fix enrollment stats completely (and add back in per-class unenrolled count) Add deletedMembers to classroom schema More fixes to enrollment stats (don't count nonmember prepaids) Don't use 0 as implicit false for openSpots Update suggested number of credit to buy automatically Fix classroom edit form ignoring cleared values Add alert text when more users selected than enrollments available Alert user when trying to assign course to unenrolled students Alert user when assigning course to nobody Add some tests for TeacherClassView bulk assign alerts Fix TeacherClassView tests failing without demos Use model/collection.fakeRequests :D Remove unused comment Fix handling of improperly sorted deleted users on clientside Add test for moving deleted users to deletedMembers Add script for moving all deleted classroom members to classroom.deletedMembers Completely rewrite tallying up enrollment statistics Fix some tests to not be dependent on logged-in user Address PR comments Fix default number of enrollments to buy Fix i18n for not enough enrollments Use custom error message for classroom name length
2016-04-07 17:55:42 -04:00
mongoose = require 'mongoose'
Improve student account recovery This adds the ability to verify email addresses of a user, so we know they have access to the email address on their account. Until a user has verified their email address, any teacher of a class they're in can reset their password for them via the Teacher Dashboard. When a user's email address is verified, a teacher may trigger a password recovery email to be sent to the student. Verification links are valid forever, until the user changes the email address they have on file. They are created using a timestamp, with a sha256 of timestamp+salt+userID+email. Currently the hash value is rather long, could be shorter. Squashed commit messages: Add server endpoints for verifying email address Add server endpoints for verifying email address (pt 2) Add Server+Client endpoint for sending verification email Add client view for verification links Add Edit Student Modal for resetting passwords Add specs for EditStudentModal Tweak method name in EditStudentModal Add edit student button to TeacherClassView Fix up frontend for teacher password resetting Add middleware for teacher password resetting Improve button UX in EditStudentModal Add JoinClassModal Add welcome emails, use broad name Use email without domain as fallback instead of full email Fetch user on edit student modal open Don't allow password reset if student email is verified Set role to student on user signup with classCode Tweak interface for joinClassModal Add button to request verification email for yourself Fix verify email template ID Move text to en.coffee Minor tweaks Fix code review comments Fix some tests, disable a broken one Fix misc tests Fix more tests Refactor recovery email sending to auth Fix overbroad sass Add options to refactored recovery email function Rename getByCode to fetchByCode Fix error message Fix up error handling in users middleware Use .get instead of .toObject Use findById Fix more code review comments Disable still-broken test
2016-05-11 17:39:26 -04:00
sendwithus = require '../sendwithus'
User = require '../models/User'
Stuff Partially fix ActivateLicensesModal.spec [IN PROGRESS] Don't display deleted users Move userID to classroom.deletedMembers on user delete (not retroactive) Fix PDF links for course guides, remove old PDFs from repo Remove deprecated SalesView Remove underline for not-yet-linked student names Only show class select when there's more than one Ignore case when sorting student names Use student.broadName instead of name for display and sorting Fix initial load not showing progress after joining a course (hacky) Fix text entry for enrollment number input Fix enrollment statistics Fix enrollment stats completely (and add back in per-class unenrolled count) Add deletedMembers to classroom schema More fixes to enrollment stats (don't count nonmember prepaids) Don't use 0 as implicit false for openSpots Update suggested number of credit to buy automatically Fix classroom edit form ignoring cleared values Add alert text when more users selected than enrollments available Alert user when trying to assign course to unenrolled students Alert user when assigning course to nobody Add some tests for TeacherClassView bulk assign alerts Fix TeacherClassView tests failing without demos Use model/collection.fakeRequests :D Remove unused comment Fix handling of improperly sorted deleted users on clientside Add test for moving deleted users to deletedMembers Add script for moving all deleted classroom members to classroom.deletedMembers Completely rewrite tallying up enrollment statistics Fix some tests to not be dependent on logged-in user Address PR comments Fix default number of enrollments to buy Fix i18n for not enough enrollments Use custom error message for classroom name length
2016-04-07 17:55:42 -04:00
Classroom = require '../models/Classroom'
2016-06-30 18:32:58 -04:00
facebook = require '../lib/facebook'
gplus = require '../lib/gplus'
TrialRequest = require '../models/TrialRequest'
log = require 'winston'
module.exports =
fetchByGPlusID: wrap (req, res, next) ->
gpID = req.query.gplusID
gpAT = req.query.gplusAccessToken
return next() unless gpID and gpAT
2016-06-30 18:32:58 -04:00
googleResponse = yield gplus.fetchMe(gpAT)
idsMatch = gpID is googleResponse.id
throw new errors.UnprocessableEntity('Invalid G+ Access Token.') unless idsMatch
dbq = User.find()
dbq.select(parse.getProjectFromReq(req))
user = yield User.findOne({gplusID: gpID})
throw new errors.NotFound('No user with that G+ ID') unless user
res.status(200).send(user.toObject({req: req}))
fetchByFacebookID: wrap (req, res, next) ->
fbID = req.query.facebookID
fbAT = req.query.facebookAccessToken
return next() unless fbID and fbAT
2016-06-30 18:32:58 -04:00
facebookResponse = yield facebook.fetchMe(fbAT)
idsMatch = fbID is facebookResponse.id
throw new errors.UnprocessableEntity('Invalid Facebook Access Token.') unless idsMatch
dbq = User.find()
dbq.select(parse.getProjectFromReq(req))
user = yield User.findOne({facebookID: fbID})
throw new errors.NotFound('No user with that Facebook ID') unless user
res.status(200).send(user.toObject({req: req}))
Stuff Partially fix ActivateLicensesModal.spec [IN PROGRESS] Don't display deleted users Move userID to classroom.deletedMembers on user delete (not retroactive) Fix PDF links for course guides, remove old PDFs from repo Remove deprecated SalesView Remove underline for not-yet-linked student names Only show class select when there's more than one Ignore case when sorting student names Use student.broadName instead of name for display and sorting Fix initial load not showing progress after joining a course (hacky) Fix text entry for enrollment number input Fix enrollment statistics Fix enrollment stats completely (and add back in per-class unenrolled count) Add deletedMembers to classroom schema More fixes to enrollment stats (don't count nonmember prepaids) Don't use 0 as implicit false for openSpots Update suggested number of credit to buy automatically Fix classroom edit form ignoring cleared values Add alert text when more users selected than enrollments available Alert user when trying to assign course to unenrolled students Alert user when assigning course to nobody Add some tests for TeacherClassView bulk assign alerts Fix TeacherClassView tests failing without demos Use model/collection.fakeRequests :D Remove unused comment Fix handling of improperly sorted deleted users on clientside Add test for moving deleted users to deletedMembers Add script for moving all deleted classroom members to classroom.deletedMembers Completely rewrite tallying up enrollment statistics Fix some tests to not be dependent on logged-in user Address PR comments Fix default number of enrollments to buy Fix i18n for not enough enrollments Use custom error message for classroom name length
2016-04-07 17:55:42 -04:00
removeFromClassrooms: wrap (req, res, next) ->
yield req.user.removeFromClassrooms()
Stuff Partially fix ActivateLicensesModal.spec [IN PROGRESS] Don't display deleted users Move userID to classroom.deletedMembers on user delete (not retroactive) Fix PDF links for course guides, remove old PDFs from repo Remove deprecated SalesView Remove underline for not-yet-linked student names Only show class select when there's more than one Ignore case when sorting student names Use student.broadName instead of name for display and sorting Fix initial load not showing progress after joining a course (hacky) Fix text entry for enrollment number input Fix enrollment statistics Fix enrollment stats completely (and add back in per-class unenrolled count) Add deletedMembers to classroom schema More fixes to enrollment stats (don't count nonmember prepaids) Don't use 0 as implicit false for openSpots Update suggested number of credit to buy automatically Fix classroom edit form ignoring cleared values Add alert text when more users selected than enrollments available Alert user when trying to assign course to unenrolled students Alert user when assigning course to nobody Add some tests for TeacherClassView bulk assign alerts Fix TeacherClassView tests failing without demos Use model/collection.fakeRequests :D Remove unused comment Fix handling of improperly sorted deleted users on clientside Add test for moving deleted users to deletedMembers Add script for moving all deleted classroom members to classroom.deletedMembers Completely rewrite tallying up enrollment statistics Fix some tests to not be dependent on logged-in user Address PR comments Fix default number of enrollments to buy Fix i18n for not enough enrollments Use custom error message for classroom name length
2016-04-07 17:55:42 -04:00
next()
remainTeacher: wrap (req, res, next) ->
yield req.user.removeFromClassrooms()
user = yield User.findById req.user.id
res.status(200).send(user.toObject({req: req}))
becomeStudent: wrap (req, res, next) ->
userID = mongoose.Types.ObjectId(req.user.id)
yield Classroom.remove({ ownerID: userID }, false)
userID = mongoose.Types.ObjectId(req.user.id)
yield User.update({ _id: userID }, { $set: { "role": "student" } })
user = yield User.findById req.user.id
res.status(200).send(user.toObject({req: req}))
Improve student account recovery This adds the ability to verify email addresses of a user, so we know they have access to the email address on their account. Until a user has verified their email address, any teacher of a class they're in can reset their password for them via the Teacher Dashboard. When a user's email address is verified, a teacher may trigger a password recovery email to be sent to the student. Verification links are valid forever, until the user changes the email address they have on file. They are created using a timestamp, with a sha256 of timestamp+salt+userID+email. Currently the hash value is rather long, could be shorter. Squashed commit messages: Add server endpoints for verifying email address Add server endpoints for verifying email address (pt 2) Add Server+Client endpoint for sending verification email Add client view for verification links Add Edit Student Modal for resetting passwords Add specs for EditStudentModal Tweak method name in EditStudentModal Add edit student button to TeacherClassView Fix up frontend for teacher password resetting Add middleware for teacher password resetting Improve button UX in EditStudentModal Add JoinClassModal Add welcome emails, use broad name Use email without domain as fallback instead of full email Fetch user on edit student modal open Don't allow password reset if student email is verified Set role to student on user signup with classCode Tweak interface for joinClassModal Add button to request verification email for yourself Fix verify email template ID Move text to en.coffee Minor tweaks Fix code review comments Fix some tests, disable a broken one Fix misc tests Fix more tests Refactor recovery email sending to auth Fix overbroad sass Add options to refactored recovery email function Rename getByCode to fetchByCode Fix error message Fix up error handling in users middleware Use .get instead of .toObject Use findById Fix more code review comments Disable still-broken test
2016-05-11 17:39:26 -04:00
verifyEmailAddress: wrap (req, res, next) ->
user = yield User.findOne({ _id: mongoose.Types.ObjectId(req.params.userID) })
[timestamp, hash] = req.params.verificationCode.split(':')
unless user
throw new errors.UnprocessableEntity('User not found')
unless req.params.verificationCode is user.verificationCode(timestamp)
throw new errors.UnprocessableEntity('Verification code does not match')
yield User.update({ _id: user.id }, { emailVerified: true })
res.status(200).send({ role: user.get('role') })
resetEmailVerifiedFlag: wrap (req, res, next) ->
newEmail = req.body.email
_id = mongoose.Types.ObjectId(req.body._id)
if newEmail
user = yield User.findOne({ _id })
oldEmail = user.get('email')
if newEmail isnt oldEmail
yield User.update({ _id }, { $set: { emailVerified: false } })
next()
sendVerificationEmail: wrap (req, res, next) ->
user = yield User.findById(req.params.userID)
timestamp = (new Date).getTime()
if not user
throw new errors.NotFound('User not found')
if not user.get('email')
throw new errors.UnprocessableEntity('User must have an email address to receive a verification email')
Improve student account recovery This adds the ability to verify email addresses of a user, so we know they have access to the email address on their account. Until a user has verified their email address, any teacher of a class they're in can reset their password for them via the Teacher Dashboard. When a user's email address is verified, a teacher may trigger a password recovery email to be sent to the student. Verification links are valid forever, until the user changes the email address they have on file. They are created using a timestamp, with a sha256 of timestamp+salt+userID+email. Currently the hash value is rather long, could be shorter. Squashed commit messages: Add server endpoints for verifying email address Add server endpoints for verifying email address (pt 2) Add Server+Client endpoint for sending verification email Add client view for verification links Add Edit Student Modal for resetting passwords Add specs for EditStudentModal Tweak method name in EditStudentModal Add edit student button to TeacherClassView Fix up frontend for teacher password resetting Add middleware for teacher password resetting Improve button UX in EditStudentModal Add JoinClassModal Add welcome emails, use broad name Use email without domain as fallback instead of full email Fetch user on edit student modal open Don't allow password reset if student email is verified Set role to student on user signup with classCode Tweak interface for joinClassModal Add button to request verification email for yourself Fix verify email template ID Move text to en.coffee Minor tweaks Fix code review comments Fix some tests, disable a broken one Fix misc tests Fix more tests Refactor recovery email sending to auth Fix overbroad sass Add options to refactored recovery email function Rename getByCode to fetchByCode Fix error message Fix up error handling in users middleware Use .get instead of .toObject Use findById Fix more code review comments Disable still-broken test
2016-05-11 17:39:26 -04:00
context =
email_id: sendwithus.templates.verify_email
Improve student account recovery This adds the ability to verify email addresses of a user, so we know they have access to the email address on their account. Until a user has verified their email address, any teacher of a class they're in can reset their password for them via the Teacher Dashboard. When a user's email address is verified, a teacher may trigger a password recovery email to be sent to the student. Verification links are valid forever, until the user changes the email address they have on file. They are created using a timestamp, with a sha256 of timestamp+salt+userID+email. Currently the hash value is rather long, could be shorter. Squashed commit messages: Add server endpoints for verifying email address Add server endpoints for verifying email address (pt 2) Add Server+Client endpoint for sending verification email Add client view for verification links Add Edit Student Modal for resetting passwords Add specs for EditStudentModal Tweak method name in EditStudentModal Add edit student button to TeacherClassView Fix up frontend for teacher password resetting Add middleware for teacher password resetting Improve button UX in EditStudentModal Add JoinClassModal Add welcome emails, use broad name Use email without domain as fallback instead of full email Fetch user on edit student modal open Don't allow password reset if student email is verified Set role to student on user signup with classCode Tweak interface for joinClassModal Add button to request verification email for yourself Fix verify email template ID Move text to en.coffee Minor tweaks Fix code review comments Fix some tests, disable a broken one Fix misc tests Fix more tests Refactor recovery email sending to auth Fix overbroad sass Add options to refactored recovery email function Rename getByCode to fetchByCode Fix error message Fix up error handling in users middleware Use .get instead of .toObject Use findById Fix more code review comments Disable still-broken test
2016-05-11 17:39:26 -04:00
recipient:
address: user.get('email')
name: user.broadName()
email_data:
name: user.broadName()
verify_link: "http://codecombat.com/user/#{user._id}/verify/#{user.verificationCode(timestamp)}"
sendwithus.api.send context, (err, result) ->
res.status(200).send({})
2016-06-21 12:29:41 -04:00
getStudents: wrap (req, res, next) ->
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
query = $or: [{role: 'student'}, {$and: [{schoolName: {$exists: true}}, {schoolName: {$ne: ''}}, {anonymous: false}]}]
users = yield User.find(query).select('lastIP schoolName').lean()
for user in users
if ip = user.lastIP
user.geo = geoip.lookup(ip)
if country = user.geo?.country
user.geo.countryName = countryList.getName(country)
res.status(200).send(users)
2016-06-21 12:29:41 -04:00
getTeachers: wrap (req, res, next) ->
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
users = yield User.find(anonymous: false, role: {$in: teacherRoles}).select('lastIP').lean()
for user in users
if ip = user.lastIP
user.geo = geoip.lookup(ip)
if country = user.geo?.country
user.geo.countryName = countryList.getName(country)
res.status(200).send(users)
2016-06-30 18:32:58 -04:00
signupWithPassword: wrap (req, res) ->
unless req.user.isAnonymous()
throw new errors.Forbidden('You are already signed in.')
{ name, email, password } = req.body
unless password
throw new errors.UnprocessableEntity('Requires password')
unless name or email
throw new errors.UnprocessableEntity('Requires username or email')
2016-06-30 18:32:58 -04:00
if not _.isEmpty(email) and yield User.findByEmail(email)
2016-06-30 18:32:58 -04:00
throw new errors.Conflict('Email already taken')
if not _.isEmpty(name) and yield User.findByName(name)
throw new errors.Conflict('Name already taken')
2016-06-30 18:32:58 -04:00
req.user.set({ name, email, password, anonymous: false })
yield module.exports.finishSignup(req, res)
2016-06-30 18:32:58 -04:00
signupWithFacebook: wrap (req, res) ->
unless req.user.isAnonymous()
throw new errors.Forbidden('You are already signed in.')
2016-07-18 14:41:18 -04:00
{ facebookID, facebookAccessToken, email, name } = req.body
unless _.all([facebookID, facebookAccessToken, email, name])
throw new errors.UnprocessableEntity('Requires facebookID, facebookAccessToken, email, and name')
if not _.isEmpty(name) and yield User.findByName(name)
throw new errors.Conflict('Name already taken')
2016-06-30 18:32:58 -04:00
facebookResponse = yield facebook.fetchMe(facebookAccessToken)
emailsMatch = email is facebookResponse.email
idsMatch = facebookID is facebookResponse.id
unless emailsMatch and idsMatch
throw new errors.UnprocessableEntity('Invalid facebookAccessToken')
2016-07-18 14:41:18 -04:00
req.user.set({ facebookID, email, name, anonymous: false })
yield module.exports.finishSignup(req, res)
2016-06-30 18:32:58 -04:00
signupWithGPlus: wrap (req, res) ->
unless req.user.isAnonymous()
throw new errors.Forbidden('You are already signed in.')
2016-07-18 14:41:18 -04:00
{ gplusID, gplusAccessToken, email, name } = req.body
unless _.all([gplusID, gplusAccessToken, email, name])
throw new errors.UnprocessableEntity('Requires gplusID, gplusAccessToken, email, and name')
if not _.isEmpty(name) and yield User.findByName(name)
throw new errors.Conflict('Name already taken')
2016-06-30 18:32:58 -04:00
gplusResponse = yield gplus.fetchMe(gplusAccessToken)
emailsMatch = email is gplusResponse.email
idsMatch = gplusID is gplusResponse.id
unless emailsMatch and idsMatch
throw new errors.UnprocessableEntity('Invalid gplusAccessToken')
2016-07-18 14:41:18 -04:00
req.user.set({ gplusID, email, name, anonymous: false })
yield module.exports.finishSignup(req, res)
finishSignup: co.wrap (req, res) ->
2016-06-30 18:32:58 -04:00
try
yield req.user.save()
catch e
if e.code is 11000 # Duplicate key error
throw new errors.Conflict('Email already taken')
else
throw e
# post-successful account signup tasks
2016-06-30 18:32:58 -04:00
req.user.sendWelcomeEmail()
# If person A creates a trial request without creating an account, then person B uses that computer
# to create an account, then person A's trial request is associated with person B's account. To prevent
# this, we check that the signup email matches the trial request email, for every signup. If they do
# not match, the trial request applicant field is cleared, disassociating the trial request from this
# account.
trialRequest = yield TrialRequest.findOne({applicant: req.user._id})
if trialRequest
email = trialRequest.get('properties')?.email or ''
emailLower = email.toLowerCase()
if emailLower and emailLower isnt req.user.get('emailLower')
log.warn('User submitted trial request and created account with different emails. Disassociating trial request.')
yield trialRequest.update({$unset: {applicant: ''}})
2016-06-30 18:32:58 -04:00
res.status(200).send(req.user.toObject({req: req}))