mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-04 21:01:06 -05:00
f1f1c23fd4
* Take `/server/routes/auth` and move most of the logic to `/server/middleware/auth`, refactoring to use generators. * List all `/auth/*` endpoints in `/server/routes/index.coffee`. * Fill in testing gaps for `/auth/unsubscribe`. * Add debug log when `sendwithus` is not operational, so it 'works' in development and testing. * Use passport properly! * Track Facebook and G+ logins in user activity as well as passport logins.
370 lines
13 KiB
CoffeeScript
370 lines
13 KiB
CoffeeScript
require '../common'
|
|
User = require '../../../server/models/User'
|
|
utils = require '../utils'
|
|
_ = require 'lodash'
|
|
Promise = require 'bluebird'
|
|
nock = require 'nock'
|
|
request = require '../request'
|
|
sendwithus = require '../../../server/sendwithus'
|
|
LevelSession = require '../../../server/models/LevelSession'
|
|
|
|
urlLogin = getURL('/auth/login')
|
|
urlReset = getURL('/auth/reset')
|
|
|
|
describe 'GET /auth/whoami', ->
|
|
it 'returns 200', utils.wrap (done) ->
|
|
[res, body] = yield request.getAsync(getURL('/auth/whoami'))
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
describe 'POST /auth/login', ->
|
|
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
yield utils.becomeAnonymous()
|
|
done()
|
|
|
|
it 'allows logging in by iosIdentifierForVendor', utils.wrap (done) ->
|
|
yield utils.initUser({
|
|
'iosIdentifierForVendor': '012345678901234567890123456789012345'
|
|
'password': '12345'
|
|
})
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: '012345678901234567890123456789012345'
|
|
password: '12345'
|
|
}})
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
it 'returns 401 when the user does not exist', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: 'some@email.com'
|
|
password: '12345'
|
|
}})
|
|
expect(res.statusCode).toBe(401)
|
|
done()
|
|
|
|
it 'returns 200 when the user does exist', utils.wrap (done) ->
|
|
yield utils.initUser({
|
|
'email': 'some@email.com'
|
|
'password': '12345'
|
|
})
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: 'some@email.com'
|
|
password: '12345'
|
|
}})
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
it 'rejects wrong passwords', utils.wrap (done) ->
|
|
yield utils.initUser({
|
|
'email': 'some@email.com'
|
|
'password': '12345'
|
|
})
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: 'some@email.com'
|
|
password: '12346'
|
|
}})
|
|
expect(res.statusCode).toBe(401)
|
|
done()
|
|
|
|
it 'is completely case insensitive', utils.wrap (done) ->
|
|
yield utils.initUser({
|
|
'email': 'Some@Email.com'
|
|
'password': 'AbCdE'
|
|
})
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: 'sOmE@eMaIl.com'
|
|
password: 'aBcDe'
|
|
}})
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
describe 'POST /auth/reset', ->
|
|
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
@user = yield utils.initUser({
|
|
'email': 'some@email.com'
|
|
'password': '12345'
|
|
})
|
|
done()
|
|
|
|
it 'returns 422 if no email is included', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync(
|
|
{uri: urlReset, json: {username: 'some@email.com'}}
|
|
)
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 404 if account not found', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync(
|
|
{uri: urlReset, json: {email: 'some@other-email.com'}}
|
|
)
|
|
expect(res.statusCode).toBe(404)
|
|
done()
|
|
|
|
it 'resets the user password', utils.wrap (done) ->
|
|
spyOn(sendwithus.api, 'send').and.callFake (options, cb) ->
|
|
expect(options.recipient.address).toBe('some@email.com')
|
|
cb()
|
|
[res, body] = yield request.postAsync(
|
|
{uri: urlReset, json: {email: 'some@email.com'}}
|
|
)
|
|
expect(res.statusCode).toBe(200)
|
|
expect(sendwithus.api.send).toHaveBeenCalled()
|
|
user = yield User.findById(@user.id)
|
|
passwordReset = user.get('passwordReset')
|
|
expect(passwordReset).toBeTruthy()
|
|
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
|
username: 'some@email.com'
|
|
password: passwordReset
|
|
}})
|
|
expect(res.statusCode).toBe(200)
|
|
|
|
done()
|
|
|
|
it 'resetting password is not idempotent', utils.wrap (done) ->
|
|
spyOn(sendwithus.api, 'send').and.callFake (options, cb) ->
|
|
expect(options.recipient.address).toBe('some@email.com')
|
|
cb()
|
|
[res, body] = yield request.postAsync(
|
|
{uri: urlReset, json: {email: 'some@email.com'}}
|
|
)
|
|
expect(res.statusCode).toBe(200)
|
|
user = yield User.findById(@user.id)
|
|
passwordReset = user.get('passwordReset')
|
|
expect(passwordReset).toBeTruthy()
|
|
postArgs = {uri: urlLogin, json: {
|
|
username: 'some@email.com'
|
|
password: passwordReset
|
|
}}
|
|
|
|
[res, body] = yield request.postAsync(postArgs)
|
|
expect(res.statusCode).toBe(200)
|
|
[res, body] = yield request.postAsync(postArgs)
|
|
expect(res.statusCode).toBe(401)
|
|
done()
|
|
|
|
describe 'GET /auth/unsubscribe', ->
|
|
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
@user = yield utils.initUser()
|
|
done()
|
|
|
|
it 'returns 422 if email is not included', utils.wrap (done) ->
|
|
url = getURL('/auth/unsubscribe')
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 404 if email is not found', utils.wrap (done) ->
|
|
url = getURL('/auth/unsubscribe?email=ladeeda')
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(404)
|
|
done()
|
|
|
|
describe '?recruitNotes=1', ->
|
|
|
|
it 'unsubscribes the user from recruitment emails', utils.wrap (done) ->
|
|
url = getURL('/auth/unsubscribe?recruitNotes=1&email='+@user.get('email'))
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(200)
|
|
user = yield User.findOne(@user._id)
|
|
expect(user.get('emails').recruitNotes.enabled).toBe(false)
|
|
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
|
|
done()
|
|
|
|
describe '?employerNotes=1', ->
|
|
|
|
it 'unsubscribes the user from employer emails', utils.wrap (done) ->
|
|
url = getURL('/auth/unsubscribe?employerNotes=1&email='+@user.get('email'))
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(200)
|
|
user = yield User.findOne(@user._id)
|
|
expect(user.get('emails').employerNotes.enabled).toBe(false)
|
|
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
|
|
done()
|
|
|
|
describe '?session=:id', ->
|
|
|
|
it 'sets the given LevelSession\'s unsubscribed property to true', utils.wrap (done) ->
|
|
session = new LevelSession({permissions:[target: @user._id, access: 'owner']})
|
|
yield session.save()
|
|
url = getURL("/auth/unsubscribe?session=#{session.id}&email=#{@user.get('email')}")
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(200)
|
|
session = yield LevelSession.findById(session.id)
|
|
expect(session.get('unsubscribed')).toBe(true)
|
|
done()
|
|
|
|
describe 'no GET query params', ->
|
|
|
|
it 'unsubscribes the user from all emails', utils.wrap (done) ->
|
|
url = getURL("/auth/unsubscribe?email=#{@user.get('email')}")
|
|
[res, body] = yield request.getAsync(url)
|
|
expect(res.statusCode).toBe(200)
|
|
user = yield User.findOne(@user._id)
|
|
expect(user.get('emails').generalNews.enabled).toBe(false)
|
|
expect(user.get('emails').anyNotes.enabled).toBe(false)
|
|
done()
|
|
|
|
describe 'GET /auth/name', ->
|
|
url = '/auth/name'
|
|
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
done()
|
|
|
|
it 'returns 422 if no name is provided', utils.wrap (done) ->
|
|
[res, body] = yield request.getAsync {url: getURL(url + '/'), json: true}
|
|
expect(res.statusCode).toBe 422
|
|
done()
|
|
|
|
it 'returns the name given if there is no conflict', utils.wrap (done) ->
|
|
[res, body] = yield request.getAsync {url: getURL(url + '/Gandalf'), json: {}}
|
|
expect(res.statusCode).toBe 200
|
|
expect(res.body.name).toBe 'Gandalf'
|
|
done()
|
|
|
|
it 'returns a new name in case of conflict', utils.wrap (done) ->
|
|
yield utils.initUser({name: 'joe'})
|
|
[res, body] = yield request.getAsync {url: getURL(url + '/joe'), json: {}}
|
|
expect(res.statusCode).toBe 409
|
|
expect(res.body.name).not.toBe 'joe'
|
|
expect(/joe[0-9]/.test(res.body.name)).toBe(true)
|
|
done()
|
|
|
|
|
|
describe 'POST /auth/login-facebook', ->
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
done()
|
|
|
|
afterEach ->
|
|
nock.cleanAll()
|
|
|
|
url = getURL('/auth/login-facebook')
|
|
it 'takes facebookID and facebookAccessToken and logs the user in', utils.wrap (done) ->
|
|
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
|
yield new User({name: 'someone', facebookID: '1234'}).save()
|
|
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
it 'returns 422 if no token or id is provided', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync url
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 422 if the token is invalid', utils.wrap (done) ->
|
|
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(400, {})
|
|
yield new User({name: 'someone', facebookID: '1234'}).save()
|
|
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 404 if the user does not already exist', utils.wrap (done) ->
|
|
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
|
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(404)
|
|
done()
|
|
|
|
|
|
describe 'POST /auth/login-gplus', ->
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
done()
|
|
|
|
afterEach ->
|
|
nock.cleanAll()
|
|
|
|
url = getURL('/auth/login-gplus')
|
|
it 'takes gplusID and gplusAccessToken and logs the user in', utils.wrap (done) ->
|
|
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
|
yield new User({name: 'someone', gplusID: '1234'}).save()
|
|
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
it 'returns 422 if no token or id is provided', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync url
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 422 if the token is invalid', utils.wrap (done) ->
|
|
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(400, {})
|
|
yield new User({name: 'someone', gplusID: '1234'}).save()
|
|
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(422)
|
|
done()
|
|
|
|
it 'returns 404 if the user does not already exist', utils.wrap (done) ->
|
|
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
|
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
|
expect(res.statusCode).toBe(404)
|
|
done()
|
|
|
|
|
|
describe 'POST /auth/spy', ->
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
@admin = yield utils.initAdmin()
|
|
@user1 = yield utils.initUser({name: 'Test User 1'})
|
|
@user2 = yield utils.initUser({name: 'Test User 2'})
|
|
done()
|
|
|
|
it 'logs in an admin as an arbitrary user', utils.wrap (done) ->
|
|
yield utils.loginUser(@admin)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user1.id}}
|
|
expect(res.statusCode).toBe(200)
|
|
expect(body._id).toBe(@user1.id)
|
|
[res, body] = yield request.getAsync {uri: getURL('/auth/whoami'), json: true}
|
|
expect(body._id).toBe(@user1.id)
|
|
done()
|
|
|
|
it 'accepts the user\'s email as input', utils.wrap (done) ->
|
|
yield utils.loginUser(@admin)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user1.get('email')}}
|
|
expect(res.statusCode).toBe(200)
|
|
expect(body._id).toBe(@user1.id)
|
|
done()
|
|
|
|
it 'accepts the user\'s username as input', utils.wrap (done) ->
|
|
yield utils.loginUser(@admin)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user1.get('name')}}
|
|
expect(res.statusCode).toBe(200)
|
|
expect(body._id).toBe(@user1.id)
|
|
done()
|
|
|
|
it 'does not work for anonymous users', utils.wrap (done) ->
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user1.get('name')}}
|
|
expect(res.statusCode).toBe(401)
|
|
done()
|
|
|
|
it 'does not work for non-admins', utils.wrap (done) ->
|
|
yield utils.loginUser(@user1)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user1.get('name')}}
|
|
expect(res.statusCode).toBe(403)
|
|
done()
|
|
|
|
describe 'POST /auth/stop-spying', ->
|
|
beforeEach utils.wrap (done) ->
|
|
yield utils.clearModels([User])
|
|
@admin = yield utils.initAdmin()
|
|
@user = yield utils.initUser()
|
|
yield utils.loginUser(@admin)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/spy'), json: {user: @user.id}}
|
|
expect(res.statusCode).toBe(200)
|
|
done()
|
|
|
|
it 'it reverts the spying user back to the admin', utils.wrap (done) ->
|
|
[res, body] = yield request.getAsync {uri: getURL('/auth/whoami'), json: true}
|
|
expect(body._id).toBe(@user.id)
|
|
[res, body] = yield request.postAsync {uri: getURL('/auth/stop-spying'), json: true}
|
|
expect(res.statusCode).toBe(200)
|
|
expect(body._id).toBe(@admin.id)
|
|
[res, body] = yield request.getAsync {uri: getURL('/auth/whoami'), json: true}
|
|
expect(body._id).toBe(@admin.id)
|
|
done()
|