UserEmailsConfirmationHandlerTests.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const SandboxedModule = require('sandboxed-module')
  14. const assert = require('assert')
  15. const path = require('path')
  16. const sinon = require('sinon')
  17. const modulePath = path.join(
  18. __dirname,
  19. '../../../../app/src/Features/User/UserEmailsConfirmationHandler'
  20. )
  21. const { expect } = require('chai')
  22. const Errors = require('../../../../app/src/Features/Errors/Errors')
  23. const EmailHelper = require('../../../../app/src/Features/Helpers/EmailHelper')
  24. describe('UserEmailsConfirmationHandler', function () {
  25. beforeEach(function () {
  26. this.mockUser = {
  27. _id: 'mock-user-id',
  28. email: 'mock@example.com',
  29. emails: [{ email: 'mock@example.com' }],
  30. }
  31. this.user_id = this.mockUser._id
  32. this.email = this.mockUser.email
  33. this.req = {}
  34. this.UserEmailsConfirmationHandler = SandboxedModule.require(modulePath, {
  35. requires: {
  36. '@overleaf/settings': (this.settings = {
  37. siteUrl: 'https://emails.example.com',
  38. }),
  39. '../Security/OneTimeTokenHandler': (this.OneTimeTokenHandler = {
  40. promises: {},
  41. }),
  42. './UserUpdater': (this.UserUpdater = {
  43. promises: {},
  44. }),
  45. './UserGetter': (this.UserGetter = {
  46. getUser: sinon.stub().yields(null, this.mockUser),
  47. promises: {
  48. getUser: sinon.stub().resolves(this.mockUser),
  49. },
  50. }),
  51. '../Email/EmailHandler': (this.EmailHandler = {}),
  52. '../Helpers/EmailHelper': EmailHelper,
  53. '../Authentication/SessionManager': (this.SessionManager = {
  54. getLoggedInUserId: sinon.stub().returns(this.mockUser._id),
  55. }),
  56. },
  57. })
  58. return (this.callback = sinon.stub())
  59. })
  60. describe('sendConfirmationEmail', function () {
  61. beforeEach(function () {
  62. this.OneTimeTokenHandler.getNewToken = sinon
  63. .stub()
  64. .yields(null, (this.token = 'new-token'))
  65. return (this.EmailHandler.sendEmail = sinon.stub().yields())
  66. })
  67. describe('successfully', function () {
  68. beforeEach(function () {
  69. return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
  70. this.user_id,
  71. this.email,
  72. this.callback
  73. )
  74. })
  75. it('should generate a token for the user which references their id and email', function () {
  76. return this.OneTimeTokenHandler.getNewToken
  77. .calledWith(
  78. 'email_confirmation',
  79. { user_id: this.user_id, email: this.email },
  80. { expiresIn: 90 * 24 * 60 * 60 }
  81. )
  82. .should.equal(true)
  83. })
  84. it('should send an email to the user', function () {
  85. return this.EmailHandler.sendEmail
  86. .calledWith('confirmEmail', {
  87. to: this.email,
  88. confirmEmailUrl:
  89. 'https://emails.example.com/user/emails/confirm?token=new-token',
  90. sendingUser_id: this.user_id,
  91. })
  92. .should.equal(true)
  93. })
  94. it('should call the callback', function () {
  95. return this.callback.called.should.equal(true)
  96. })
  97. })
  98. describe('with invalid email', function () {
  99. beforeEach(function () {
  100. return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
  101. this.user_id,
  102. '!"£$%^&*()',
  103. this.callback
  104. )
  105. })
  106. it('should return an error', function () {
  107. return this.callback
  108. .calledWith(sinon.match.instanceOf(Error))
  109. .should.equal(true)
  110. })
  111. })
  112. describe('a custom template', function () {
  113. beforeEach(function () {
  114. return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
  115. this.user_id,
  116. this.email,
  117. 'myCustomTemplate',
  118. this.callback
  119. )
  120. })
  121. it('should send an email with the given template', function () {
  122. return this.EmailHandler.sendEmail
  123. .calledWith('myCustomTemplate')
  124. .should.equal(true)
  125. })
  126. })
  127. })
  128. describe('confirmEmailFromToken', function () {
  129. beforeEach(function () {
  130. this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
  131. .stub()
  132. .resolves({ data: { user_id: this.user_id, email: this.email } })
  133. this.OneTimeTokenHandler.promises.expireToken = sinon.stub().resolves()
  134. this.UserUpdater.promises.confirmEmail = sinon.stub().resolves()
  135. })
  136. describe('successfully', function () {
  137. beforeEach(async function () {
  138. await this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  139. this.req,
  140. (this.token = 'mock-token')
  141. )
  142. })
  143. it('should call peekValueFromToken', function () {
  144. return this.OneTimeTokenHandler.promises.peekValueFromToken
  145. .calledWith('email_confirmation', this.token)
  146. .should.equal(true)
  147. })
  148. it('should call expireToken', function () {
  149. return this.OneTimeTokenHandler.promises.expireToken
  150. .calledWith('email_confirmation', this.token)
  151. .should.equal(true)
  152. })
  153. it('should confirm the email of the user_id', function () {
  154. return this.UserUpdater.promises.confirmEmail
  155. .calledWith(this.user_id, this.email)
  156. .should.equal(true)
  157. })
  158. })
  159. describe('with an expired token', function () {
  160. beforeEach(function () {
  161. this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
  162. .stub()
  163. .rejects(new Errors.NotFoundError('no token found'))
  164. })
  165. it('should reject with a NotFoundError', async function () {
  166. await expect(
  167. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  168. this.req,
  169. (this.token = 'mock-token')
  170. )
  171. ).to.be.rejectedWith(Errors.NotFoundError)
  172. })
  173. })
  174. describe('with no user_id in the token', function () {
  175. beforeEach(function () {
  176. this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
  177. .stub()
  178. .resolves({ data: { email: this.email } })
  179. })
  180. it('should reject with a NotFoundError', async function () {
  181. await expect(
  182. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  183. this.req,
  184. (this.token = 'mock-token')
  185. )
  186. ).to.be.rejectedWith(Errors.NotFoundError)
  187. })
  188. })
  189. describe('with no email in the token', function () {
  190. beforeEach(function () {
  191. this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
  192. .stub()
  193. .resolves({ data: { user_id: this.user_id } })
  194. })
  195. it('should reject with a NotFoundError', async function () {
  196. await expect(
  197. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  198. this.req,
  199. (this.token = 'mock-token')
  200. )
  201. ).to.be.rejectedWith(Errors.NotFoundError)
  202. })
  203. })
  204. describe('with no user found', function () {
  205. beforeEach(function () {
  206. this.UserGetter.promises.getUser.resolves(null)
  207. })
  208. it('should reject with a NotFoundError', async function () {
  209. await expect(
  210. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  211. this.req,
  212. (this.token = 'mock-token')
  213. )
  214. ).to.be.rejectedWith(Errors.NotFoundError)
  215. })
  216. })
  217. describe('with secondary email missing on user', function () {
  218. beforeEach(function () {
  219. this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
  220. .stub()
  221. .resolves({
  222. data: { user_id: this.user_id, email: 'deleted@email.com' },
  223. })
  224. })
  225. it('should reject with a NotFoundError', async function () {
  226. await expect(
  227. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  228. this.req,
  229. (this.token = 'mock-token')
  230. )
  231. ).to.be.rejectedWith(Errors.NotFoundError)
  232. })
  233. })
  234. describe('when the logged in user is not the token user', function () {
  235. beforeEach(function () {
  236. this.SessionManager.getLoggedInUserId = sinon
  237. .stub()
  238. .returns('other-user-id')
  239. })
  240. it('should reject with a ForbiddenError', async function () {
  241. await expect(
  242. this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
  243. this.req,
  244. (this.token = 'mock-token')
  245. )
  246. ).to.be.rejectedWith(Errors.ForbiddenError)
  247. })
  248. })
  249. })
  250. })