AuthenticationManagerTests.coffee 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/Features/Authentication/AuthenticationManager.js"
  6. SandboxedModule = require('sandboxed-module')
  7. events = require "events"
  8. ObjectId = require("mongojs").ObjectId
  9. describe "AuthenticationManager", ->
  10. beforeEach ->
  11. @settings = { security: { bcryptRounds: 12 } }
  12. @AuthenticationManager = SandboxedModule.require modulePath, requires:
  13. "../../models/User": User: @User = {}
  14. "../../infrastructure/mongojs":
  15. db: @db =
  16. users: {}
  17. ObjectId: ObjectId
  18. "bcrypt": @bcrypt = {}
  19. "settings-sharelatex": @settings
  20. @callback = sinon.stub()
  21. describe "authenticate", ->
  22. describe "when the user exists in the database", ->
  23. beforeEach ->
  24. @user =
  25. _id: "user-id"
  26. email: @email = "USER@sharelatex.com"
  27. @unencryptedPassword = "banana"
  28. @User.findOne = sinon.stub().callsArgWith(1, null, @user)
  29. describe "when the hashed password matches", ->
  30. beforeEach (done) ->
  31. @user.hashedPassword = @hashedPassword = "asdfjadflasdf"
  32. @bcrypt.compare = sinon.stub().callsArgWith(2, null, true)
  33. @bcrypt.getRounds = sinon.stub().returns 12
  34. @AuthenticationManager.authenticate email: @email, @unencryptedPassword, (error, user) =>
  35. @callback(error, user)
  36. done()
  37. it "should look up the correct user in the database", ->
  38. @User.findOne.calledWith(email: @email).should.equal true
  39. it "should check that the passwords match", ->
  40. @bcrypt.compare
  41. .calledWith(@unencryptedPassword, @hashedPassword)
  42. .should.equal true
  43. it "should return the user", ->
  44. @callback.calledWith(null, @user).should.equal true
  45. describe "when the encrypted passwords do not match", ->
  46. beforeEach ->
  47. @AuthenticationManager._encryptPassword = sinon.stub().returns("Not the encrypted password")
  48. @AuthenticationManager.authenticate(email: @email, @unencryptedPassword, @callback)
  49. it "should not return the user", ->
  50. @callback.calledWith(null, null).should.equal true
  51. describe "when the hashed password matches but the number of rounds is too low", ->
  52. beforeEach (done) ->
  53. @user.hashedPassword = @hashedPassword = "asdfjadflasdf"
  54. @bcrypt.compare = sinon.stub().callsArgWith(2, null, true)
  55. @bcrypt.getRounds = sinon.stub().returns 7
  56. @AuthenticationManager.setUserPassword = sinon.stub().callsArgWith(2, null)
  57. @AuthenticationManager.authenticate email: @email, @unencryptedPassword, (error, user) =>
  58. @callback(error, user)
  59. done()
  60. it "should look up the correct user in the database", ->
  61. @User.findOne.calledWith(email: @email).should.equal true
  62. it "should check that the passwords match", ->
  63. @bcrypt.compare
  64. .calledWith(@unencryptedPassword, @hashedPassword)
  65. .should.equal true
  66. it "should check the number of rounds", ->
  67. @bcrypt.getRounds.called.should.equal true
  68. it "should set the users password (with a higher number of rounds)", ->
  69. @AuthenticationManager.setUserPassword
  70. .calledWith("user-id", @unencryptedPassword)
  71. .should.equal true
  72. it "should return the user", ->
  73. @callback.calledWith(null, @user).should.equal true
  74. describe "when the user does not exist in the database", ->
  75. beforeEach ->
  76. @User.findOne = sinon.stub().callsArgWith(1, null, null)
  77. @AuthenticationManager.authenticate(email: @email, @unencrpytedPassword, @callback)
  78. it "should not return a user", ->
  79. @callback.calledWith(null, null).should.equal true
  80. describe "validateEmail", ->
  81. describe "valid", ->
  82. it "should return null", ->
  83. result = @AuthenticationManager.validateEmail 'foo@example.com'
  84. expect(result).to.equal null
  85. describe "invalid", ->
  86. it "should return validation error object for no email", ->
  87. result = @AuthenticationManager.validateEmail ''
  88. expect(result).to.not.equal null
  89. expect(result.message).to.equal 'email not valid'
  90. it "should return validation error object for invalid", ->
  91. result = @AuthenticationManager.validateEmail 'notanemail'
  92. expect(result).to.not.equal null
  93. expect(result.message).to.equal 'email not valid'
  94. describe "validatePassword", ->
  95. it "should return null if valid", ->
  96. result = @AuthenticationManager.validatePassword 'banana'
  97. expect(result).to.equal null
  98. describe "invalid", ->
  99. beforeEach ->
  100. @settings.passwordStrengthOptions =
  101. length:
  102. max:10
  103. min:6
  104. it "should return validation error object if not set", ->
  105. result = @AuthenticationManager.validatePassword()
  106. expect(result).to.not.equal null
  107. expect(result.message).to.equal 'password not set'
  108. it "should return validation error object if too short", ->
  109. result = @AuthenticationManager.validatePassword 'dsd'
  110. expect(result).to.not.equal null
  111. expect(result.message).to.equal 'password is too short'
  112. it "should return validation error object if too long", ->
  113. result = @AuthenticationManager.validatePassword 'dsdsadsadsadsadsadkjsadjsadjsadljs'
  114. describe "setUserPassword", ->
  115. beforeEach ->
  116. @user_id = ObjectId()
  117. @password = "banana"
  118. @hashedPassword = "asdkjfa;osiuvandf"
  119. @salt = "saltaasdfasdfasdf"
  120. @bcrypt.genSalt = sinon.stub().callsArgWith(1, null, @salt)
  121. @bcrypt.hash = sinon.stub().callsArgWith(2, null, @hashedPassword)
  122. @db.users.update = sinon.stub().callsArg(2)
  123. describe "too long", ->
  124. beforeEach ->
  125. @settings.passwordStrengthOptions =
  126. length:
  127. max:10
  128. @password = "dsdsadsadsadsadsadkjsadjsadjsadljs"
  129. it "should return and error", (done)->
  130. @AuthenticationManager.setUserPassword @user_id, @password, (err)->
  131. expect(err).to.exist
  132. done()
  133. it "should not start the bcrypt process", (done)->
  134. @AuthenticationManager.setUserPassword @user_id, @password, (err)=>
  135. @bcrypt.genSalt.called.should.equal false
  136. @bcrypt.hash.called.should.equal false
  137. done()
  138. describe "too short", ->
  139. beforeEach ->
  140. @settings.passwordStrengthOptions =
  141. length:
  142. max:10
  143. min:6
  144. @password = "dsd"
  145. it "should return and error", (done)->
  146. @AuthenticationManager.setUserPassword @user_id, @password, (err)->
  147. expect(err).to.exist
  148. done()
  149. it "should not start the bcrypt process", (done)->
  150. @AuthenticationManager.setUserPassword @user_id, @password, (err)=>
  151. @bcrypt.genSalt.called.should.equal false
  152. @bcrypt.hash.called.should.equal false
  153. done()
  154. describe "successful set", ->
  155. beforeEach ->
  156. @AuthenticationManager.setUserPassword(@user_id, @password, @callback)
  157. it "should update the user's password in the database", ->
  158. args = @db.users.update.lastCall.args
  159. expect(args[0]).to.deep.equal {_id: ObjectId(@user_id.toString())}
  160. expect(args[1]).to.deep.equal {
  161. $set: {
  162. "hashedPassword": @hashedPassword
  163. }
  164. $unset: password: true
  165. }
  166. it "should hash the password", ->
  167. @bcrypt.genSalt
  168. .calledWith(12)
  169. .should.equal true
  170. @bcrypt.hash
  171. .calledWith(@password, @salt)
  172. .should.equal true
  173. it "should call the callback", ->
  174. @callback.called.should.equal true