UserEmailsTests.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. const { expect } = require('chai')
  2. const async = require('async')
  3. const moment = require('moment')
  4. const Features = require('../../../app/src/infrastructure/Features')
  5. const User = require('./helpers/User')
  6. const UserHelper = require('./helpers/UserHelper')
  7. const UserUpdater = require('../../../app/src/Features/User/UserUpdater')
  8. const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
  9. const MockV1ApiClass = require('./mocks/MockV1Api')
  10. const expectErrorResponse = require('./helpers/expectErrorResponse')
  11. let MockV1Api
  12. before(function() {
  13. MockV1Api = MockV1ApiClass.instance()
  14. })
  15. describe('UserEmails', function() {
  16. beforeEach(function(done) {
  17. this.timeout(20000)
  18. this.user = new User()
  19. this.user.login(done)
  20. })
  21. describe('confirming an email', function() {
  22. it('should confirm the email', function(done) {
  23. let token = null
  24. async.series(
  25. [
  26. cb => {
  27. this.user.request(
  28. {
  29. method: 'POST',
  30. url: '/user/emails',
  31. json: {
  32. email: 'newly-added-email@example.com'
  33. }
  34. },
  35. (error, response, body) => {
  36. expect(error).to.not.exist
  37. expect(response.statusCode).to.equal(204)
  38. cb()
  39. }
  40. )
  41. },
  42. cb => {
  43. this.user.request(
  44. { url: '/user/emails', json: true },
  45. (error, response, body) => {
  46. expect(error).to.not.exist
  47. expect(response.statusCode).to.equal(200)
  48. expect(body[0].confirmedAt).to.not.exist
  49. expect(body[0].reconfirmedAt).to.not.exist
  50. expect(body[1].confirmedAt).to.not.exist
  51. expect(body[1].reconfirmedAt).to.not.exist
  52. cb()
  53. }
  54. )
  55. },
  56. cb => {
  57. db.tokens
  58. .find({
  59. use: 'email_confirmation',
  60. 'data.user_id': this.user._id,
  61. usedAt: { $exists: false }
  62. })
  63. .toArray((error, tokens) => {
  64. expect(error).to.not.exist
  65. // There should only be one confirmation token at the moment
  66. expect(tokens.length).to.equal(1)
  67. expect(tokens[0].data.email).to.equal(
  68. 'newly-added-email@example.com'
  69. )
  70. expect(tokens[0].data.user_id).to.equal(this.user._id)
  71. ;({ token } = tokens[0])
  72. cb()
  73. })
  74. },
  75. cb => {
  76. this.user.request(
  77. {
  78. method: 'POST',
  79. url: '/user/emails/confirm',
  80. json: {
  81. token
  82. }
  83. },
  84. (error, response, body) => {
  85. expect(error).to.not.exist
  86. expect(response.statusCode).to.equal(200)
  87. cb()
  88. }
  89. )
  90. },
  91. cb => {
  92. this.user.request(
  93. { url: '/user/emails', json: true },
  94. (error, response, body) => {
  95. expect(error).to.not.exist
  96. expect(response.statusCode).to.equal(200)
  97. expect(body[0].confirmedAt).to.not.exist
  98. expect(body[0].reconfirmedAt).to.not.exist
  99. expect(body[1].confirmedAt).to.exist
  100. expect(body[1].reconfirmedAt).to.exist
  101. expect(body[1].reconfirmedAt).to.deep.equal(body[1].confirmedAt)
  102. cb()
  103. }
  104. )
  105. },
  106. cb => {
  107. db.tokens
  108. .find({
  109. use: 'email_confirmation',
  110. 'data.user_id': this.user._id,
  111. usedAt: { $exists: false }
  112. })
  113. .toArray((error, tokens) => {
  114. expect(error).to.not.exist
  115. // Token should be deleted after use
  116. expect(tokens.length).to.equal(0)
  117. cb()
  118. })
  119. }
  120. ],
  121. done
  122. )
  123. })
  124. it('should not allow confirmation of the email if the user has changed', function(done) {
  125. let token1 = null
  126. let token2 = null
  127. this.user2 = new User()
  128. this.email = 'duplicate-email@example.com'
  129. async.series(
  130. [
  131. cb => this.user2.login(cb),
  132. cb => {
  133. // Create email for first user
  134. this.user.request(
  135. {
  136. method: 'POST',
  137. url: '/user/emails',
  138. json: { email: this.email }
  139. },
  140. cb
  141. )
  142. },
  143. cb => {
  144. db.tokens
  145. .find({
  146. use: 'email_confirmation',
  147. 'data.user_id': this.user._id,
  148. usedAt: { $exists: false }
  149. })
  150. .toArray((error, tokens) => {
  151. expect(error).to.not.exist
  152. // There should only be one confirmation token at the moment
  153. expect(tokens.length).to.equal(1)
  154. expect(tokens[0].data.email).to.equal(this.email)
  155. expect(tokens[0].data.user_id).to.equal(this.user._id)
  156. token1 = tokens[0].token
  157. cb()
  158. })
  159. },
  160. cb => {
  161. // Delete the email from the first user
  162. this.user.request(
  163. {
  164. method: 'POST',
  165. url: '/user/emails/delete',
  166. json: { email: this.email }
  167. },
  168. cb
  169. )
  170. },
  171. cb => {
  172. // Create email for second user
  173. this.user2.request(
  174. {
  175. method: 'POST',
  176. url: '/user/emails',
  177. json: { email: this.email }
  178. },
  179. cb
  180. )
  181. },
  182. cb => {
  183. // Original confirmation token should no longer work
  184. this.user.request(
  185. {
  186. method: 'POST',
  187. url: '/user/emails/confirm',
  188. json: {
  189. token: token1
  190. }
  191. },
  192. (error, response, body) => {
  193. expect(error).to.not.exist
  194. expect(response.statusCode).to.equal(404)
  195. cb()
  196. }
  197. )
  198. },
  199. cb => {
  200. db.tokens
  201. .find({
  202. use: 'email_confirmation',
  203. 'data.user_id': this.user2._id,
  204. usedAt: { $exists: false }
  205. })
  206. .toArray((error, tokens) => {
  207. expect(error).to.not.exist
  208. // The first token has been used, so this should be token2 now
  209. expect(tokens.length).to.equal(1)
  210. expect(tokens[0].data.email).to.equal(this.email)
  211. expect(tokens[0].data.user_id).to.equal(this.user2._id)
  212. token2 = tokens[0].token
  213. cb()
  214. })
  215. },
  216. cb => {
  217. // Second user should be able to confirm the email
  218. this.user2.request(
  219. {
  220. method: 'POST',
  221. url: '/user/emails/confirm',
  222. json: {
  223. token: token2
  224. }
  225. },
  226. (error, response, body) => {
  227. expect(error).to.not.exist
  228. expect(response.statusCode).to.equal(200)
  229. cb()
  230. }
  231. )
  232. },
  233. cb => {
  234. this.user2.request(
  235. { url: '/user/emails', json: true },
  236. (error, response, body) => {
  237. expect(error).to.not.exist
  238. expect(response.statusCode).to.equal(200)
  239. expect(body[0].confirmedAt).to.not.exist
  240. expect(body[1].confirmedAt).to.exist
  241. cb()
  242. }
  243. )
  244. }
  245. ],
  246. done
  247. )
  248. })
  249. })
  250. describe('reconfirm an email', function() {
  251. let email, userHelper, confirmedAtDate
  252. beforeEach(async function() {
  253. userHelper = new UserHelper()
  254. email = userHelper.getDefaultEmail()
  255. userHelper = await UserHelper.createUser({ email })
  256. userHelper = await UserHelper.loginUser({
  257. email,
  258. password: userHelper.getDefaultPassword()
  259. })
  260. // original confirmation
  261. await userHelper.confirmEmail(userHelper.user._id, email)
  262. const user = (await UserHelper.getUser({ email })).user
  263. confirmedAtDate = user.emails[0].confirmedAt
  264. expect(user.emails[0].confirmedAt).to.exist
  265. expect(user.emails[0].reconfirmedAt).to.exist
  266. })
  267. it('should set reconfirmedAt and not reset confirmedAt', async function() {
  268. await userHelper.confirmEmail(userHelper.user._id, email)
  269. const user = (await UserHelper.getUser({ email })).user
  270. expect(user.emails[0].confirmedAt).to.exist
  271. expect(user.emails[0].reconfirmedAt).to.exist
  272. expect(user.emails[0].confirmedAt).to.deep.equal(confirmedAtDate)
  273. expect(user.emails[0].reconfirmedAt).to.not.deep.equal(
  274. user.emails[0].confirmedAt
  275. )
  276. expect(user.emails[0].reconfirmedAt > user.emails[0].confirmedAt).to.be
  277. .true
  278. })
  279. })
  280. describe('with an expired token', function() {
  281. it('should not confirm the email', function(done) {
  282. let token = null
  283. async.series(
  284. [
  285. cb => {
  286. this.user.request(
  287. {
  288. method: 'POST',
  289. url: '/user/emails',
  290. json: {
  291. email: (this.email = 'expired-token-email@example.com')
  292. }
  293. },
  294. (error, response, body) => {
  295. expect(error).to.not.exist
  296. expect(response.statusCode).to.equal(204)
  297. cb()
  298. }
  299. )
  300. },
  301. cb => {
  302. db.tokens
  303. .find({
  304. use: 'email_confirmation',
  305. 'data.user_id': this.user._id,
  306. usedAt: { $exists: false }
  307. })
  308. .toArray((error, tokens) => {
  309. expect(error).to.not.exist
  310. // There should only be one confirmation token at the moment
  311. expect(tokens.length).to.equal(1)
  312. expect(tokens[0].data.email).to.equal(this.email)
  313. expect(tokens[0].data.user_id).to.equal(this.user._id)
  314. ;({ token } = tokens[0])
  315. cb()
  316. })
  317. },
  318. cb => {
  319. db.tokens.update(
  320. {
  321. token
  322. },
  323. {
  324. $set: {
  325. expiresAt: new Date(Date.now() - 1000000)
  326. }
  327. },
  328. cb
  329. )
  330. },
  331. cb => {
  332. this.user.request(
  333. {
  334. method: 'POST',
  335. url: '/user/emails/confirm',
  336. json: {
  337. token
  338. }
  339. },
  340. (error, response, body) => {
  341. expect(error).to.not.exist
  342. expect(response.statusCode).to.equal(404)
  343. cb()
  344. }
  345. )
  346. }
  347. ],
  348. done
  349. )
  350. })
  351. })
  352. describe('resending the confirmation', function() {
  353. it('should generate a new token', function(done) {
  354. async.series(
  355. [
  356. cb => {
  357. this.user.request(
  358. {
  359. method: 'POST',
  360. url: '/user/emails',
  361. json: {
  362. email: 'reconfirmation-email@example.com'
  363. }
  364. },
  365. (error, response, body) => {
  366. expect(error).to.not.exist
  367. expect(response.statusCode).to.equal(204)
  368. cb()
  369. }
  370. )
  371. },
  372. cb => {
  373. db.tokens
  374. .find({
  375. use: 'email_confirmation',
  376. 'data.user_id': this.user._id,
  377. usedAt: { $exists: false }
  378. })
  379. .toArray((error, tokens) => {
  380. expect(error).to.not.exist
  381. // There should only be one confirmation token at the moment
  382. expect(tokens.length).to.equal(1)
  383. expect(tokens[0].data.email).to.equal(
  384. 'reconfirmation-email@example.com'
  385. )
  386. expect(tokens[0].data.user_id).to.equal(this.user._id)
  387. cb()
  388. })
  389. },
  390. cb => {
  391. this.user.request(
  392. {
  393. method: 'POST',
  394. url: '/user/emails/resend_confirmation',
  395. json: {
  396. email: 'reconfirmation-email@example.com'
  397. }
  398. },
  399. (error, response, body) => {
  400. expect(error).to.not.exist
  401. expect(response.statusCode).to.equal(200)
  402. cb()
  403. }
  404. )
  405. },
  406. cb => {
  407. db.tokens
  408. .find({
  409. use: 'email_confirmation',
  410. 'data.user_id': this.user._id,
  411. usedAt: { $exists: false }
  412. })
  413. .toArray((error, tokens) => {
  414. expect(error).to.not.exist
  415. // There should be two tokens now
  416. expect(tokens.length).to.equal(2)
  417. expect(tokens[0].data.email).to.equal(
  418. 'reconfirmation-email@example.com'
  419. )
  420. expect(tokens[0].data.user_id).to.equal(this.user._id)
  421. expect(tokens[1].data.email).to.equal(
  422. 'reconfirmation-email@example.com'
  423. )
  424. expect(tokens[1].data.user_id).to.equal(this.user._id)
  425. cb()
  426. })
  427. }
  428. ],
  429. done
  430. )
  431. })
  432. it('should create a new token if none exists', function(done) {
  433. // This should only be for users that have sign up with their main
  434. // emails before the confirmation system existed
  435. async.series(
  436. [
  437. cb => {
  438. db.tokens.remove(
  439. {
  440. use: 'email_confirmation',
  441. 'data.user_id': this.user._id,
  442. usedAt: { $exists: false }
  443. },
  444. cb
  445. )
  446. },
  447. cb => {
  448. this.user.request(
  449. {
  450. method: 'POST',
  451. url: '/user/emails/resend_confirmation',
  452. json: {
  453. email: this.user.email
  454. }
  455. },
  456. (error, response, body) => {
  457. expect(error).to.not.exist
  458. expect(response.statusCode).to.equal(200)
  459. cb()
  460. }
  461. )
  462. },
  463. cb => {
  464. db.tokens
  465. .find({
  466. use: 'email_confirmation',
  467. 'data.user_id': this.user._id,
  468. usedAt: { $exists: false }
  469. })
  470. .toArray((error, tokens) => {
  471. expect(error).to.not.exist
  472. // There should still only be one confirmation token
  473. expect(tokens.length).to.equal(1)
  474. expect(tokens[0].data.email).to.equal(this.user.email)
  475. expect(tokens[0].data.user_id).to.equal(this.user._id)
  476. cb()
  477. })
  478. }
  479. ],
  480. done
  481. )
  482. })
  483. it("should not allow reconfirmation if the email doesn't match the user", function(done) {
  484. async.series(
  485. [
  486. cb => {
  487. this.user.request(
  488. {
  489. method: 'POST',
  490. url: '/user/emails/resend_confirmation',
  491. json: {
  492. email: 'non-matching-email@example.com'
  493. }
  494. },
  495. (error, response, body) => {
  496. expect(error).to.not.exist
  497. expect(response.statusCode).to.equal(422)
  498. cb()
  499. }
  500. )
  501. },
  502. cb => {
  503. db.tokens
  504. .find({
  505. use: 'email_confirmation',
  506. 'data.user_id': this.user._id,
  507. usedAt: { $exists: false }
  508. })
  509. .toArray((error, tokens) => {
  510. expect(error).to.not.exist
  511. expect(tokens.length).to.equal(0)
  512. cb()
  513. })
  514. }
  515. ],
  516. done
  517. )
  518. })
  519. })
  520. describe('setting a default email', function() {
  521. it('should update confirmed emails for users not in v1', function(done) {
  522. async.series(
  523. [
  524. cb => {
  525. this.user.request(
  526. {
  527. method: 'POST',
  528. url: '/user/emails',
  529. json: {
  530. email: 'new-confirmed-default@example.com'
  531. }
  532. },
  533. (error, response, body) => {
  534. expect(error).to.not.exist
  535. expect(response.statusCode).to.equal(204)
  536. cb()
  537. }
  538. )
  539. },
  540. cb => {
  541. // Mark the email as confirmed
  542. db.users.updateOne(
  543. {
  544. 'emails.email': 'new-confirmed-default@example.com'
  545. },
  546. {
  547. $set: {
  548. 'emails.$.confirmedAt': new Date()
  549. }
  550. },
  551. cb
  552. )
  553. },
  554. cb => {
  555. this.user.request(
  556. {
  557. method: 'POST',
  558. url: '/user/emails/default',
  559. json: {
  560. email: 'new-confirmed-default@example.com'
  561. }
  562. },
  563. (error, response, body) => {
  564. expect(error).to.not.exist
  565. expect(response.statusCode).to.equal(200)
  566. cb()
  567. }
  568. )
  569. },
  570. cb => {
  571. this.user.request(
  572. { url: '/user/emails', json: true },
  573. (error, response, body) => {
  574. expect(error).to.not.exist
  575. expect(response.statusCode).to.equal(200)
  576. expect(body[0].confirmedAt).to.not.exist
  577. expect(body[0].default).to.equal(false)
  578. expect(body[1].confirmedAt).to.exist
  579. expect(body[1].default).to.equal(true)
  580. cb()
  581. }
  582. )
  583. }
  584. ],
  585. done
  586. )
  587. })
  588. it('should not allow changing unconfirmed emails in v1', function(done) {
  589. async.series(
  590. [
  591. cb => {
  592. db.users.updateOne(
  593. {
  594. _id: ObjectId(this.user._id)
  595. },
  596. {
  597. $set: {
  598. 'overleaf.id': 42
  599. }
  600. },
  601. cb
  602. )
  603. },
  604. cb => {
  605. this.user.request(
  606. {
  607. method: 'POST',
  608. url: '/user/emails',
  609. json: {
  610. email: 'new-unconfirmed-default@example.com'
  611. }
  612. },
  613. (error, response, body) => {
  614. expect(error).to.not.exist
  615. expect(response.statusCode).to.equal(204)
  616. cb()
  617. }
  618. )
  619. },
  620. cb => {
  621. this.user.request(
  622. {
  623. method: 'POST',
  624. url: '/user/emails/default',
  625. json: {
  626. email: 'new-unconfirmed-default@example.com'
  627. }
  628. },
  629. (error, response, body) => {
  630. expect(error).to.not.exist
  631. expect(response.statusCode).to.equal(409)
  632. cb()
  633. }
  634. )
  635. },
  636. cb => {
  637. this.user.request(
  638. { url: '/user/emails', json: true },
  639. (error, response, body) => {
  640. expect(error).to.not.exist
  641. expect(body[0].default).to.equal(true)
  642. expect(body[1].default).to.equal(false)
  643. cb()
  644. }
  645. )
  646. }
  647. ],
  648. done
  649. )
  650. })
  651. it('should not update the email in v1', function(done) {
  652. async.series(
  653. [
  654. cb => {
  655. db.users.updateOne(
  656. {
  657. _id: ObjectId(this.user._id)
  658. },
  659. {
  660. $set: {
  661. 'overleaf.id': 42
  662. }
  663. },
  664. cb
  665. )
  666. },
  667. cb => {
  668. this.user.request(
  669. {
  670. method: 'POST',
  671. url: '/user/emails',
  672. json: {
  673. email: 'new-confirmed-default-in-v1@example.com'
  674. }
  675. },
  676. (error, response, body) => {
  677. expect(error).to.not.exist
  678. expect(response.statusCode).to.equal(204)
  679. cb()
  680. }
  681. )
  682. },
  683. cb => {
  684. // Mark the email as confirmed
  685. db.users.updateOne(
  686. {
  687. 'emails.email': 'new-confirmed-default-in-v1@example.com'
  688. },
  689. {
  690. $set: {
  691. 'emails.$.confirmedAt': new Date()
  692. }
  693. },
  694. cb
  695. )
  696. },
  697. cb => {
  698. this.user.request(
  699. {
  700. method: 'POST',
  701. url: '/user/emails/default',
  702. json: {
  703. email: 'new-confirmed-default-in-v1@example.com'
  704. }
  705. },
  706. (error, response, body) => {
  707. expect(error).to.not.exist
  708. expect(response.statusCode).to.equal(200)
  709. cb()
  710. }
  711. )
  712. }
  713. ],
  714. error => {
  715. expect(error).to.not.exist
  716. expect(MockV1Api.updateEmail.callCount).to.equal(0)
  717. done()
  718. }
  719. )
  720. })
  721. it('should not return an error if the email exists in v1', function(done) {
  722. MockV1Api.existingEmails.push('exists-in-v1@example.com')
  723. async.series(
  724. [
  725. cb => {
  726. db.users.updateOne(
  727. {
  728. _id: ObjectId(this.user._id)
  729. },
  730. {
  731. $set: {
  732. 'overleaf.id': 42
  733. }
  734. },
  735. cb
  736. )
  737. },
  738. cb => {
  739. this.user.request(
  740. {
  741. method: 'POST',
  742. url: '/user/emails',
  743. json: {
  744. email: 'exists-in-v1@example.com'
  745. }
  746. },
  747. (error, response, body) => {
  748. expect(error).to.not.exist
  749. expect(response.statusCode).to.equal(204)
  750. cb()
  751. }
  752. )
  753. },
  754. cb => {
  755. // Mark the email as confirmed
  756. db.users.updateOne(
  757. {
  758. 'emails.email': 'exists-in-v1@example.com'
  759. },
  760. {
  761. $set: {
  762. 'emails.$.confirmedAt': new Date()
  763. }
  764. },
  765. cb
  766. )
  767. },
  768. cb => {
  769. this.user.request(
  770. {
  771. method: 'POST',
  772. url: '/user/emails/default',
  773. json: {
  774. email: 'exists-in-v1@example.com'
  775. }
  776. },
  777. (error, response, body) => {
  778. expect(error).to.not.exist
  779. expect(response.statusCode).to.equal(200)
  780. cb()
  781. }
  782. )
  783. },
  784. cb => {
  785. this.user.request(
  786. { url: '/user/emails', json: true },
  787. (error, response, body) => {
  788. expect(error).to.not.exist
  789. expect(body[0].default).to.equal(false)
  790. expect(body[1].default).to.equal(true)
  791. cb()
  792. }
  793. )
  794. }
  795. ],
  796. done
  797. )
  798. })
  799. describe('audit log', function() {
  800. const originalEmail = 'original@overleaf.com'
  801. let otherEmail, response, userHelper, user, userId
  802. beforeEach(async function() {
  803. otherEmail = 'other@overleaf.com'
  804. userHelper = new UserHelper()
  805. userHelper = await UserHelper.createUser({
  806. email: originalEmail
  807. })
  808. userHelper = await UserHelper.loginUser({
  809. email: originalEmail,
  810. password: userHelper.getDefaultPassword()
  811. })
  812. userId = userHelper.user._id
  813. response = await userHelper.request.post({
  814. form: {
  815. email: otherEmail
  816. },
  817. simple: false,
  818. uri: '/user/emails'
  819. })
  820. expect(response.statusCode).to.equal(204)
  821. const token = (
  822. await db.tokens.findOne({
  823. 'data.user_id': userId.toString(),
  824. 'data.email': otherEmail
  825. })
  826. ).token
  827. response = await userHelper.request.post(`/user/emails/confirm`, {
  828. form: {
  829. token
  830. },
  831. simple: false
  832. })
  833. expect(response.statusCode).to.equal(200)
  834. response = await userHelper.request.post('/user/emails/default', {
  835. form: {
  836. email: otherEmail
  837. },
  838. simple: false
  839. })
  840. expect(response.statusCode).to.equal(200)
  841. userHelper = await UserHelper.getUser(userId)
  842. user = userHelper.user
  843. })
  844. it('should be updated', function() {
  845. const auditLog = userHelper.getAuditLogWithoutNoise()
  846. const entry = auditLog[auditLog.length - 1]
  847. expect(typeof entry.initiatorId).to.equal('object')
  848. expect(entry.initiatorId).to.deep.equal(user._id)
  849. expect(entry.ipAddress).to.equal('127.0.0.1')
  850. expect(entry.info).to.deep.equal({
  851. newPrimaryEmail: otherEmail,
  852. oldPrimaryEmail: originalEmail
  853. })
  854. })
  855. })
  856. describe('session cleanup', function() {
  857. beforeEach(function setupSecondSession(done) {
  858. this.userSession2 = new User()
  859. this.userSession2.email = this.user.email
  860. this.userSession2.emails = this.user.emails
  861. this.userSession2.password = this.user.password
  862. // login before adding the new email address
  863. // User.login() performs a mongo update and resets the .emails field.
  864. this.userSession2.login(done)
  865. })
  866. beforeEach(function checkSecondSessionLiveness(done) {
  867. this.userSession2.request(
  868. { method: 'GET', url: '/project', followRedirect: false },
  869. (error, response) => {
  870. expect(error).to.not.exist
  871. expect(response.statusCode).to.equal(200)
  872. done()
  873. }
  874. )
  875. })
  876. beforeEach(function addSecondaryEmail(done) {
  877. this.user.request(
  878. {
  879. method: 'POST',
  880. url: '/user/emails',
  881. json: { email: 'new-confirmed-default@example.com' }
  882. },
  883. (error, response) => {
  884. expect(error).to.not.exist
  885. expect(response.statusCode).to.equal(204)
  886. done()
  887. }
  888. )
  889. })
  890. beforeEach(function confirmSecondaryEmail(done) {
  891. db.users.updateOne(
  892. { 'emails.email': 'new-confirmed-default@example.com' },
  893. { $set: { 'emails.$.confirmedAt': new Date() } },
  894. done
  895. )
  896. })
  897. beforeEach(function setDefault(done) {
  898. this.user.request(
  899. {
  900. method: 'POST',
  901. url: '/user/emails/default',
  902. json: { email: 'new-confirmed-default@example.com' }
  903. },
  904. (error, response) => {
  905. expect(error).to.not.exist
  906. expect(response.statusCode).to.equal(200)
  907. done()
  908. }
  909. )
  910. })
  911. it('should logout the other sessions', function(done) {
  912. this.userSession2.request(
  913. { method: 'GET', url: '/project', followRedirect: false },
  914. (error, response) => {
  915. expect(error).to.not.exist
  916. expect(response.statusCode).to.equal(302)
  917. expect(response.headers)
  918. .to.have.property('location')
  919. .to.match(new RegExp('^/login'))
  920. done()
  921. }
  922. )
  923. })
  924. })
  925. })
  926. describe('when not logged in', function() {
  927. beforeEach(function(done) {
  928. this.anonymous = new User()
  929. this.anonymous.getCsrfToken(done)
  930. })
  931. it('should return a plain 403 when setting the email', function(done) {
  932. this.anonymous.request(
  933. {
  934. method: 'POST',
  935. url: '/user/emails',
  936. json: {
  937. email: 'newly-added-email@example.com'
  938. }
  939. },
  940. (error, response, body) => {
  941. if (error) {
  942. return done(error)
  943. }
  944. expectErrorResponse.requireLogin.json(response, body)
  945. done()
  946. }
  947. )
  948. })
  949. })
  950. describe('secondary email', function() {
  951. let newEmail, userHelper, userId, user
  952. beforeEach(async function() {
  953. newEmail = 'a-new-email@overleaf.com'
  954. userHelper = new UserHelper()
  955. userHelper = await UserHelper.createUser()
  956. userHelper = await UserHelper.loginUser({
  957. email: userHelper.getDefaultEmail(),
  958. password: userHelper.getDefaultPassword()
  959. })
  960. userId = userHelper.user._id
  961. await userHelper.request.post({
  962. form: {
  963. email: newEmail
  964. },
  965. simple: false,
  966. uri: '/user/emails'
  967. })
  968. userHelper = await UserHelper.getUser(userId)
  969. user = userHelper.user
  970. })
  971. it('should add the email', async function() {
  972. expect(user.emails[1].email).to.equal(newEmail)
  973. })
  974. it('should add to the user audit log', async function() {
  975. const auditLog = userHelper.getAuditLogWithoutNoise()
  976. expect(typeof auditLog[0].initiatorId).to.equal('object')
  977. expect(auditLog[0].initiatorId).to.deep.equal(user._id)
  978. expect(auditLog[0].info.newSecondaryEmail).to.equal(newEmail)
  979. expect(auditLog[0].ip).to.equal(this.user.request.ip)
  980. })
  981. })
  982. describe('notification period', function() {
  983. let defaultEmail, userHelper, email1, email2, email3
  984. const maxConfirmationMonths = 12
  985. const lastDayToReconfirm = moment()
  986. .subtract(maxConfirmationMonths, 'months')
  987. .toDate()
  988. const oneDayBeforeLastDayToReconfirm = moment(lastDayToReconfirm)
  989. .add(1, 'day')
  990. .toDate()
  991. const daysToBackdate = moment().diff(oneDayBeforeLastDayToReconfirm, 'day')
  992. const daysToBackdateForAfterDate = daysToBackdate + 1
  993. beforeEach(async function() {
  994. if (!Features.hasFeature('affiliations')) {
  995. this.skip()
  996. }
  997. userHelper = new UserHelper()
  998. defaultEmail = userHelper.getDefaultEmail()
  999. userHelper = await UserHelper.createUser({ email: defaultEmail })
  1000. userHelper = await UserHelper.loginUser({
  1001. email: defaultEmail,
  1002. password: userHelper.getDefaultPassword()
  1003. })
  1004. const institutionId = MockV1Api.createInstitution({
  1005. ssoEnabled: false,
  1006. maxConfirmationMonths
  1007. })
  1008. const domain = 'example-affiliation.com'
  1009. MockV1Api.addInstitutionDomain(institutionId, domain)
  1010. email1 = `leonard@${domain}`
  1011. email2 = `mccoy@${domain}`
  1012. email3 = `bones@${domain}`
  1013. })
  1014. describe('non SSO affiliations', function() {
  1015. beforeEach(async function() {
  1016. // create a user with 3 affiliations at the institution.
  1017. // all are within in the notification period
  1018. const userId = userHelper.user._id
  1019. await userHelper.addEmailAndConfirm(userId, email1)
  1020. await userHelper.addEmailAndConfirm(userId, email2)
  1021. await userHelper.addEmailAndConfirm(userId, email3)
  1022. await userHelper.backdateConfirmation(userId, email1, daysToBackdate)
  1023. await userHelper.backdateConfirmation(userId, email2, daysToBackdate)
  1024. await userHelper.backdateConfirmation(
  1025. userId,
  1026. email3,
  1027. daysToBackdateForAfterDate
  1028. )
  1029. })
  1030. it('should flag inReconfirmNotificationPeriod for all affiliations in period', async function() {
  1031. const response = await userHelper.request.get('/user/emails')
  1032. expect(response.statusCode).to.equal(200)
  1033. const fullEmails = JSON.parse(response.body)
  1034. expect(fullEmails.length).to.equal(4)
  1035. expect(fullEmails[0].affiliation).to.not.exist
  1036. expect(
  1037. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1038. ).to.equal(true)
  1039. expect(fullEmails[1].affiliation.pastReconfirmDate).to.equal(false)
  1040. expect(
  1041. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1042. ).to.equal(true)
  1043. expect(fullEmails[2].affiliation.pastReconfirmDate).to.equal(false)
  1044. expect(
  1045. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1046. ).to.equal(true)
  1047. expect(fullEmails[3].affiliation.pastReconfirmDate).to.equal(true)
  1048. })
  1049. describe('should flag emails before their confirmation expires, but within the notification period', function() {
  1050. beforeEach(async function() {
  1051. const dateInPeriodButNotExpired = moment()
  1052. .subtract(maxConfirmationMonths, 'months')
  1053. .add(14, 'days')
  1054. .toDate()
  1055. const backdatedDays = moment().diff(dateInPeriodButNotExpired, 'days')
  1056. await userHelper.backdateConfirmation(
  1057. userHelper.user._id,
  1058. email1,
  1059. backdatedDays
  1060. )
  1061. await userHelper.backdateConfirmation(
  1062. userHelper.user._id,
  1063. email2,
  1064. backdatedDays
  1065. )
  1066. await userHelper.backdateConfirmation(
  1067. userHelper.user._id,
  1068. email3,
  1069. backdatedDays
  1070. )
  1071. })
  1072. it('should flag the emails', async function() {
  1073. const response = await userHelper.request.get('/user/emails')
  1074. expect(response.statusCode).to.equal(200)
  1075. const fullEmails = JSON.parse(response.body)
  1076. expect(fullEmails.length).to.equal(4)
  1077. expect(fullEmails[0].affiliation).to.not.exist
  1078. expect(
  1079. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1080. ).to.equal(true)
  1081. expect(
  1082. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1083. ).to.equal(true)
  1084. expect(
  1085. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1086. ).to.equal(true)
  1087. // ensure dates are not past reconfirmation period
  1088. function _getLastDayToReconfirm(date) {
  1089. return moment(date).add(maxConfirmationMonths, 'months')
  1090. }
  1091. expect(
  1092. moment(fullEmails[1].reconfirmedAt).isAfter(
  1093. _getLastDayToReconfirm(fullEmails[1].reconfirmedAt)
  1094. )
  1095. ).to.equal(false)
  1096. expect(
  1097. moment(fullEmails[2].reconfirmedAt).isAfter(
  1098. _getLastDayToReconfirm(fullEmails[2].reconfirmedAt)
  1099. )
  1100. ).to.equal(false)
  1101. expect(
  1102. moment(fullEmails[3].reconfirmedAt).isAfter(
  1103. _getLastDayToReconfirm(fullEmails[3].reconfirmedAt)
  1104. )
  1105. ).to.equal(false)
  1106. })
  1107. })
  1108. describe('missing reconfirmedAt', function() {
  1109. beforeEach(async function() {
  1110. const userId = userHelper.user._id
  1111. const query = {
  1112. _id: userId,
  1113. 'emails.email': email2
  1114. }
  1115. const update = {
  1116. $unset: { 'emails.$.reconfirmedAt': true }
  1117. }
  1118. await UserUpdater.promises.updateUser(query, update)
  1119. })
  1120. it('should fallback to confirmedAt for date check', async function() {
  1121. const response = await userHelper.request.get('/user/emails')
  1122. expect(response.statusCode).to.equal(200)
  1123. const fullEmails = JSON.parse(response.body)
  1124. expect(fullEmails.length).to.equal(4)
  1125. expect(fullEmails[0].affiliation).to.not.exist
  1126. expect(fullEmails[2].reconfirmedAt).to.not.exist
  1127. expect(
  1128. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1129. ).to.equal(true)
  1130. expect(
  1131. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1132. ).to.equal(true)
  1133. expect(
  1134. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1135. ).to.equal(true)
  1136. })
  1137. })
  1138. })
  1139. })
  1140. })