UserEmailsTests.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  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(/^\/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. beforeEach(async function () {
  986. if (!Features.hasFeature('affiliations')) {
  987. this.skip()
  988. }
  989. userHelper = new UserHelper()
  990. defaultEmail = userHelper.getDefaultEmail()
  991. userHelper = await UserHelper.createUser({ email: defaultEmail })
  992. userHelper = await UserHelper.loginUser({
  993. email: defaultEmail,
  994. password: userHelper.getDefaultPassword(),
  995. })
  996. const institutionId = MockV1Api.createInstitution({
  997. commonsAccount: true,
  998. ssoEnabled: false,
  999. maxConfirmationMonths,
  1000. })
  1001. const domain = 'example-affiliation.com'
  1002. MockV1Api.addInstitutionDomain(institutionId, domain, { confirmed: true })
  1003. email1 = `leonard@${domain}`
  1004. email2 = `mccoy@${domain}`
  1005. email3 = `bones@${domain}`
  1006. })
  1007. describe('non SSO affiliations', function () {
  1008. beforeEach(async function () {
  1009. // create a user with 3 affiliations at the institution.
  1010. // all are within in the notification period
  1011. const userId = userHelper.user._id
  1012. await userHelper.addEmailAndConfirm(userId, email1)
  1013. await userHelper.addEmailAndConfirm(userId, email2)
  1014. await userHelper.addEmailAndConfirm(userId, email3)
  1015. await userHelper.changeConfirmedToNotificationPeriod(
  1016. userId,
  1017. email1,
  1018. maxConfirmationMonths
  1019. )
  1020. await userHelper.changeConfirmedToNotificationPeriod(
  1021. userId,
  1022. email2,
  1023. maxConfirmationMonths
  1024. )
  1025. await userHelper.changeConfirmedToPastReconfirmation(
  1026. userId,
  1027. email3,
  1028. maxConfirmationMonths
  1029. )
  1030. })
  1031. describe('when all affiliations in notification period or past reconfirm date', function () {
  1032. it('should flag inReconfirmNotificationPeriod for all affiliations in period', async function () {
  1033. const response = await userHelper.request.get('/user/emails')
  1034. expect(response.statusCode).to.equal(200)
  1035. const fullEmails = JSON.parse(response.body)
  1036. expect(fullEmails.length).to.equal(4)
  1037. expect(fullEmails[0].affiliation).to.not.exist
  1038. expect(
  1039. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1040. ).to.equal(true)
  1041. expect(
  1042. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1043. ).to.equal(true)
  1044. expect(
  1045. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1046. ).to.equal(true)
  1047. })
  1048. it('should set pastReconfirmDate and emailHasInstitutionLicence:false for lapsed confirmations', async function () {
  1049. const response = await userHelper.request.get('/user/emails')
  1050. expect(response.statusCode).to.equal(200)
  1051. const fullEmails = JSON.parse(response.body)
  1052. expect(fullEmails.length).to.equal(4)
  1053. expect(fullEmails[0].affiliation).to.not.exist
  1054. expect(fullEmails[1].affiliation.pastReconfirmDate).to.equal(false)
  1055. expect(fullEmails[1].emailHasInstitutionLicence).to.equal(true)
  1056. expect(fullEmails[2].affiliation.pastReconfirmDate).to.equal(false)
  1057. expect(fullEmails[2].emailHasInstitutionLicence).to.equal(true)
  1058. expect(fullEmails[3].affiliation.pastReconfirmDate).to.equal(true)
  1059. expect(fullEmails[3].emailHasInstitutionLicence).to.equal(false)
  1060. })
  1061. })
  1062. describe('should flag emails before their confirmation expires, but within the notification period', function () {
  1063. beforeEach(async function () {
  1064. const dateInPeriodButNotExpired = moment()
  1065. .subtract(maxConfirmationMonths, 'months')
  1066. .add(14, 'days')
  1067. .toDate()
  1068. const backdatedDays = moment().diff(dateInPeriodButNotExpired, 'days')
  1069. await userHelper.changeConfirmationDate(
  1070. userHelper.user._id,
  1071. email1,
  1072. backdatedDays
  1073. )
  1074. await userHelper.changeConfirmationDate(
  1075. userHelper.user._id,
  1076. email2,
  1077. backdatedDays
  1078. )
  1079. await userHelper.changeConfirmationDate(
  1080. userHelper.user._id,
  1081. email3,
  1082. backdatedDays
  1083. )
  1084. })
  1085. it('should flag the emails', async function () {
  1086. const response = await userHelper.request.get('/user/emails')
  1087. expect(response.statusCode).to.equal(200)
  1088. const fullEmails = JSON.parse(response.body)
  1089. expect(fullEmails.length).to.equal(4)
  1090. expect(fullEmails[0].affiliation).to.not.exist
  1091. expect(
  1092. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1093. ).to.equal(true)
  1094. expect(
  1095. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1096. ).to.equal(true)
  1097. expect(
  1098. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1099. ).to.equal(true)
  1100. // ensure dates are not past reconfirmation period
  1101. function _getLastDayToReconfirm(date) {
  1102. return moment(date).add(maxConfirmationMonths, 'months')
  1103. }
  1104. expect(
  1105. moment(fullEmails[1].reconfirmedAt).isAfter(
  1106. _getLastDayToReconfirm(fullEmails[1].reconfirmedAt)
  1107. )
  1108. ).to.equal(false)
  1109. expect(
  1110. moment(fullEmails[2].reconfirmedAt).isAfter(
  1111. _getLastDayToReconfirm(fullEmails[2].reconfirmedAt)
  1112. )
  1113. ).to.equal(false)
  1114. expect(
  1115. moment(fullEmails[3].reconfirmedAt).isAfter(
  1116. _getLastDayToReconfirm(fullEmails[3].reconfirmedAt)
  1117. )
  1118. ).to.equal(false)
  1119. })
  1120. })
  1121. describe('missing reconfirmedAt', function () {
  1122. beforeEach(async function () {
  1123. const userId = userHelper.user._id
  1124. const query = {
  1125. _id: userId,
  1126. 'emails.email': email2,
  1127. }
  1128. const update = {
  1129. $unset: { 'emails.$.reconfirmedAt': true },
  1130. }
  1131. await UserUpdater.promises.updateUser(query, update)
  1132. })
  1133. it('should fallback to confirmedAt for date check', async function () {
  1134. const response = await userHelper.request.get('/user/emails')
  1135. expect(response.statusCode).to.equal(200)
  1136. const fullEmails = JSON.parse(response.body)
  1137. expect(fullEmails.length).to.equal(4)
  1138. expect(fullEmails[0].affiliation).to.not.exist
  1139. expect(fullEmails[2].reconfirmedAt).to.not.exist
  1140. expect(
  1141. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  1142. ).to.equal(true)
  1143. expect(
  1144. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  1145. ).to.equal(true)
  1146. expect(
  1147. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  1148. ).to.equal(true)
  1149. })
  1150. })
  1151. })
  1152. })
  1153. })