ProjectInviteTests.coffee 17 KB

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