AuthenticationControllerTests.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. /* eslint-disable
  2. handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const sinon = require('sinon')
  16. const chai = require('chai')
  17. const should = chai.should()
  18. const { expect } = chai
  19. const modulePath =
  20. '../../../../app/src/Features/Authentication/AuthenticationController.js'
  21. const SandboxedModule = require('sandboxed-module')
  22. const events = require('events')
  23. const tk = require('timekeeper')
  24. const MockRequest = require('../helpers/MockRequest')
  25. const MockResponse = require('../helpers/MockResponse')
  26. const { ObjectId } = require('mongojs')
  27. describe('AuthenticationController', function() {
  28. beforeEach(function() {
  29. tk.freeze(Date.now())
  30. this.UserModel = { findOne: sinon.stub() }
  31. this.AuthenticationController = SandboxedModule.require(modulePath, {
  32. globals: {
  33. console: console
  34. },
  35. requires: {
  36. './AuthenticationManager': (this.AuthenticationManager = {}),
  37. '../User/UserUpdater': (this.UserUpdater = {
  38. updateUser: sinon.stub()
  39. }),
  40. 'metrics-sharelatex': (this.Metrics = { inc: sinon.stub() }),
  41. '../Security/LoginRateLimiter': (this.LoginRateLimiter = {
  42. processLoginRequest: sinon.stub(),
  43. recordSuccessfulLogin: sinon.stub()
  44. }),
  45. '../User/UserHandler': (this.UserHandler = {
  46. setupLoginData: sinon.stub()
  47. }),
  48. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  49. recordEvent: sinon.stub()
  50. }),
  51. 'logger-sharelatex': (this.logger = {
  52. log: sinon.stub(),
  53. warn: sinon.stub(),
  54. error: sinon.stub(),
  55. err: sinon.stub()
  56. }),
  57. 'settings-sharelatex': { siteUrl: 'http://www.foo.bar' },
  58. passport: (this.passport = {
  59. authenticate: sinon.stub().returns(sinon.stub())
  60. }),
  61. '../User/UserSessionsManager': (this.UserSessionsManager = {
  62. trackSession: sinon.stub(),
  63. untrackSession: sinon.stub(),
  64. revokeAllUserSessions: sinon.stub().callsArgWith(1, null)
  65. }),
  66. '../../infrastructure/Modules': (this.Modules = {
  67. hooks: { fire: sinon.stub().callsArgWith(2, null, []) }
  68. }),
  69. '../SudoMode/SudoModeHandler': (this.SudoModeHandler = {
  70. activateSudoMode: sinon.stub().callsArgWith(1, null)
  71. }),
  72. '../Notifications/NotificationsBuilder': (this.NotificationsBuilder = {
  73. ipMatcherAffiliation: sinon.stub()
  74. }),
  75. '../../models/User': { User: this.UserModel },
  76. '../../../../modules/oauth2-server/app/src/Oauth2Server': (this.Oauth2Server = {
  77. Request: sinon.stub(),
  78. Response: sinon.stub(),
  79. server: {
  80. authenticate: sinon.stub()
  81. }
  82. })
  83. }
  84. })
  85. this.user = {
  86. _id: ObjectId(),
  87. email: (this.email = 'USER@example.com'),
  88. first_name: 'bob',
  89. last_name: 'brown',
  90. referal_id: 1234,
  91. isAdmin: false
  92. }
  93. this.password = 'banana'
  94. this.req = new MockRequest()
  95. this.res = new MockResponse()
  96. this.callback = sinon.stub()
  97. this.next = sinon.stub()
  98. })
  99. afterEach(function() {
  100. return tk.reset()
  101. })
  102. describe('isUserLoggedIn', function() {
  103. beforeEach(function() {
  104. return (this.stub = sinon.stub(
  105. this.AuthenticationController,
  106. 'getLoggedInUserId'
  107. ))
  108. })
  109. afterEach(function() {
  110. return this.stub.restore()
  111. })
  112. it('should do the right thing in all cases', function() {
  113. this.AuthenticationController.getLoggedInUserId.returns('some_id')
  114. expect(this.AuthenticationController.isUserLoggedIn(this.req)).to.equal(
  115. true
  116. )
  117. this.AuthenticationController.getLoggedInUserId.returns(null)
  118. expect(this.AuthenticationController.isUserLoggedIn(this.req)).to.equal(
  119. false
  120. )
  121. this.AuthenticationController.getLoggedInUserId.returns(false)
  122. expect(this.AuthenticationController.isUserLoggedIn(this.req)).to.equal(
  123. false
  124. )
  125. this.AuthenticationController.getLoggedInUserId.returns(undefined)
  126. return expect(
  127. this.AuthenticationController.isUserLoggedIn(this.req)
  128. ).to.equal(false)
  129. })
  130. })
  131. describe('setInSessionUser', function() {
  132. beforeEach(function() {
  133. this.user = {
  134. _id: 'id',
  135. first_name: 'a',
  136. last_name: 'b',
  137. email: 'c'
  138. }
  139. this.req.session.passport = { user: this.user }
  140. return (this.req.session.user = this.user)
  141. })
  142. it('should update the right properties', function() {
  143. this.AuthenticationController.setInSessionUser(this.req, {
  144. first_name: 'new_first_name',
  145. email: 'new_email'
  146. })
  147. const expectedUser = {
  148. _id: 'id',
  149. first_name: 'new_first_name',
  150. last_name: 'b',
  151. email: 'new_email'
  152. }
  153. expect(this.req.session.passport.user).to.deep.equal(expectedUser)
  154. return expect(this.req.session.user).to.deep.equal(expectedUser)
  155. })
  156. })
  157. describe('passportLogin', function() {
  158. beforeEach(function() {
  159. this.info = null
  160. this.req.login = sinon.stub().callsArgWith(1, null)
  161. this.res.json = sinon.stub()
  162. this.req.session = this.session = {
  163. passport: { user: this.user },
  164. postLoginRedirect: '/path/to/redir/to'
  165. }
  166. this.req.session.destroy = sinon.stub().callsArgWith(0, null)
  167. this.req.session.save = sinon.stub().callsArgWith(0, null)
  168. this.req.sessionStore = { generate: sinon.stub() }
  169. this.AuthenticationController.finishLogin = sinon.stub()
  170. this.passport.authenticate.callsArgWith(1, null, this.user, this.info)
  171. return (this.err = new Error('woops'))
  172. })
  173. it('should call passport.authenticate', function() {
  174. this.AuthenticationController.passportLogin(this.req, this.res, this.next)
  175. return this.passport.authenticate.callCount.should.equal(1)
  176. })
  177. describe('when authenticate produces an error', function() {
  178. beforeEach(function() {
  179. return this.passport.authenticate.callsArgWith(1, this.err)
  180. })
  181. it('should return next with an error', function() {
  182. this.AuthenticationController.passportLogin(
  183. this.req,
  184. this.res,
  185. this.next
  186. )
  187. return this.next.calledWith(this.err).should.equal(true)
  188. })
  189. })
  190. describe('when authenticate produces a user', function() {
  191. beforeEach(function() {
  192. this.req.session.postLoginRedirect = 'some_redirect'
  193. return this.passport.authenticate.callsArgWith(
  194. 1,
  195. null,
  196. this.user,
  197. this.info
  198. )
  199. })
  200. afterEach(function() {
  201. return delete this.req.session.postLoginRedirect
  202. })
  203. it('should call finishLogin', function() {
  204. this.AuthenticationController.passportLogin(
  205. this.req,
  206. this.res,
  207. this.next
  208. )
  209. this.AuthenticationController.finishLogin.callCount.should.equal(1)
  210. return this.AuthenticationController.finishLogin
  211. .calledWith(this.user)
  212. .should.equal(true)
  213. })
  214. })
  215. describe('when authenticate does not produce a user', function() {
  216. beforeEach(function() {
  217. this.info = { text: 'a', type: 'b' }
  218. return this.passport.authenticate.callsArgWith(
  219. 1,
  220. null,
  221. false,
  222. this.info
  223. )
  224. })
  225. it('should not call finishLogin', function() {
  226. this.AuthenticationController.passportLogin(
  227. this.req,
  228. this.res,
  229. this.next
  230. )
  231. return this.AuthenticationController.finishLogin.callCount.should.equal(
  232. 0
  233. )
  234. })
  235. it('should not send a json response with redirect', function() {
  236. this.AuthenticationController.passportLogin(
  237. this.req,
  238. this.res,
  239. this.next
  240. )
  241. this.res.json.callCount.should.equal(1)
  242. this.res.json.calledWith({ message: this.info }).should.equal(true)
  243. return expect(this.res.json.lastCall.args[0].redir != null).to.equal(
  244. false
  245. )
  246. })
  247. })
  248. })
  249. describe('afterLoginSessionSetup', function() {
  250. beforeEach(function() {
  251. this.req.login = sinon.stub().callsArgWith(1, null)
  252. this.req.session = this.session = { passport: { user: this.user } }
  253. this.req.session = { passport: { user: { _id: 'one' } } }
  254. this.req.session.destroy = sinon.stub().callsArgWith(0, null)
  255. this.req.session.save = sinon.stub().callsArgWith(0, null)
  256. this.req.sessionStore = { generate: sinon.stub() }
  257. this.UserSessionsManager.trackSession = sinon.stub()
  258. return (this.call = callback => {
  259. return this.AuthenticationController.afterLoginSessionSetup(
  260. this.req,
  261. this.user,
  262. callback
  263. )
  264. })
  265. })
  266. it('should not produce an error', function(done) {
  267. return this.call(err => {
  268. expect(err).to.equal(null)
  269. return done()
  270. })
  271. })
  272. it('should call req.login', function(done) {
  273. return this.call(err => {
  274. this.req.login.callCount.should.equal(1)
  275. return done()
  276. })
  277. })
  278. it('should call req.session.save', function(done) {
  279. return this.call(err => {
  280. this.req.session.save.callCount.should.equal(1)
  281. return done()
  282. })
  283. })
  284. it('should call UserSessionsManager.trackSession', function(done) {
  285. return this.call(err => {
  286. this.UserSessionsManager.trackSession.callCount.should.equal(1)
  287. return done()
  288. })
  289. })
  290. describe('when req.session.save produces an error', function() {
  291. beforeEach(function() {
  292. return (this.req.session.save = sinon
  293. .stub()
  294. .callsArgWith(0, new Error('woops')))
  295. })
  296. it('should produce an error', function(done) {
  297. return this.call(err => {
  298. expect(err).to.not.be.oneOf([null, undefined])
  299. expect(err).to.be.instanceof(Error)
  300. return done()
  301. })
  302. })
  303. it('should not call UserSessionsManager.trackSession', function(done) {
  304. return this.call(err => {
  305. this.UserSessionsManager.trackSession.callCount.should.equal(0)
  306. return done()
  307. })
  308. })
  309. })
  310. })
  311. describe('getSessionUser', function() {
  312. it('should get the user object from session', function() {
  313. this.req.session = {
  314. passport: {
  315. user: { _id: 'one' }
  316. }
  317. }
  318. const user = this.AuthenticationController.getSessionUser(this.req)
  319. return expect(user).to.deep.equal({ _id: 'one' })
  320. })
  321. it('should work with legacy sessions', function() {
  322. this.req.session = { user: { _id: 'one' } }
  323. const user = this.AuthenticationController.getSessionUser(this.req)
  324. return expect(user).to.deep.equal({ _id: 'one' })
  325. })
  326. })
  327. describe('doPassportLogin', function() {
  328. beforeEach(function() {
  329. this.AuthenticationController._recordFailedLogin = sinon.stub()
  330. this.AuthenticationController._recordSuccessfulLogin = sinon.stub()
  331. this.Modules.hooks.fire = sinon.stub().callsArgWith(3, null, [])
  332. // @AuthenticationController.establishUserSession = sinon.stub().callsArg(2)
  333. this.req.body = {
  334. email: this.email,
  335. password: this.password,
  336. session: {
  337. postLoginRedirect: '/path/to/redir/to'
  338. }
  339. }
  340. return (this.cb = sinon.stub())
  341. })
  342. describe('when the preDoPassportLogin hooks produce an info object', function() {
  343. beforeEach(function() {
  344. return (this.Modules.hooks.fire = sinon
  345. .stub()
  346. .callsArgWith(3, null, [null, { redir: '/somewhere' }, null]))
  347. })
  348. it('should stop early and call done with this info object', function(done) {
  349. this.AuthenticationController.doPassportLogin(
  350. this.req,
  351. this.req.body.email,
  352. this.req.body.password,
  353. this.cb
  354. )
  355. this.cb.callCount.should.equal(1)
  356. this.cb
  357. .calledWith(null, false, { redir: '/somewhere' })
  358. .should.equal(true)
  359. this.LoginRateLimiter.processLoginRequest.callCount.should.equal(0)
  360. return done()
  361. })
  362. })
  363. describe('when the users rate limit', function() {
  364. beforeEach(function() {
  365. return this.LoginRateLimiter.processLoginRequest.callsArgWith(
  366. 1,
  367. null,
  368. false
  369. )
  370. })
  371. it('should block the request if the limit has been exceeded', function(done) {
  372. this.AuthenticationController.doPassportLogin(
  373. this.req,
  374. this.req.body.email,
  375. this.req.body.password,
  376. this.cb
  377. )
  378. this.cb.callCount.should.equal(1)
  379. this.cb.calledWith(null, null).should.equal(true)
  380. return done()
  381. })
  382. })
  383. describe('when the user is authenticated', function() {
  384. beforeEach(function() {
  385. this.cb = sinon.stub()
  386. this.LoginRateLimiter.processLoginRequest.callsArgWith(1, null, true)
  387. this.AuthenticationManager.authenticate = sinon
  388. .stub()
  389. .callsArgWith(2, null, this.user)
  390. this.req.sessionID = Math.random()
  391. return this.AuthenticationController.doPassportLogin(
  392. this.req,
  393. this.req.body.email,
  394. this.req.body.password,
  395. this.cb
  396. )
  397. })
  398. it('should attempt to authorise the user', function() {
  399. return this.AuthenticationManager.authenticate
  400. .calledWith({ email: this.email.toLowerCase() }, this.password)
  401. .should.equal(true)
  402. })
  403. it("should establish the user's session", function() {
  404. return this.cb.calledWith(null, this.user).should.equal(true)
  405. })
  406. })
  407. describe('_loginAsyncHandlers', function() {
  408. beforeEach(function() {
  409. this.UserHandler.setupLoginData = sinon.stub()
  410. this.LoginRateLimiter.recordSuccessfulLogin = sinon.stub()
  411. this.AuthenticationController._recordSuccessfulLogin = sinon.stub()
  412. this.AnalyticsManager.recordEvent = sinon.stub()
  413. this.AnalyticsManager.identifyUser = sinon.stub()
  414. return this.AuthenticationController._loginAsyncHandlers(
  415. this.req,
  416. this.user
  417. )
  418. })
  419. it('should call identifyUser', function() {
  420. return this.AnalyticsManager.identifyUser
  421. .calledWith(this.user._id, this.req.sessionID)
  422. .should.equal(true)
  423. })
  424. it('should setup the user data in the background', function() {
  425. return this.UserHandler.setupLoginData
  426. .calledWith(this.user)
  427. .should.equal(true)
  428. })
  429. it('should set res.session.justLoggedIn', function() {
  430. return this.req.session.justLoggedIn.should.equal(true)
  431. })
  432. it('should record the successful login', function() {
  433. return this.AuthenticationController._recordSuccessfulLogin
  434. .calledWith(this.user._id)
  435. .should.equal(true)
  436. })
  437. it('should tell the rate limiter that there was a success for that email', function() {
  438. return this.LoginRateLimiter.recordSuccessfulLogin
  439. .calledWith(this.user.email)
  440. .should.equal(true)
  441. })
  442. it('should log the successful login', function() {
  443. return this.logger.log
  444. .calledWith(
  445. { email: this.user.email, user_id: this.user._id.toString() },
  446. 'successful log in'
  447. )
  448. .should.equal(true)
  449. })
  450. it('should track the login event', function() {
  451. return this.AnalyticsManager.recordEvent
  452. .calledWith(this.user._id, 'user-logged-in')
  453. .should.equal(true)
  454. })
  455. })
  456. describe('when the user is not authenticated', function() {
  457. beforeEach(function() {
  458. this.LoginRateLimiter.processLoginRequest.callsArgWith(1, null, true)
  459. this.AuthenticationManager.authenticate = sinon
  460. .stub()
  461. .callsArgWith(2, null, null)
  462. this.cb = sinon.stub()
  463. return this.AuthenticationController.doPassportLogin(
  464. this.req,
  465. this.req.body.email,
  466. this.req.body.password,
  467. this.cb
  468. )
  469. })
  470. it('should not establish the login', function() {
  471. this.cb.callCount.should.equal(1)
  472. this.cb.calledWith(null, false)
  473. // @res.body.should.exist
  474. return expect(this.cb.lastCall.args[2]).to.contain.all.keys([
  475. 'text',
  476. 'type'
  477. ])
  478. })
  479. // message:
  480. // text: 'Your email or password were incorrect. Please try again',
  481. // type: 'error'
  482. it('should not setup the user data in the background', function() {
  483. return this.UserHandler.setupLoginData.called.should.equal(false)
  484. })
  485. it('should record a failed login', function() {
  486. return this.AuthenticationController._recordFailedLogin.called.should.equal(
  487. true
  488. )
  489. })
  490. it('should log the failed login', function() {
  491. return this.logger.log
  492. .calledWith({ email: this.email.toLowerCase() }, 'failed log in')
  493. .should.equal(true)
  494. })
  495. })
  496. })
  497. describe('getLoggedInUserId', function() {
  498. beforeEach(function() {
  499. return (this.req = { session: {} })
  500. })
  501. it('should return the user id from the session', function() {
  502. this.user_id = '2134'
  503. this.req.session.user = { _id: this.user_id }
  504. const result = this.AuthenticationController.getLoggedInUserId(this.req)
  505. return expect(result).to.equal(this.user_id)
  506. })
  507. it('should return user for passport session', function() {
  508. this.user_id = '2134'
  509. this.req.session = {
  510. passport: {
  511. user: {
  512. _id: this.user_id
  513. }
  514. }
  515. }
  516. const result = this.AuthenticationController.getLoggedInUserId(this.req)
  517. return expect(result).to.equal(this.user_id)
  518. })
  519. it('should return null if there is no user on the session', function() {
  520. const result = this.AuthenticationController.getLoggedInUserId(this.req)
  521. return expect(result).to.equal(null)
  522. })
  523. it('should return null if there is no session', function() {
  524. this.req = {}
  525. const result = this.AuthenticationController.getLoggedInUserId(this.req)
  526. return expect(result).to.equal(null)
  527. })
  528. it('should return null if there is no req', function() {
  529. this.req = {}
  530. const result = this.AuthenticationController.getLoggedInUserId(this.req)
  531. return expect(result).to.equal(null)
  532. })
  533. })
  534. describe('requireLogin', function() {
  535. beforeEach(function() {
  536. this.user = {
  537. _id: 'user-id-123',
  538. email: 'user@sharelatex.com'
  539. }
  540. return (this.middleware = this.AuthenticationController.requireLogin())
  541. })
  542. describe('when the user is logged in', function() {
  543. beforeEach(function() {
  544. this.req.session = {
  545. user: (this.user = {
  546. _id: 'user-id-123',
  547. email: 'user@sharelatex.com'
  548. })
  549. }
  550. return this.middleware(this.req, this.res, this.next)
  551. })
  552. it('should call the next method in the chain', function() {
  553. return this.next.called.should.equal(true)
  554. })
  555. })
  556. describe('when the user is not logged in', function() {
  557. beforeEach(function() {
  558. this.req.session = {}
  559. this.AuthenticationController._redirectToLoginOrRegisterPage = sinon.stub()
  560. this.req.query = {}
  561. return this.middleware(this.req, this.res, this.next)
  562. })
  563. it('should redirect to the register or login page', function() {
  564. return this.AuthenticationController._redirectToLoginOrRegisterPage
  565. .calledWith(this.req, this.res)
  566. .should.equal(true)
  567. })
  568. })
  569. })
  570. describe('requireOauth', function() {
  571. beforeEach(function() {
  572. this.res.send = sinon.stub()
  573. this.res.status = sinon.stub().returns(this.res)
  574. this.res.sendStatus = sinon.stub()
  575. this.middleware = this.AuthenticationController.requireOauth()
  576. })
  577. describe('when Oauth2Server authenticates', function() {
  578. beforeEach(function() {
  579. this.token = {
  580. accessToken: 'token',
  581. user: 'user'
  582. }
  583. this.Oauth2Server.server.authenticate.yields(null, this.token)
  584. this.middleware(this.req, this.res, this.next)
  585. })
  586. it('should set oauth_token on request', function() {
  587. this.req.oauth_token.should.equal(this.token)
  588. })
  589. it('should set oauth on request', function() {
  590. this.req.oauth.access_token.should.equal(this.token.accessToken)
  591. })
  592. it('should set oauth_user on request', function() {
  593. this.req.oauth_user.should.equal('user')
  594. })
  595. it('should call next', function() {
  596. this.next.should.have.been.calledOnce
  597. })
  598. })
  599. describe('when Oauth2Server returns 401 error', function() {
  600. beforeEach(function() {
  601. this.Oauth2Server.server.authenticate.yields({ code: 401 })
  602. this.middleware(this.req, this.res, this.next)
  603. })
  604. it('should return 401 error', function() {
  605. this.res.status.should.have.been.calledWith(401)
  606. })
  607. it('should not call next', function() {
  608. this.next.should.have.not.been.calledOnce
  609. })
  610. })
  611. })
  612. describe('requireGlobalLogin', function() {
  613. beforeEach(function() {
  614. this.req.headers = {}
  615. this.AuthenticationController.httpAuth = sinon.stub()
  616. return (this.setRedirect = sinon.spy(
  617. this.AuthenticationController,
  618. 'setRedirectInSession'
  619. ))
  620. })
  621. afterEach(function() {
  622. return this.setRedirect.restore()
  623. })
  624. describe('with white listed url', function() {
  625. beforeEach(function() {
  626. this.AuthenticationController.addEndpointToLoginWhitelist('/login')
  627. this.req._parsedUrl.pathname = '/login'
  628. return this.AuthenticationController.requireGlobalLogin(
  629. this.req,
  630. this.res,
  631. this.next
  632. )
  633. })
  634. it('should call next() directly', function() {
  635. return this.next.called.should.equal(true)
  636. })
  637. })
  638. describe('with white listed url and a query string', function() {
  639. beforeEach(function() {
  640. this.AuthenticationController.addEndpointToLoginWhitelist('/login')
  641. this.req._parsedUrl.pathname = '/login'
  642. this.req.url = '/login?query=something'
  643. return this.AuthenticationController.requireGlobalLogin(
  644. this.req,
  645. this.res,
  646. this.next
  647. )
  648. })
  649. it('should call next() directly', function() {
  650. return this.next.called.should.equal(true)
  651. })
  652. })
  653. describe('with http auth', function() {
  654. beforeEach(function() {
  655. this.req.headers['authorization'] = 'Mock Basic Auth'
  656. return this.AuthenticationController.requireGlobalLogin(
  657. this.req,
  658. this.res,
  659. this.next
  660. )
  661. })
  662. it('should pass the request onto httpAuth', function() {
  663. return this.AuthenticationController.httpAuth
  664. .calledWith(this.req, this.res, this.next)
  665. .should.equal(true)
  666. })
  667. })
  668. describe('with a user session', function() {
  669. beforeEach(function() {
  670. this.req.session = { user: { mock: 'user', _id: 'some_id' } }
  671. return this.AuthenticationController.requireGlobalLogin(
  672. this.req,
  673. this.res,
  674. this.next
  675. )
  676. })
  677. it('should call next() directly', function() {
  678. return this.next.called.should.equal(true)
  679. })
  680. })
  681. describe('with no login credentials', function() {
  682. beforeEach(function() {
  683. this.req.session = {}
  684. return this.AuthenticationController.requireGlobalLogin(
  685. this.req,
  686. this.res,
  687. this.next
  688. )
  689. })
  690. it('should have called setRedirectInSession', function() {
  691. return this.setRedirect.callCount.should.equal(1)
  692. })
  693. it('should redirect to the /login page', function() {
  694. return this.res.redirectedTo.should.equal('/login')
  695. })
  696. })
  697. })
  698. describe('_redirectToLoginOrRegisterPage', function() {
  699. beforeEach(function() {
  700. this.middleware = this.AuthenticationController.requireLogin(
  701. (this.options = { load_from_db: false })
  702. )
  703. this.req.session = {}
  704. this.AuthenticationController._redirectToRegisterPage = sinon.stub()
  705. this.AuthenticationController._redirectToLoginPage = sinon.stub()
  706. return (this.req.query = {})
  707. })
  708. describe('they have come directly to the url', function() {
  709. beforeEach(function() {
  710. this.req.query = {}
  711. return this.middleware(this.req, this.res, this.next)
  712. })
  713. it('should redirect to the login page', function() {
  714. this.AuthenticationController._redirectToRegisterPage
  715. .calledWith(this.req, this.res)
  716. .should.equal(false)
  717. return this.AuthenticationController._redirectToLoginPage
  718. .calledWith(this.req, this.res)
  719. .should.equal(true)
  720. })
  721. })
  722. describe('they have come via a templates link', function() {
  723. beforeEach(function() {
  724. this.req.query.zipUrl = 'something'
  725. return this.middleware(this.req, this.res, this.next)
  726. })
  727. it('should redirect to the register page', function() {
  728. this.AuthenticationController._redirectToRegisterPage
  729. .calledWith(this.req, this.res)
  730. .should.equal(true)
  731. return this.AuthenticationController._redirectToLoginPage
  732. .calledWith(this.req, this.res)
  733. .should.equal(false)
  734. })
  735. })
  736. describe('they have been invited to a project', function() {
  737. beforeEach(function() {
  738. this.req.query.project_name = 'something'
  739. return this.middleware(this.req, this.res, this.next)
  740. })
  741. it('should redirect to the register page', function() {
  742. this.AuthenticationController._redirectToRegisterPage
  743. .calledWith(this.req, this.res)
  744. .should.equal(true)
  745. return this.AuthenticationController._redirectToLoginPage
  746. .calledWith(this.req, this.res)
  747. .should.equal(false)
  748. })
  749. })
  750. })
  751. describe('_redirectToRegisterPage', function() {
  752. beforeEach(function() {
  753. this.req.path = '/target/url'
  754. this.req.query = { extra_query: 'foo' }
  755. return this.AuthenticationController._redirectToRegisterPage(
  756. this.req,
  757. this.res
  758. )
  759. })
  760. it('should redirect to the register page with a query string attached', function() {
  761. this.req.session.postLoginRedirect.should.equal(
  762. '/target/url?extra_query=foo'
  763. )
  764. return this.res.redirectedTo.should.equal('/register?extra_query=foo')
  765. })
  766. it('should log out a message', function() {
  767. return this.logger.log
  768. .calledWith(
  769. { url: this.url },
  770. 'user not logged in so redirecting to register page'
  771. )
  772. .should.equal(true)
  773. })
  774. })
  775. describe('_redirectToLoginPage', function() {
  776. beforeEach(function() {
  777. this.req.path = '/target/url'
  778. this.req.query = { extra_query: 'foo' }
  779. return this.AuthenticationController._redirectToLoginPage(
  780. this.req,
  781. this.res
  782. )
  783. })
  784. it('should redirect to the register page with a query string attached', function() {
  785. this.req.session.postLoginRedirect.should.equal(
  786. '/target/url?extra_query=foo'
  787. )
  788. return this.res.redirectedTo.should.equal('/login?extra_query=foo')
  789. })
  790. })
  791. describe('_recordSuccessfulLogin', function() {
  792. beforeEach(function() {
  793. this.UserUpdater.updateUser = sinon.stub().callsArg(2)
  794. return this.AuthenticationController._recordSuccessfulLogin(
  795. this.user._id,
  796. this.callback
  797. )
  798. })
  799. it('should increment the user.login.success metric', function() {
  800. return this.Metrics.inc
  801. .calledWith('user.login.success')
  802. .should.equal(true)
  803. })
  804. it("should update the user's login count and last logged in date", function() {
  805. this.UserUpdater.updateUser.args[0][1]['$set'][
  806. 'lastLoggedIn'
  807. ].should.not.equal(undefined)
  808. return this.UserUpdater.updateUser.args[0][1]['$inc'][
  809. 'loginCount'
  810. ].should.equal(1)
  811. })
  812. it('should call the callback', function() {
  813. return this.callback.called.should.equal(true)
  814. })
  815. })
  816. describe('_recordFailedLogin', function() {
  817. beforeEach(function() {
  818. return this.AuthenticationController._recordFailedLogin(this.callback)
  819. })
  820. it('should increment the user.login.failed metric', function() {
  821. return this.Metrics.inc.calledWith('user.login.failed').should.equal(true)
  822. })
  823. it('should call the callback', function() {
  824. return this.callback.called.should.equal(true)
  825. })
  826. })
  827. describe('setRedirectInSession', function() {
  828. beforeEach(function() {
  829. this.req = { session: {} }
  830. this.req.path = '/somewhere'
  831. return (this.req.query = { one: '1' })
  832. })
  833. it('should set redirect property on session', function() {
  834. this.AuthenticationController.setRedirectInSession(this.req)
  835. return expect(this.req.session.postLoginRedirect).to.equal(
  836. '/somewhere?one=1'
  837. )
  838. })
  839. it('should set the supplied value', function() {
  840. this.AuthenticationController.setRedirectInSession(
  841. this.req,
  842. '/somewhere/specific'
  843. )
  844. return expect(this.req.session.postLoginRedirect).to.equal(
  845. '/somewhere/specific'
  846. )
  847. })
  848. it('should not allow open redirects', function() {
  849. this.AuthenticationController.setRedirectInSession(
  850. this.req,
  851. 'https://evil.com'
  852. )
  853. return expect(this.req.session.postLoginRedirect).to.be.undefined
  854. })
  855. describe('with a png', function() {
  856. beforeEach(function() {
  857. return (this.req = { session: {} })
  858. })
  859. it('should not set the redirect', function() {
  860. this.AuthenticationController.setRedirectInSession(
  861. this.req,
  862. '/something.png'
  863. )
  864. return expect(this.req.session.postLoginRedirect).to.equal(undefined)
  865. })
  866. })
  867. describe('with a js path', function() {
  868. beforeEach(function() {
  869. return (this.req = { session: {} })
  870. })
  871. it('should not set the redirect', function() {
  872. this.AuthenticationController.setRedirectInSession(
  873. this.req,
  874. '/js/something.js'
  875. )
  876. return expect(this.req.session.postLoginRedirect).to.equal(undefined)
  877. })
  878. })
  879. })
  880. describe('_getRedirectFromSession', function() {
  881. it('should get redirect property from session', function() {
  882. this.req = { session: { postLoginRedirect: '/a?b=c' } }
  883. return expect(
  884. this.AuthenticationController._getRedirectFromSession(this.req)
  885. ).to.equal('/a?b=c')
  886. })
  887. it('should not allow open redirects', function() {
  888. this.req = { session: { postLoginRedirect: 'https://evil.com' } }
  889. return expect(
  890. this.AuthenticationController._getRedirectFromSession(this.req)
  891. ).to.be.null
  892. })
  893. it('handle null values', function() {
  894. this.req = { session: {} }
  895. return expect(
  896. this.AuthenticationController._getRedirectFromSession(this.req)
  897. ).to.be.null
  898. })
  899. })
  900. describe('_getSafeRedirectPath', function() {
  901. it('sanitize redirect path to prevent open redirects', function() {
  902. expect(
  903. this.AuthenticationController._getSafeRedirectPath('https://evil.com')
  904. ).to.be.undefined
  905. expect(this.AuthenticationController._getSafeRedirectPath('//evil.com'))
  906. .to.be.undefined
  907. expect(
  908. this.AuthenticationController._getSafeRedirectPath('//ol.com/evil')
  909. ).to.equal('/evil')
  910. expect(this.AuthenticationController._getSafeRedirectPath('////evil.com'))
  911. .to.be.undefined
  912. expect(
  913. this.AuthenticationController._getSafeRedirectPath('%2F%2Fevil.com')
  914. ).to.equal('/%2F%2Fevil.com')
  915. return expect(
  916. this.AuthenticationController._getSafeRedirectPath('.evil.com')
  917. ).to.equal('/.evil.com')
  918. })
  919. })
  920. describe('_clearRedirectFromSession', function() {
  921. beforeEach(function() {
  922. return (this.req = { session: { postLoginRedirect: '/a?b=c' } })
  923. })
  924. it('should remove the redirect property from session', function() {
  925. this.AuthenticationController._clearRedirectFromSession(this.req)
  926. return expect(this.req.session.postLoginRedirect).to.equal(undefined)
  927. })
  928. })
  929. describe('finishLogin', function() {
  930. // - get redirect
  931. // - async handlers
  932. // - afterLoginSessionSetup
  933. // - clear redirect
  934. // - issue redir, two ways
  935. beforeEach(function() {
  936. this.AuthenticationController._getRedirectFromSession = sinon
  937. .stub()
  938. .returns('/some/page')
  939. this.AuthenticationController._loginAsyncHandlers = sinon.stub()
  940. this.AuthenticationController.afterLoginSessionSetup = sinon
  941. .stub()
  942. .callsArgWith(2, null)
  943. this.AuthenticationController._clearRedirectFromSession = sinon.stub()
  944. this.AuthenticationController._redirectToReconfirmPage = sinon.stub()
  945. this.req.headers = { accept: 'application/json, whatever' }
  946. this.res.json = sinon.stub()
  947. return (this.res.redirect = sinon.stub())
  948. })
  949. it('should extract the redirect from the session', function() {
  950. this.AuthenticationController.finishLogin(
  951. this.user,
  952. this.req,
  953. this.res,
  954. this.next
  955. )
  956. expect(
  957. this.AuthenticationController._getRedirectFromSession.callCount
  958. ).to.equal(1)
  959. return expect(
  960. this.AuthenticationController._getRedirectFromSession.calledWith(
  961. this.req
  962. )
  963. ).to.equal(true)
  964. })
  965. it('should call the async handlers', function() {
  966. this.AuthenticationController.finishLogin(
  967. this.user,
  968. this.req,
  969. this.res,
  970. this.next
  971. )
  972. expect(
  973. this.AuthenticationController._loginAsyncHandlers.callCount
  974. ).to.equal(1)
  975. return expect(
  976. this.AuthenticationController._loginAsyncHandlers.calledWith(
  977. this.req,
  978. this.user
  979. )
  980. ).to.equal(true)
  981. })
  982. it('should call afterLoginSessionSetup', function() {
  983. this.AuthenticationController.finishLogin(
  984. this.user,
  985. this.req,
  986. this.res,
  987. this.next
  988. )
  989. expect(
  990. this.AuthenticationController.afterLoginSessionSetup.callCount
  991. ).to.equal(1)
  992. return expect(
  993. this.AuthenticationController.afterLoginSessionSetup.calledWith(
  994. this.req,
  995. this.user
  996. )
  997. ).to.equal(true)
  998. })
  999. it('should clear redirect from session', function() {
  1000. this.AuthenticationController.finishLogin(
  1001. this.user,
  1002. this.req,
  1003. this.res,
  1004. this.next
  1005. )
  1006. expect(
  1007. this.AuthenticationController._clearRedirectFromSession.callCount
  1008. ).to.equal(1)
  1009. return expect(
  1010. this.AuthenticationController._clearRedirectFromSession.calledWith(
  1011. this.req
  1012. )
  1013. ).to.equal(true)
  1014. })
  1015. it('should issue a json response with a redirect', function() {
  1016. this.AuthenticationController.finishLogin(
  1017. this.user,
  1018. this.req,
  1019. this.res,
  1020. this.next
  1021. )
  1022. expect(this.res.json.callCount).to.equal(1)
  1023. expect(this.res.redirect.callCount).to.equal(0)
  1024. return expect(this.res.json.calledWith({ redir: '/some/page' })).to.equal(
  1025. true
  1026. )
  1027. })
  1028. describe('with a non-json request', function() {
  1029. beforeEach(function() {
  1030. this.req.headers = {}
  1031. this.res.json = sinon.stub()
  1032. return (this.res.redirect = sinon.stub())
  1033. })
  1034. it('should issue a plain redirect', function() {
  1035. this.AuthenticationController.finishLogin(
  1036. this.user,
  1037. this.req,
  1038. this.res,
  1039. this.next
  1040. )
  1041. expect(this.res.json.callCount).to.equal(0)
  1042. expect(this.res.redirect.callCount).to.equal(1)
  1043. return expect(this.res.redirect.calledWith('/some/page')).to.equal(true)
  1044. })
  1045. })
  1046. describe('when user is flagged to reconfirm', function() {
  1047. beforeEach(function() {
  1048. this.req.session = {}
  1049. return (this.user.must_reconfirm = true)
  1050. })
  1051. it('should redirect to reconfirm page', function() {
  1052. this.AuthenticationController.finishLogin(
  1053. this.user,
  1054. this.req,
  1055. this.res,
  1056. this.next
  1057. )
  1058. return expect(
  1059. this.AuthenticationController._redirectToReconfirmPage.calledWith(
  1060. this.req
  1061. )
  1062. ).to.equal(true)
  1063. })
  1064. })
  1065. })
  1066. })