ProjectInviteTests.coffee 17 KB

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