ProjectInviteTests.coffee 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. expect = require("chai").expect
  2. Async = require("async")
  3. User = require "./helpers/User"
  4. request = require "./helpers/request"
  5. settings = require "settings-sharelatex"
  6. CollaboratorsEmailHandler = require "../../../app/js/Features/Collaborators/CollaboratorsEmailHandler"
  7. createInvite = (sendingUser, projectId, email, callback=(err, invite)->) ->
  8. sendingUser.getCsrfToken (err) ->
  9. return callback(err) if err
  10. sendingUser.request.post {
  11. uri: "/project/#{projectId}/invite",
  12. json:
  13. email: email
  14. privileges: 'readAndWrite'
  15. }, (err, response, body) ->
  16. return callback(err) if err
  17. callback(null, body.invite)
  18. createProject = (owner, projectName, callback=(err, projectId, project)->) ->
  19. owner.createProject projectName, (err, projectId) ->
  20. throw err if err
  21. fakeProject = {
  22. _id: projectId,
  23. name: projectName,
  24. owner_ref: owner
  25. }
  26. callback(err, projectId, fakeProject)
  27. createProjectAndInvite = (owner, projectName, email, callback=(err, project, invite)->) ->
  28. createProject owner, projectName, (err, projectId, project) ->
  29. return callback(err) if err
  30. createInvite owner, projectId, email, (err, invite) ->
  31. return callback(err) if err
  32. link = CollaboratorsEmailHandler._buildInviteUrl(project, invite)
  33. callback(null, project, invite, link)
  34. revokeInvite = (sendingUser, projectId, inviteId, callback=(err)->) ->
  35. sendingUser.getCsrfToken (err) ->
  36. return callback(err) if err
  37. sendingUser.request.delete {
  38. uri: "/project/#{projectId}/invite/#{inviteId}",
  39. }, (err, response, body) ->
  40. return callback(err) if err
  41. callback(null)
  42. # Actions
  43. tryFollowInviteLink = (user, link, callback=(err, response, body)->) ->
  44. user.request.get {
  45. uri: link
  46. baseUrl: null
  47. }, callback
  48. tryAcceptInvite = (user, invite, callback=(err, response, body)->) ->
  49. user.request.post {
  50. uri: "/project/#{invite.projectId}/invite/token/#{invite.token}/accept"
  51. json:
  52. token: invite.token
  53. }, callback
  54. tryRegisterUser = (user, email, redir, callback=(err, response, body)->) ->
  55. user.getCsrfToken (error) =>
  56. return callback(error) if error?
  57. user.request.post {
  58. url: "/register"
  59. json:
  60. email: email
  61. password: "some_weird_password"
  62. redir: redir
  63. }, callback
  64. tryFollowLoginLink = (user, loginLink, callback=(err, response, body)->) ->
  65. user.getCsrfToken (error) =>
  66. return callback(error) if error?
  67. user.request.get loginLink, callback
  68. tryLoginUser = (user, redir, callback=(err, response, body)->) ->
  69. user.getCsrfToken (error) =>
  70. return callback(error) if error?
  71. user.request.post {
  72. url: "/login"
  73. json:
  74. email: user.email
  75. password: user.password
  76. redir: redir
  77. }, callback
  78. tryGetInviteList = (user, projectId, callback=(err, response, body)->) ->
  79. user.getCsrfToken (error) =>
  80. return callback(error) if error?
  81. user.request.get {
  82. url: "/project/#{projectId}/invites"
  83. json: true
  84. }, callback
  85. tryJoinProject = (user, projectId, callback=(err, response, body)->) ->
  86. user.getCsrfToken (error) =>
  87. return callback(error) if error?
  88. user.request.post {
  89. url: "/project/#{projectId}/join"
  90. qs: {user_id: user._id}
  91. auth:
  92. user: settings.apis.web.user
  93. pass: settings.apis.web.pass
  94. sendImmediately: true
  95. json: true
  96. jar: false
  97. }, callback
  98. # Expectations
  99. expectProjectAccess = (user, projectId, callback=(err,result)->) ->
  100. # should have access to project
  101. user.openProject projectId, (err) =>
  102. expect(err).to.be.oneOf [null, undefined]
  103. callback()
  104. expectNoProjectAccess = (user, projectId, callback=(err,result)->) ->
  105. # should not have access to project page
  106. user.openProject projectId, (err) =>
  107. expect(err).to.be.instanceof Error
  108. callback()
  109. expectInvitePage = (user, link, callback=(err,result)->) ->
  110. # view invite
  111. tryFollowInviteLink user, link, (err, response, body) ->
  112. expect(err).to.be.oneOf [null, undefined]
  113. expect(response.statusCode).to.equal 200
  114. expect(body).to.match new RegExp("<title>Project Invite - .*</title>")
  115. callback()
  116. expectInvalidInvitePage = (user, link, callback=(err,result)->) ->
  117. # view invalid invite
  118. tryFollowInviteLink user, link, (err, response, body) ->
  119. expect(err).to.be.oneOf [null, undefined]
  120. expect(response.statusCode).to.equal 200
  121. expect(body).to.match new RegExp("<title>Invalid Invite - .*</title>")
  122. callback()
  123. expectInviteRedirectToRegister = (user, link, callback=(err,result)->) ->
  124. # view invite, redirect to `/register`
  125. tryFollowInviteLink user, link, (err, response, body) ->
  126. expect(err).to.be.oneOf [null, undefined]
  127. expect(response.statusCode).to.equal 302
  128. expect(response.headers.location).to.match new RegExp("^/register\?.*redir=.*$")
  129. # follow redirect to register page and extract the redirectUrl from form
  130. user.request.get response.headers.location, (err, response, body) ->
  131. redirectUrl = body.match(/input name="redir" type="hidden" value="([^"]*)"/m)?[1]
  132. loginUrl = body.match(/href="([^"]*)">\s*Login here/m)?[1]
  133. expect(redirectUrl).to.not.be.oneOf [null, undefined]
  134. expect(loginUrl).to.not.be.oneOf [null, undefined]
  135. callback(null, redirectUrl, loginUrl)
  136. expectLoginPage = (user, loginLink, callback=(err, result)->) ->
  137. tryFollowLoginLink user, loginLink, (err, response, body) ->
  138. expect(err).to.be.oneOf [null, undefined]
  139. expect(response.statusCode).to.equal 200
  140. expect(body).to.match new RegExp("<title>Login - .*</title>")
  141. redirectUrl = body.match(/input name="redir" type="hidden" value="([^"]*)"/m)?[1]
  142. callback(null, redirectUrl)
  143. expectLoginRedirectToInvite = (user, redir, link, callback=(err, result)->) ->
  144. tryLoginUser user, redir, (err, response, body) ->
  145. expect(err).to.be.oneOf [null, undefined]
  146. expect(response.statusCode).to.equal 200
  147. expect(link).to.match new RegExp("^.*#{body.redir}\?.*$")
  148. callback(null, null)
  149. expectRegistrationRedirectToInvite = (user, email, redir, link, callback=(err, result)->) ->
  150. tryRegisterUser user, email, redir, (err, response, body) ->
  151. expect(err).to.be.oneOf [null, undefined]
  152. expect(response.statusCode).to.equal 200
  153. expect(link).to.match new RegExp("^.*#{body.redir}\?.*$")
  154. callback(null, null)
  155. expectInviteRedirectToProject = (user, link, invite, callback=(err,result)->) ->
  156. # view invite, redirect straight to project
  157. tryFollowInviteLink user, link, (err, response, body) ->
  158. expect(err).to.be.oneOf [null, undefined]
  159. expect(response.statusCode).to.equal 302
  160. expect(response.headers.location).to.equal "/project/#{invite.projectId}"
  161. callback()
  162. expectAcceptInviteAndRedirect = (user, invite, callback=(err,result)->) ->
  163. # should accept the invite and redirect to project
  164. tryAcceptInvite user, invite, (err, response, body) =>
  165. expect(err).to.be.oneOf [null, undefined]
  166. expect(response.statusCode).to.equal 302
  167. expect(response.headers.location).to.equal "/project/#{invite.projectId}"
  168. callback()
  169. expectInviteListCount = (user, projectId, count, callback=(err)->) ->
  170. tryGetInviteList user, projectId, (err, response, body) ->
  171. expect(err).to.be.oneOf [null, undefined]
  172. expect(response.statusCode).to.equal 200
  173. expect(body).to.have.all.keys ['invites']
  174. expect(body.invites.length).to.equal count
  175. callback()
  176. expectInvitesInJoinProjectCount = (user, projectId, count, callback=(err,result)->) ->
  177. tryJoinProject user, projectId, (err, response, body) ->
  178. expect(err).to.be.oneOf [null, undefined]
  179. expect(response.statusCode).to.equal 200
  180. expect(body.project).to.contain.keys ['invites']
  181. expect(body.project.invites.length).to.equal count
  182. callback()
  183. describe "ProjectInviteTests", ->
  184. before (done) ->
  185. @sendingUser = new User()
  186. @user = new User()
  187. @site_admin = new User({email: "admin@example.com"})
  188. @email = 'smoketestuser@example.com'
  189. @projectName = 'sharing test'
  190. Async.series [
  191. (cb) => @user.login cb
  192. (cb) => @user.logout cb
  193. (cb) => @sendingUser.login cb
  194. ], done
  195. describe 'creating invites', ->
  196. beforeEach (done) ->
  197. @projectName = "wat"
  198. @projectId = null
  199. @fakeProject = null
  200. done()
  201. afterEach ->
  202. describe 'creating two invites', ->
  203. beforeEach (done) ->
  204. Async.series [
  205. (cb) =>
  206. createProject @sendingUser, @projectName, (err, projectId, project) =>
  207. @projectId = projectId
  208. @fakeProject = project
  209. cb()
  210. ], done
  211. afterEach (done) ->
  212. Async.series [
  213. (cb) => @sendingUser.deleteProject(@projectId, cb)
  214. (cb) => @sendingUser.deleteProject(@projectId, cb)
  215. ], done
  216. it 'should allow the project owner to create and remove invites', (done) ->
  217. @invite = null
  218. Async.series [
  219. (cb) => expectProjectAccess @sendingUser, @projectId, cb
  220. (cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
  221. # create invite, check invite list count
  222. (cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
  223. return cb(err) if err
  224. @invite = invite
  225. cb()
  226. (cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
  227. (cb) => revokeInvite @sendingUser, @projectId, @invite._id, cb
  228. (cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
  229. # and a second time
  230. (cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
  231. return cb(err) if err
  232. @invite = invite
  233. cb()
  234. (cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
  235. # check the joinProject view
  236. (cb) => expectInvitesInJoinProjectCount @sendingUser, @projectId, 1, cb
  237. # revoke invite
  238. (cb) => revokeInvite @sendingUser, @projectId, @invite._id, cb
  239. (cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
  240. (cb) => expectInvitesInJoinProjectCount @sendingUser, @projectId, 0, cb
  241. ], done
  242. it 'should allow the project owner to many invites at once', (done) ->
  243. @inviteOne = null
  244. @inviteTwo = null
  245. Async.series [
  246. (cb) => expectProjectAccess @sendingUser, @projectId, cb
  247. (cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
  248. # create first invite
  249. (cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
  250. return cb(err) if err
  251. @inviteOne = invite
  252. cb()
  253. (cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
  254. # and a second
  255. (cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
  256. return cb(err) if err
  257. @inviteTwo = invite
  258. cb()
  259. # should have two
  260. (cb) => expectInviteListCount @sendingUser, @projectId, 2, cb
  261. (cb) => expectInvitesInJoinProjectCount @sendingUser, @projectId, 2, cb
  262. # revoke first
  263. (cb) => revokeInvite @sendingUser, @projectId, @inviteOne._id, cb
  264. (cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
  265. # revoke second
  266. (cb) => revokeInvite @sendingUser, @projectId, @inviteTwo._id, cb
  267. (cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
  268. ], done
  269. describe 'clicking the invite link', ->
  270. beforeEach (done) ->
  271. @projectId = null
  272. @fakeProject = null
  273. done()
  274. describe "user is logged in already", ->
  275. beforeEach (done) ->
  276. Async.series [
  277. (cb) =>
  278. createProjectAndInvite @sendingUser, @projectName, @email, (err, project, invite, link) =>
  279. @projectId = project._id
  280. @fakeProject = project
  281. @invite = invite
  282. @link = link
  283. cb()
  284. (cb) =>
  285. @user.login (err) =>
  286. if err
  287. throw err
  288. cb()
  289. ], done
  290. afterEach (done) ->
  291. Async.series [
  292. (cb) => @sendingUser.deleteProject(@projectId, cb)
  293. (cb) => @sendingUser.deleteProject(@projectId, cb)
  294. (cb) => revokeInvite(@sendingUser, @projectId, @invite._id, cb)
  295. ], done
  296. describe 'user is already a member of the project', ->
  297. beforeEach (done) ->
  298. Async.series [
  299. (cb) => expectInvitePage @user, @link, cb
  300. (cb) => expectAcceptInviteAndRedirect @user, @invite, cb
  301. (cb) => expectProjectAccess @user, @invite.projectId, cb
  302. ], done
  303. describe 'when user clicks on the invite a second time', ->
  304. it 'should just redirect to the project page', (done) ->
  305. Async.series [
  306. (cb) => expectProjectAccess @user, @invite.projectId, cb
  307. (cb) => expectInviteRedirectToProject @user, @link, @invite, cb
  308. (cb) => expectProjectAccess @user, @invite.projectId, cb
  309. ], done
  310. describe 'when the user recieves another invite to the same project', ->
  311. it 'should redirect to the project page', (done) ->
  312. Async.series [
  313. (cb) =>
  314. createInvite @sendingUser, @projectId, @email, (err, invite) =>
  315. if err
  316. throw err
  317. @secondInvite = invite
  318. @secondLink = CollaboratorsEmailHandler._buildInviteUrl(@fakeProject, invite)
  319. cb()
  320. (cb) => expectInviteRedirectToProject @user, @secondLink, @secondInvite, cb
  321. (cb) => expectProjectAccess @user, @invite.projectId, cb
  322. (cb) => revokeInvite @sendingUser, @projectId, @secondInvite._id, cb
  323. ], done
  324. describe 'user is not a member of the project', ->
  325. it 'should not grant access if the user does not accept the invite', (done) ->
  326. Async.series(
  327. [
  328. (cb) => expectInvitePage @user, @link, cb
  329. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  330. ], done
  331. )
  332. it 'should render the invalid-invite page if the token is invalid', (done) ->
  333. Async.series(
  334. [
  335. (cb) =>
  336. link = @link.replace(@invite.token, 'not_a_real_token')
  337. expectInvalidInvitePage @user, link, cb
  338. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  339. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  340. ], done
  341. )
  342. it 'should allow the user to accept the invite and access the project', (done) ->
  343. Async.series(
  344. [
  345. (cb) => expectInvitePage @user, @link, cb
  346. (cb) => expectAcceptInviteAndRedirect @user, @invite, cb
  347. (cb) => expectProjectAccess @user, @invite.projectId, cb
  348. ], done
  349. )
  350. describe 'user is not logged in initially', ->
  351. before (done) ->
  352. @user.logout done
  353. beforeEach (done) ->
  354. Async.series [
  355. (cb) =>
  356. createProjectAndInvite @sendingUser, @projectName, @email, (err, project, invite, link) =>
  357. @projectId = project._id
  358. @fakeProject = project
  359. @invite = invite
  360. @link = link
  361. cb()
  362. ], done
  363. afterEach (done) ->
  364. Async.series [
  365. (cb) => @sendingUser.deleteProject(@projectId, cb)
  366. (cb) => @sendingUser.deleteProject(@projectId, cb)
  367. (cb) => revokeInvite(@sendingUser, @projectId, @invite._id, cb)
  368. ], done
  369. describe 'registration prompt workflow with valid token', ->
  370. it 'should redirect to the register page', (done) ->
  371. Async.series [
  372. (cb) => expectInviteRedirectToRegister(@user, @link, cb)
  373. ], done
  374. it 'should allow user to accept the invite if the user registers a new account', (done) ->
  375. Async.series [
  376. (cb) =>
  377. expectInviteRedirectToRegister @user, @link, (err, redirectUrl) =>
  378. @_redir = redirectUrl
  379. cb()
  380. (cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", @_redir, @link, cb
  381. (cb) => expectInvitePage @user, @link, cb
  382. (cb) => expectAcceptInviteAndRedirect @user, @invite, cb
  383. (cb) => expectProjectAccess @user, @invite.projectId, cb
  384. ], done
  385. describe 'registration prompt workflow with non-valid token', ->
  386. before (done)->
  387. @user.logout done
  388. it 'should redirect to the register page', (done) ->
  389. Async.series [
  390. (cb) => expectInviteRedirectToRegister(@user, @link, cb)
  391. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  392. ], done
  393. it 'should display invalid-invite if the user registers a new account', (done) ->
  394. badLink = @link.replace(@invite.token, 'not_a_real_token')
  395. Async.series [
  396. (cb) =>
  397. expectInviteRedirectToRegister @user, badLink, (err, redirectUrl) =>
  398. @_redir = redirectUrl
  399. cb()
  400. (cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", @_redir, badLink, cb
  401. (cb) => expectInvalidInvitePage @user, badLink, cb
  402. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  403. ], done
  404. describe 'login workflow with valid token', ->
  405. before (done)->
  406. @user.logout done
  407. it 'should redirect to the register page', (done) ->
  408. Async.series [
  409. (cb) => expectInviteRedirectToRegister(@user, @link, cb)
  410. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  411. ], done
  412. it 'should allow the user to login to view the invite', (done) ->
  413. Async.series [
  414. (cb) =>
  415. expectInviteRedirectToRegister @user, @link, (err, redirectUrl, loginUrl) =>
  416. @_redir = redirectUrl
  417. @_loginLink = loginUrl
  418. cb()
  419. (cb) =>
  420. expectLoginPage @user, @_loginLink, (err, redirectUrl) =>
  421. expect(@_redir).to.equal redirectUrl
  422. cb()
  423. (cb) => expectLoginRedirectToInvite @user, @_redir, @link, cb
  424. (cb) => expectInvitePage @user, @link, cb
  425. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  426. ], done
  427. it 'should allow user to accept the invite if the user registers a new account', (done) ->
  428. Async.series [
  429. (cb) => expectInvitePage @user, @link, cb
  430. (cb) => expectAcceptInviteAndRedirect @user, @invite, cb
  431. (cb) => expectProjectAccess @user, @invite.projectId, cb
  432. ], done
  433. describe 'login workflow with non-valid token', ->
  434. before (done)->
  435. @user.logout done
  436. it 'should redirect to the register page', (done) ->
  437. Async.series [
  438. (cb) => expectInviteRedirectToRegister(@user, @link, cb)
  439. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  440. ], done
  441. it 'should show the invalid-invite page once the user has logged in', (done) ->
  442. badLink = @link.replace(@invite.token, 'not_a_real_token')
  443. Async.series [
  444. (cb) =>
  445. expectInviteRedirectToRegister @user, badLink, (err, redirectUrl, loginUrl) =>
  446. @_redir = redirectUrl
  447. @_loginLink = loginUrl
  448. cb()
  449. (cb) =>
  450. expectLoginPage @user, @_loginLink, (err, redirectUrl) =>
  451. expect(@_redir).to.equal redirectUrl
  452. cb()
  453. (cb) => expectLoginRedirectToInvite @user, @_redir, badLink, cb
  454. (cb) => expectInvalidInvitePage @user, badLink, cb
  455. (cb) => expectNoProjectAccess @user, @invite.projectId, cb
  456. ], done