UserRegistrationHandlerTests.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. const SandboxedModule = require('sandboxed-module')
  2. const assert = require('assert')
  3. const path = require('path')
  4. const modulePath = path.join(
  5. __dirname,
  6. '../../../../app/src/Features/User/UserRegistrationHandler'
  7. )
  8. const sinon = require('sinon')
  9. const { expect } = require('chai')
  10. const EmailHelper = require('../../../../app/src/Features/Helpers/EmailHelper')
  11. describe('UserRegistrationHandler', function () {
  12. beforeEach(function () {
  13. this.analyticsId = '123456'
  14. this.user = {
  15. _id: (this.user_id = '31j2lk21kjl'),
  16. analyticsId: this.analyticsId,
  17. }
  18. this.User = {
  19. updateOne: sinon.stub().returns({ exec: sinon.stub().resolves() }),
  20. }
  21. this.UserGetter = {
  22. promises: {
  23. getUserByAnyEmail: sinon.stub(),
  24. },
  25. }
  26. this.UserCreator = {
  27. promises: {
  28. createNewUser: sinon.stub().resolves(this.user),
  29. },
  30. }
  31. this.AuthenticationManager = {
  32. validateEmail: sinon.stub().returns(null),
  33. validatePassword: sinon.stub().returns(null),
  34. promises: {
  35. setUserPassword: sinon.stub().resolves(this.user),
  36. },
  37. }
  38. this.NewsLetterManager = {
  39. subscribe: sinon.stub(),
  40. }
  41. this.EmailHandler = {
  42. promises: { sendEmail: sinon.stub().resolves() },
  43. }
  44. this.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } }
  45. this.handler = SandboxedModule.require(modulePath, {
  46. requires: {
  47. '../../models/User': { User: this.User },
  48. './UserGetter': this.UserGetter,
  49. './UserCreator': this.UserCreator,
  50. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  51. '../Newsletter/NewsletterManager': this.NewsLetterManager,
  52. crypto: (this.crypto = {}),
  53. '../Email/EmailHandler': this.EmailHandler,
  54. '../Security/OneTimeTokenHandler': this.OneTimeTokenHandler,
  55. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  56. recordEventForUser: sinon.stub(),
  57. setUserPropertyForUser: sinon.stub(),
  58. identifyUser: sinon.stub(),
  59. }),
  60. '@overleaf/settings': (this.settings = {
  61. siteUrl: 'http://sl.example.com',
  62. }),
  63. '../Helpers/EmailHelper': EmailHelper,
  64. },
  65. })
  66. this.passingRequest = {
  67. email: 'something@email.com',
  68. password: '123',
  69. analyticsId: this.analyticsId,
  70. }
  71. })
  72. describe('validate Register Request', function () {
  73. it('allows passing validation through', function () {
  74. const result = this.handler.promises._registrationRequestIsValid(
  75. this.passingRequest
  76. )
  77. result.should.equal(true)
  78. })
  79. describe('failing email validation', function () {
  80. beforeEach(function () {
  81. this.AuthenticationManager.validateEmail.returns({
  82. message: 'email not set',
  83. })
  84. })
  85. it('does not allow through', function () {
  86. const result = this.handler.promises._registrationRequestIsValid(
  87. this.passingRequest
  88. )
  89. return result.should.equal(false)
  90. })
  91. })
  92. describe('failing password validation', function () {
  93. beforeEach(function () {
  94. this.AuthenticationManager.validatePassword.returns({
  95. message: 'password is too short',
  96. })
  97. })
  98. it('does not allow through', function () {
  99. const result = this.handler.promises._registrationRequestIsValid(
  100. this.passingRequest
  101. )
  102. result.should.equal(false)
  103. })
  104. })
  105. })
  106. describe('registerNewUser', function () {
  107. describe('holdingAccount', function (done) {
  108. beforeEach(function () {
  109. this.user.holdingAccount = true
  110. this.handler.promises._registrationRequestIsValid = sinon
  111. .stub()
  112. .returns(true)
  113. this.UserGetter.promises.getUserByAnyEmail.resolves(this.user)
  114. })
  115. it('should not create a new user if there is a holding account there', async function () {
  116. await this.handler.promises.registerNewUser(this.passingRequest)
  117. this.UserCreator.promises.createNewUser.called.should.equal(false)
  118. })
  119. it('should set holding account to false', async function () {
  120. await this.handler.promises.registerNewUser(this.passingRequest)
  121. const update = this.User.updateOne.args[0]
  122. assert.deepEqual(update[0], { _id: this.user._id })
  123. assert.deepEqual(update[1], { $set: { holdingAccount: false } })
  124. })
  125. })
  126. describe('invalidRequest', function () {
  127. it('should not create a new user if the the request is not valid', async function () {
  128. this.handler.promises._registrationRequestIsValid = sinon
  129. .stub()
  130. .returns(false)
  131. expect(this.handler.promises.registerNewUser(this.passingRequest)).to.be
  132. .rejected
  133. this.UserCreator.promises.createNewUser.called.should.equal(false)
  134. })
  135. it('should return email registered in the error if there is a non holdingAccount there', async function () {
  136. this.UserGetter.promises.getUserByAnyEmail.resolves(
  137. (this.user = { holdingAccount: false })
  138. )
  139. expect(
  140. this.handler.promises.registerNewUser(this.passingRequest)
  141. ).to.be.rejectedWith('EmailAlreadyRegistered')
  142. })
  143. })
  144. describe('validRequest', function () {
  145. beforeEach(function () {
  146. this.handler.promises._registrationRequestIsValid = sinon
  147. .stub()
  148. .returns(true)
  149. this.UserGetter.promises.getUserByAnyEmail.resolves()
  150. })
  151. it('should create a new user', async function () {
  152. await this.handler.promises.registerNewUser(this.passingRequest)
  153. sinon.assert.calledWith(this.UserCreator.promises.createNewUser, {
  154. email: this.passingRequest.email,
  155. holdingAccount: false,
  156. first_name: this.passingRequest.first_name,
  157. last_name: this.passingRequest.last_name,
  158. analyticsId: this.user.analyticsId,
  159. })
  160. })
  161. it('lower case email', async function () {
  162. this.passingRequest.email = 'soMe@eMail.cOm'
  163. await this.handler.promises.registerNewUser(this.passingRequest)
  164. this.UserCreator.promises.createNewUser.args[0][0].email.should.equal(
  165. 'some@email.com'
  166. )
  167. })
  168. it('trim white space from email', async function () {
  169. this.passingRequest.email = ' some@email.com '
  170. await this.handler.promises.registerNewUser(this.passingRequest)
  171. this.UserCreator.promises.createNewUser.args[0][0].email.should.equal(
  172. 'some@email.com'
  173. )
  174. })
  175. it('should set the password', async function () {
  176. await this.handler.promises.registerNewUser(this.passingRequest)
  177. this.AuthenticationManager.promises.setUserPassword
  178. .calledWith(this.user, this.passingRequest.password)
  179. .should.equal(true)
  180. })
  181. it('should add the user to the newsletter if accepted terms', async function () {
  182. this.passingRequest.subscribeToNewsletter = 'true'
  183. await this.handler.promises.registerNewUser(this.passingRequest)
  184. this.NewsLetterManager.subscribe
  185. .calledWith(this.user)
  186. .should.equal(true)
  187. })
  188. it('should not add the user to the newsletter if not accepted terms', async function () {
  189. await this.handler.promises.registerNewUser(this.passingRequest)
  190. this.NewsLetterManager.subscribe
  191. .calledWith(this.user)
  192. .should.equal(false)
  193. })
  194. })
  195. })
  196. describe('registerNewUserAndSendActivationEmail', function () {
  197. beforeEach(function () {
  198. this.email = 'Email@example.com'
  199. this.crypto.randomBytes = sinon.stub().returns({
  200. toString: () => {
  201. return (this.password = 'mock-password')
  202. },
  203. })
  204. this.OneTimeTokenHandler.promises.getNewToken.resolves(
  205. (this.token = 'mock-token')
  206. )
  207. this.handler.promises.registerNewUser = sinon.stub()
  208. })
  209. describe('with a new user', function () {
  210. beforeEach(async function () {
  211. this.user.email = this.email.toLowerCase()
  212. this.handler.promises.registerNewUser.resolves(this.user)
  213. this.result =
  214. await this.handler.promises.registerNewUserAndSendActivationEmail(
  215. this.email
  216. )
  217. })
  218. it('should ask the UserRegistrationHandler to register user', function () {
  219. sinon.assert.calledWith(this.handler.promises.registerNewUser, {
  220. email: this.email,
  221. password: this.password,
  222. })
  223. })
  224. it('should generate a new password reset token', function () {
  225. const data = {
  226. user_id: this.user._id.toString(),
  227. email: this.user.email,
  228. }
  229. this.OneTimeTokenHandler.promises.getNewToken
  230. .calledWith('password', data, { expiresIn: 7 * 24 * 60 * 60 })
  231. .should.equal(true)
  232. })
  233. it('should send a registered email', function () {
  234. this.EmailHandler.promises.sendEmail
  235. .calledWith('registered', {
  236. to: this.user.email,
  237. setNewPasswordUrl: `${this.settings.siteUrl}/user/activate?token=${this.token}&user_id=${this.user_id}`,
  238. })
  239. .should.equal(true)
  240. })
  241. it('should return the user and new password url', function () {
  242. const { user, setNewPasswordUrl } = this.result
  243. expect(user).to.deep.equal(this.user)
  244. expect(setNewPasswordUrl).to.equal(
  245. `${this.settings.siteUrl}/user/activate?token=${this.token}&user_id=${this.user_id}`
  246. )
  247. })
  248. })
  249. describe('with a user that already exists', function () {
  250. beforeEach(async function () {
  251. this.handler.promises.registerNewUser.rejects(
  252. new Error('EmailAlreadyRegistered')
  253. )
  254. this.UserGetter.promises.getUserByAnyEmail.resolves(this.user)
  255. await this.handler.promises.registerNewUserAndSendActivationEmail(
  256. this.email
  257. )
  258. })
  259. it('should still generate a new password token and email', function () {
  260. this.OneTimeTokenHandler.promises.getNewToken.called.should.equal(true)
  261. this.EmailHandler.promises.sendEmail.called.should.equal(true)
  262. })
  263. })
  264. })
  265. })