ProjectInviteTests.mjs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. import { expect } from 'chai'
  2. import Async from 'async'
  3. import User from './helpers/User.mjs'
  4. import settings from '@overleaf/settings'
  5. import CollaboratorsEmailHandler from '../../../app/src/Features/Collaborators/CollaboratorsEmailHandler.mjs'
  6. import CollaboratorsInviteHelper from '../../../app/src/Features/Collaborators/CollaboratorsInviteHelper.js'
  7. import Features from '../../../app/src/infrastructure/Features.js'
  8. import sinon from 'sinon'
  9. let generateTokenSpy
  10. const createInvite = (sendingUser, projectId, email, callback) => {
  11. sendingUser.getCsrfToken(err => {
  12. if (err) {
  13. return callback(err)
  14. }
  15. sendingUser.request.post(
  16. {
  17. uri: `/project/${projectId}/invite`,
  18. json: {
  19. email,
  20. privileges: 'readAndWrite',
  21. },
  22. },
  23. (err, response, body) => {
  24. if (err) {
  25. return callback(err)
  26. }
  27. expect(response.statusCode).to.equal(200)
  28. expect(body.error).to.not.exist
  29. expect(body.invite).to.exist
  30. callback(null, body.invite)
  31. }
  32. )
  33. })
  34. }
  35. const createProject = (owner, projectName, callback) => {
  36. owner.createProject(projectName, (err, projectId) => {
  37. if (err) {
  38. throw err
  39. }
  40. const fakeProject = {
  41. _id: projectId,
  42. name: projectName,
  43. owner_ref: owner,
  44. }
  45. callback(err, projectId, fakeProject)
  46. })
  47. }
  48. const createProjectAndInvite = (owner, projectName, email, callback) => {
  49. createProject(owner, projectName, (err, projectId, project) => {
  50. if (err) {
  51. return callback(err)
  52. }
  53. createInvite(owner, projectId, email, (err, invite) => {
  54. if (err) {
  55. return callback(err)
  56. }
  57. // attach the token to the invite
  58. invite.token = generateTokenSpy.getCall(0).returnValue
  59. const link = CollaboratorsEmailHandler._buildInviteUrl(project, invite)
  60. callback(null, project, invite, link)
  61. })
  62. })
  63. }
  64. const revokeInvite = (sendingUser, projectId, inviteId, callback) => {
  65. sendingUser.getCsrfToken(err => {
  66. if (err) {
  67. return callback(err)
  68. }
  69. sendingUser.request.delete(
  70. {
  71. uri: `/project/${projectId}/invite/${inviteId}`,
  72. },
  73. err => {
  74. if (err) {
  75. return callback(err)
  76. }
  77. callback()
  78. }
  79. )
  80. })
  81. }
  82. // Actions
  83. const tryFollowInviteLink = (user, link, callback) => {
  84. user.request.get(
  85. {
  86. uri: link,
  87. baseUrl: null,
  88. },
  89. callback
  90. )
  91. }
  92. const tryAcceptInvite = (user, invite, projectId, callback) => {
  93. user.getCsrfToken(err => {
  94. if (err) {
  95. return callback(err)
  96. }
  97. user.request.post(
  98. {
  99. uri: `/project/${projectId}/invite/token/${invite.token}/accept`,
  100. json: {
  101. token: invite.token,
  102. },
  103. },
  104. callback
  105. )
  106. })
  107. }
  108. const tryFollowLoginLink = (user, loginLink, callback) => {
  109. user.getCsrfToken(error => {
  110. if (error != null) {
  111. return callback(error)
  112. }
  113. user.request.get(loginLink, callback)
  114. })
  115. }
  116. const tryLoginUser = (user, callback) => {
  117. user.getCsrfToken(error => {
  118. if (error != null) {
  119. return callback(error)
  120. }
  121. user.request.post(
  122. {
  123. url: '/login',
  124. json: {
  125. email: user.email,
  126. password: user.password,
  127. 'g-recaptcha-response': 'valid',
  128. },
  129. },
  130. callback
  131. )
  132. })
  133. }
  134. const tryGetInviteList = (user, projectId, callback) => {
  135. user.getCsrfToken(error => {
  136. if (error != null) {
  137. return callback(error)
  138. }
  139. user.request.get(
  140. {
  141. url: `/project/${projectId}/invites`,
  142. json: true,
  143. },
  144. callback
  145. )
  146. })
  147. }
  148. const tryJoinProject = (user, projectId, callback) => {
  149. return user.getCsrfToken(error => {
  150. if (error != null) {
  151. return callback(error)
  152. }
  153. user.request.post(
  154. {
  155. url: `/project/${projectId}/join`,
  156. auth: {
  157. user: settings.apis.web.user,
  158. pass: settings.apis.web.pass,
  159. sendImmediately: true,
  160. },
  161. json: { userId: user._id },
  162. jar: false,
  163. },
  164. callback
  165. )
  166. })
  167. }
  168. // Expectations
  169. const expectProjectAccess = (user, projectId, callback) => {
  170. // should have access to project
  171. user.openProject(projectId, err => {
  172. expect(err).not.to.exist
  173. callback()
  174. })
  175. }
  176. const expectNoProjectAccess = (user, projectId, callback) => {
  177. // should not have access to project page
  178. user.openProject(projectId, err => {
  179. expect(err).to.be.instanceof(Error)
  180. callback()
  181. })
  182. }
  183. const expectInvitePage = (user, link, callback) => {
  184. // view invite
  185. tryFollowInviteLink(user, link, (err, response, body) => {
  186. expect(err).not.to.exist
  187. expect(response.statusCode).to.equal(200)
  188. expect(body).to.match(/<title[^>]*>Project Invite - .*<\/title>/)
  189. callback()
  190. })
  191. }
  192. const expectInvalidInvitePage = (user, link, callback) => {
  193. // view invalid invite
  194. tryFollowInviteLink(user, link, (err, response, body) => {
  195. expect(err).not.to.exist
  196. expect(response.statusCode).to.equal(404)
  197. expect(body).to.match(/<title[^>]*>Invalid Invite - .*<\/title>/)
  198. callback()
  199. })
  200. }
  201. const expectInviteRedirectToRegister = (user, link, callback) => {
  202. // view invite, redirect to `/register`
  203. tryFollowInviteLink(user, link, (err, response) => {
  204. expect(err).not.to.exist
  205. expect(response.statusCode).to.equal(302)
  206. expect(response.headers.location).to.match(/^\/register.*$/)
  207. user.getSession((err, session) => {
  208. if (err) return callback(err)
  209. expect(session.sharedProjectData).deep.equals({
  210. project_name: PROJECT_NAME,
  211. user_first_name: OWNER_NAME,
  212. })
  213. callback()
  214. })
  215. })
  216. }
  217. const expectLoginPage = (user, callback) => {
  218. tryFollowLoginLink(user, '/login', (err, response, body) => {
  219. expect(err).not.to.exist
  220. expect(response.statusCode).to.equal(200)
  221. expect(body).to.match(
  222. /<title[^>]*>(Login|Log in to Overleaf) - .*<\/title>/
  223. )
  224. callback()
  225. })
  226. }
  227. const expectLoginRedirectToInvite = (user, link, callback) => {
  228. tryLoginUser(user, (err, response) => {
  229. expect(err).not.to.exist
  230. expect(response.statusCode).to.equal(200)
  231. callback()
  232. })
  233. }
  234. const expectRegistrationRedirectToInvite = (user, link, callback) => {
  235. user.register((err, _user, response) => {
  236. expect(err).not.to.exist
  237. expect(response.statusCode).to.equal(200)
  238. if (response.body.redir === '/registration/try-premium') {
  239. user.request.get('/registration/onboarding', (err, response) => {
  240. if (err) return callback(err)
  241. expect(response.statusCode).to.equal(200)
  242. callback()
  243. })
  244. } else {
  245. expect(response.body.redir).to.equal(link)
  246. callback()
  247. }
  248. })
  249. }
  250. const expectInviteRedirectToProject = (user, link, projectId, callback) => {
  251. // view invite, redirect straight to project
  252. tryFollowInviteLink(user, link, (err, response) => {
  253. expect(err).not.to.exist
  254. expect(response.statusCode).to.equal(302)
  255. expect(response.headers.location).to.equal(`/project/${projectId}`)
  256. callback()
  257. })
  258. }
  259. const expectAcceptInviteAndRedirect = (user, invite, projectId, callback) => {
  260. // should accept the invite and redirect to project
  261. tryAcceptInvite(user, invite, projectId, (err, response) => {
  262. expect(err).not.to.exist
  263. expect(response.statusCode).to.equal(302)
  264. expect(response.headers.location).to.equal(`/project/${projectId}`)
  265. callback()
  266. })
  267. }
  268. const expectInviteListCount = (user, projectId, count, callback) => {
  269. tryGetInviteList(user, projectId, (err, response, body) => {
  270. expect(err).not.to.exist
  271. expect(response.statusCode).to.equal(200)
  272. expect(body).to.have.all.keys(['invites'])
  273. expect(body.invites.length).to.equal(count)
  274. callback()
  275. })
  276. }
  277. const expectInvitesInJoinProjectCount = (user, projectId, count, callback) => {
  278. tryJoinProject(user, projectId, (err, response, body) => {
  279. expect(err).not.to.exist
  280. expect(response.statusCode).to.equal(200)
  281. expect(body.project).to.contain.keys(['invites'])
  282. expect(body.project.invites.length).to.equal(count)
  283. callback()
  284. })
  285. }
  286. const PROJECT_NAME = 'project name for sharing test'
  287. const OWNER_NAME = 'sending user name'
  288. describe('ProjectInviteTests', function () {
  289. beforeEach(function (done) {
  290. this.sendingUser = new User()
  291. this.user = new User()
  292. this.site_admin = new User({ email: `admin+${Math.random()}@example.com` })
  293. this.email = `smoketestuser+${Math.random()}@example.com`
  294. generateTokenSpy = sinon.spy(CollaboratorsInviteHelper, 'generateToken')
  295. Async.series(
  296. [
  297. cb => this.sendingUser.ensureUserExists(cb),
  298. cb => this.sendingUser.upgradeFeatures({ collaborators: 10 }, cb),
  299. cb => this.sendingUser.login(cb),
  300. cb =>
  301. this.sendingUser.mongoUpdate(
  302. {
  303. $set: { first_name: OWNER_NAME },
  304. },
  305. cb
  306. ),
  307. ],
  308. done
  309. )
  310. })
  311. afterEach(function () {
  312. generateTokenSpy.restore()
  313. })
  314. describe('creating invites', function () {
  315. describe('creating two invites', function () {
  316. beforeEach(function (done) {
  317. createProject(
  318. this.sendingUser,
  319. PROJECT_NAME,
  320. (err, projectId, project) => {
  321. expect(err).not.to.exist
  322. this.projectId = projectId
  323. this.fakeProject = project
  324. done()
  325. }
  326. )
  327. })
  328. it('should fail if email is not a string', function (done) {
  329. this.sendingUser.getCsrfToken(err => {
  330. if (err) {
  331. return done(err)
  332. }
  333. this.sendingUser.request.post(
  334. {
  335. uri: `/project/${this.projectId}/invite`,
  336. json: {
  337. email: {},
  338. privileges: 'readAndWrite',
  339. },
  340. },
  341. (err, response, body) => {
  342. if (err) {
  343. return done(err)
  344. }
  345. expect(response.statusCode).to.equal(400)
  346. expect(body.details).to.have.lengthOf(1)
  347. expect(response.body.details[0].path).to.eql(['body', 'email'])
  348. expect(response.body.details[0].message).to.equal(
  349. 'Invalid input: expected string, received object'
  350. )
  351. done()
  352. }
  353. )
  354. })
  355. })
  356. it('should fail on invalid privileges', function (done) {
  357. this.sendingUser.getCsrfToken(err => {
  358. if (err) {
  359. return done(err)
  360. }
  361. this.sendingUser.request.post(
  362. {
  363. uri: `/project/${this.projectId}/invite`,
  364. json: {
  365. email: this.email,
  366. privileges: 'invalid-privilege',
  367. },
  368. },
  369. (err, response, body) => {
  370. if (err) {
  371. return done(err)
  372. }
  373. expect(response.statusCode).to.equal(400)
  374. expect(body.details).to.have.lengthOf(1)
  375. expect(response.body.details[0].path).to.eql([
  376. 'body',
  377. 'privileges',
  378. ])
  379. expect(response.body.details[0].message).to.equal(
  380. 'Invalid option: expected one of "readOnly"|"readAndWrite"|"review"'
  381. )
  382. done()
  383. }
  384. )
  385. })
  386. })
  387. it('should allow the project owner to create and remove invites', function (done) {
  388. Async.series(
  389. [
  390. cb => expectProjectAccess(this.sendingUser, this.projectId, cb),
  391. cb =>
  392. expectInviteListCount(this.sendingUser, this.projectId, 0, cb),
  393. // create invite, check invite list count
  394. cb => {
  395. createInvite(
  396. this.sendingUser,
  397. this.projectId,
  398. this.email,
  399. (err, invite) => {
  400. if (err) {
  401. return cb(err)
  402. }
  403. this.invite = invite
  404. cb()
  405. }
  406. )
  407. },
  408. cb =>
  409. expectInviteListCount(this.sendingUser, this.projectId, 1, cb),
  410. cb =>
  411. revokeInvite(
  412. this.sendingUser,
  413. this.projectId,
  414. this.invite._id,
  415. cb
  416. ),
  417. cb =>
  418. expectInviteListCount(this.sendingUser, this.projectId, 0, cb),
  419. // and a second time
  420. cb => {
  421. createInvite(
  422. this.sendingUser,
  423. this.projectId,
  424. this.email,
  425. (err, invite) => {
  426. if (err) {
  427. return cb(err)
  428. }
  429. this.invite = invite
  430. cb()
  431. }
  432. )
  433. },
  434. cb =>
  435. expectInviteListCount(this.sendingUser, this.projectId, 1, cb),
  436. // check the joinProject view
  437. cb =>
  438. expectInvitesInJoinProjectCount(
  439. this.sendingUser,
  440. this.projectId,
  441. 1,
  442. cb
  443. ),
  444. // revoke invite
  445. cb =>
  446. revokeInvite(
  447. this.sendingUser,
  448. this.projectId,
  449. this.invite._id,
  450. cb
  451. ),
  452. cb =>
  453. expectInviteListCount(this.sendingUser, this.projectId, 0, cb),
  454. cb =>
  455. expectInvitesInJoinProjectCount(
  456. this.sendingUser,
  457. this.projectId,
  458. 0,
  459. cb
  460. ),
  461. ],
  462. done
  463. )
  464. })
  465. it('should allow the project owner to create many invites at once', function (done) {
  466. Async.series(
  467. [
  468. cb => expectProjectAccess(this.sendingUser, this.projectId, cb),
  469. cb =>
  470. expectInviteListCount(this.sendingUser, this.projectId, 0, cb),
  471. // create first invite
  472. cb => {
  473. createInvite(
  474. this.sendingUser,
  475. this.projectId,
  476. this.email,
  477. (err, invite) => {
  478. if (err) {
  479. return cb(err)
  480. }
  481. this.inviteOne = invite
  482. cb()
  483. }
  484. )
  485. },
  486. cb =>
  487. expectInviteListCount(this.sendingUser, this.projectId, 1, cb),
  488. // and a second
  489. cb => {
  490. createInvite(
  491. this.sendingUser,
  492. this.projectId,
  493. this.email,
  494. (err, invite) => {
  495. if (err) {
  496. return cb(err)
  497. }
  498. this.inviteTwo = invite
  499. cb()
  500. }
  501. )
  502. },
  503. // should have two
  504. cb =>
  505. expectInviteListCount(this.sendingUser, this.projectId, 2, cb),
  506. cb =>
  507. expectInvitesInJoinProjectCount(
  508. this.sendingUser,
  509. this.projectId,
  510. 2,
  511. cb
  512. ),
  513. // revoke first
  514. cb =>
  515. revokeInvite(
  516. this.sendingUser,
  517. this.projectId,
  518. this.inviteOne._id,
  519. cb
  520. ),
  521. cb =>
  522. expectInviteListCount(this.sendingUser, this.projectId, 1, cb),
  523. // revoke second
  524. cb =>
  525. revokeInvite(
  526. this.sendingUser,
  527. this.projectId,
  528. this.inviteTwo._id,
  529. cb
  530. ),
  531. cb =>
  532. expectInviteListCount(this.sendingUser, this.projectId, 0, cb),
  533. ],
  534. done
  535. )
  536. })
  537. })
  538. })
  539. describe('clicking the invite link', function () {
  540. beforeEach(function (done) {
  541. createProjectAndInvite(
  542. this.sendingUser,
  543. PROJECT_NAME,
  544. this.email,
  545. (err, project, invite, link) => {
  546. expect(err).not.to.exist
  547. this.projectId = project._id
  548. this.fakeProject = project
  549. this.invite = invite
  550. this.link = link
  551. done()
  552. }
  553. )
  554. })
  555. describe('user is logged in already', function () {
  556. beforeEach(function (done) {
  557. this.user.login(done)
  558. })
  559. describe('user is already a member of the project', function () {
  560. beforeEach(function (done) {
  561. Async.series(
  562. [
  563. cb => expectInvitePage(this.user, this.link, cb),
  564. cb =>
  565. expectAcceptInviteAndRedirect(
  566. this.user,
  567. this.invite,
  568. this.projectId,
  569. cb
  570. ),
  571. cb => expectProjectAccess(this.user, this.projectId, cb),
  572. ],
  573. done
  574. )
  575. })
  576. describe('when user clicks on the invite a second time', function () {
  577. it('should just redirect to the project page', function (done) {
  578. Async.series(
  579. [
  580. cb => expectProjectAccess(this.user, this.projectId, cb),
  581. cb =>
  582. expectInviteRedirectToProject(
  583. this.user,
  584. this.link,
  585. this.projectId,
  586. cb
  587. ),
  588. cb => expectProjectAccess(this.user, this.projectId, cb),
  589. ],
  590. done
  591. )
  592. })
  593. describe('when the user recieves another invite to the same project', function () {
  594. it('should redirect to the project page', function (done) {
  595. Async.series(
  596. [
  597. cb => {
  598. createInvite(
  599. this.sendingUser,
  600. this.projectId,
  601. this.email,
  602. (err, invite) => {
  603. if (err) {
  604. throw err
  605. }
  606. this.secondInvite = invite
  607. const token = generateTokenSpy.getCall(1).returnValue
  608. this.secondLink =
  609. CollaboratorsEmailHandler._buildInviteUrl(
  610. this.fakeProject,
  611. { ...invite, token }
  612. )
  613. cb()
  614. }
  615. )
  616. },
  617. cb =>
  618. expectInviteRedirectToProject(
  619. this.user,
  620. this.secondLink,
  621. this.projectId,
  622. cb
  623. ),
  624. cb => expectProjectAccess(this.user, this.projectId, cb),
  625. cb =>
  626. revokeInvite(
  627. this.sendingUser,
  628. this.projectId,
  629. this.secondInvite._id,
  630. cb
  631. ),
  632. ],
  633. done
  634. )
  635. })
  636. })
  637. })
  638. })
  639. describe('user is not a member of the project', function () {
  640. it('should not grant access if the user does not accept the invite', function (done) {
  641. Async.series(
  642. [
  643. cb => expectInvitePage(this.user, this.link, cb),
  644. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  645. ],
  646. done
  647. )
  648. })
  649. it('should render the invalid-invite page if the token is invalid', function (done) {
  650. Async.series(
  651. [
  652. cb => {
  653. const link = this.link.replace(
  654. this.invite.token,
  655. 'not_a_real_token'
  656. )
  657. expectInvalidInvitePage(this.user, link, cb)
  658. },
  659. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  660. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  661. ],
  662. done
  663. )
  664. })
  665. it('should allow the user to accept the invite and access the project', function (done) {
  666. Async.series(
  667. [
  668. cb => expectInvitePage(this.user, this.link, cb),
  669. cb =>
  670. expectAcceptInviteAndRedirect(
  671. this.user,
  672. this.invite,
  673. this.projectId,
  674. cb
  675. ),
  676. cb => expectProjectAccess(this.user, this.projectId, cb),
  677. ],
  678. done
  679. )
  680. })
  681. })
  682. })
  683. describe('user is not logged in initially', function () {
  684. describe('registration prompt workflow with valid token', function () {
  685. before(function () {
  686. if (!Features.hasFeature('registration')) {
  687. this.skip()
  688. }
  689. })
  690. it('should redirect to the register page', function (done) {
  691. expectInviteRedirectToRegister(this.user, this.link, done)
  692. })
  693. it('should allow user to accept the invite if the user registers a new account', function (done) {
  694. Async.series(
  695. [
  696. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  697. cb =>
  698. expectRegistrationRedirectToInvite(this.user, this.link, cb),
  699. cb => expectInvitePage(this.user, this.link, cb),
  700. cb =>
  701. expectAcceptInviteAndRedirect(
  702. this.user,
  703. this.invite,
  704. this.projectId,
  705. cb
  706. ),
  707. cb => expectProjectAccess(this.user, this.projectId, cb),
  708. ],
  709. done
  710. )
  711. })
  712. })
  713. describe('registration prompt workflow with non-valid token', function () {
  714. before(function () {
  715. if (!Features.hasFeature('registration')) {
  716. this.skip()
  717. }
  718. })
  719. it('should redirect to the register page', function (done) {
  720. Async.series(
  721. [
  722. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  723. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  724. ],
  725. done
  726. )
  727. })
  728. it('should display invalid-invite right away', function (done) {
  729. const badLink = this.link.replace(
  730. this.invite.token,
  731. 'not_a_real_token'
  732. )
  733. Async.series(
  734. [
  735. cb => expectInvalidInvitePage(this.user, badLink, cb),
  736. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  737. ],
  738. done
  739. )
  740. })
  741. })
  742. describe('login workflow with valid token', function () {
  743. beforeEach(function (done) {
  744. this.user.ensureUserExists(done)
  745. })
  746. it('should redirect to the register page', function (done) {
  747. Async.series(
  748. [
  749. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  750. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  751. ],
  752. done
  753. )
  754. })
  755. it('should allow the user to login to view the invite', function (done) {
  756. Async.series(
  757. [
  758. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  759. cb => expectLoginPage(this.user, cb),
  760. cb => expectLoginRedirectToInvite(this.user, this.link, cb),
  761. cb => expectInvitePage(this.user, this.link, cb),
  762. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  763. ],
  764. done
  765. )
  766. })
  767. it('should allow user to accept the invite if the user logs in', function (done) {
  768. Async.series(
  769. [
  770. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  771. cb => expectLoginPage(this.user, cb),
  772. cb => expectLoginRedirectToInvite(this.user, this.link, cb),
  773. cb => expectInvitePage(this.user, this.link, cb),
  774. cb =>
  775. expectAcceptInviteAndRedirect(
  776. this.user,
  777. this.invite,
  778. this.projectId,
  779. cb
  780. ),
  781. cb => expectProjectAccess(this.user, this.projectId, cb),
  782. ],
  783. done
  784. )
  785. })
  786. })
  787. describe('login workflow with non-valid token', function () {
  788. it('should redirect to the register page', function (done) {
  789. Async.series(
  790. [
  791. cb => expectInviteRedirectToRegister(this.user, this.link, cb),
  792. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  793. ],
  794. done
  795. )
  796. })
  797. it('should show the invalid-invite page right away', function (done) {
  798. const badLink = this.link.replace(
  799. this.invite.token,
  800. 'not_a_real_token'
  801. )
  802. Async.series(
  803. [
  804. cb => expectInvalidInvitePage(this.user, badLink, cb),
  805. cb => expectNoProjectAccess(this.user, this.projectId, cb),
  806. ],
  807. done
  808. )
  809. })
  810. })
  811. })
  812. })
  813. })