LaunchpadController.test.mjs 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. import { vi } from 'vitest'
  2. import * as path from 'node:path'
  3. import { expect } from 'chai'
  4. import sinon from 'sinon'
  5. import MockResponse from '../../../../../test/unit/src/helpers/MockResponse.js'
  6. const modulePath = path.join(
  7. import.meta.dirname,
  8. '../../../app/src/LaunchpadController.mjs'
  9. )
  10. describe('LaunchpadController', function () {
  11. // esmock doesn't work well with CommonJS dependencies, global imports for
  12. // @overleaf/settings aren't working until that module is migrated to ESM. In the
  13. // meantime, the workaround is to set and restore settings values
  14. beforeEach(async function (ctx) {
  15. ctx.user = {
  16. _id: '323123',
  17. first_name: 'fn',
  18. last_name: 'ln',
  19. save: sinon.stub().callsArgWith(0),
  20. }
  21. ctx.User = {}
  22. ctx.Settings = {
  23. adminPrivilegeAvailable: true,
  24. }
  25. vi.doMock('@overleaf/settings', () => ({ default: ctx.Settings }))
  26. vi.doMock('@overleaf/metrics', () => ({
  27. default: (ctx.Metrics = {}),
  28. }))
  29. vi.doMock(
  30. '../../../../../app/src/Features/User/UserRegistrationHandler.js',
  31. () => ({
  32. default: (ctx.UserRegistrationHandler = {
  33. promises: {},
  34. }),
  35. })
  36. )
  37. vi.doMock('../../../../../app/src/Features/Email/EmailHandler.js', () => ({
  38. default: (ctx.EmailHandler = { promises: {} }),
  39. }))
  40. vi.doMock('../../../../../app/src/Features/User/UserGetter.js', () => ({
  41. default: (ctx.UserGetter = {
  42. promises: {},
  43. }),
  44. }))
  45. vi.doMock('../../../../../app/src/models/User.js', () => ({
  46. User: ctx.User,
  47. }))
  48. vi.doMock(
  49. '../../../../../app/src/Features/Authentication/AuthenticationController.js',
  50. () => ({
  51. default: (ctx.AuthenticationController = {}),
  52. })
  53. )
  54. vi.doMock(
  55. '../../../../../app/src/Features/Authentication/AuthenticationManager.js',
  56. () => ({
  57. default: (ctx.AuthenticationManager = {}),
  58. })
  59. )
  60. vi.doMock(
  61. '../../../../../app/src/Features/Authentication/SessionManager.js',
  62. () => ({
  63. default: (ctx.SessionManager = {
  64. getSessionUser: sinon.stub(),
  65. }),
  66. })
  67. )
  68. ctx.LaunchpadController = (await import(modulePath)).default
  69. ctx.email = 'bob@smith.com'
  70. ctx.req = {
  71. query: {},
  72. body: {},
  73. session: {},
  74. }
  75. ctx.res = new MockResponse()
  76. ctx.res.locals = {
  77. translate(key) {
  78. return key
  79. },
  80. }
  81. ctx.next = sinon.stub()
  82. })
  83. describe('launchpadPage', function () {
  84. beforeEach(function (ctx) {
  85. ctx.LaunchpadController._mocks._atLeastOneAdminExists = sinon.stub()
  86. ctx._atLeastOneAdminExists =
  87. ctx.LaunchpadController._mocks._atLeastOneAdminExists
  88. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  89. })
  90. describe('when the user is not logged in', function () {
  91. beforeEach(function (ctx) {
  92. ctx.SessionManager.getSessionUser = sinon.stub().returns(null)
  93. })
  94. describe('when there are no admins', function () {
  95. beforeEach(async function (ctx) {
  96. ctx._atLeastOneAdminExists.resolves(false)
  97. await ctx.LaunchpadController.launchpadPage(
  98. ctx.req,
  99. ctx.res,
  100. ctx.next
  101. )
  102. })
  103. it('should render the launchpad page', function (ctx) {
  104. const viewPath = path.join(
  105. import.meta.dirname,
  106. '../../../app/views/launchpad'
  107. )
  108. ctx.res.render.callCount.should.equal(1)
  109. expect(ctx.res.render).to.have.been.calledWith(viewPath, {
  110. adminUserExists: false,
  111. authMethod: 'local',
  112. })
  113. })
  114. })
  115. describe('when there is at least one admin', function () {
  116. beforeEach(async function (ctx) {
  117. ctx._atLeastOneAdminExists.resolves(true)
  118. await ctx.LaunchpadController.launchpadPage(
  119. ctx.req,
  120. ctx.res,
  121. ctx.next
  122. )
  123. })
  124. it('should redirect to login page', function (ctx) {
  125. ctx.AuthenticationController.setRedirectInSession.callCount.should.equal(
  126. 1
  127. )
  128. ctx.res.redirect.calledWith('/login').should.equal(true)
  129. })
  130. it('should not render the launchpad page', function (ctx) {
  131. ctx.res.render.callCount.should.equal(0)
  132. })
  133. })
  134. })
  135. describe('when the user is logged in', function () {
  136. beforeEach(function (ctx) {
  137. ctx.user = {
  138. _id: 'abcd',
  139. email: 'abcd@example.com',
  140. }
  141. ctx.SessionManager.getSessionUser.returns(ctx.user)
  142. ctx._atLeastOneAdminExists.resolves(true)
  143. })
  144. describe('when the user is an admin', function () {
  145. beforeEach(async function (ctx) {
  146. ctx.UserGetter.promises.getUser = sinon
  147. .stub()
  148. .resolves({ isAdmin: true })
  149. await ctx.LaunchpadController.launchpadPage(
  150. ctx.req,
  151. ctx.res,
  152. ctx.next
  153. )
  154. })
  155. it('should render the launchpad page', function (ctx) {
  156. const viewPath = path.join(
  157. import.meta.dirname,
  158. '../../../app/views/launchpad'
  159. )
  160. ctx.res.render.callCount.should.equal(1)
  161. expect(ctx.res.render).to.have.been.calledWith(viewPath, {
  162. wsUrl: undefined,
  163. adminUserExists: true,
  164. authMethod: 'local',
  165. })
  166. })
  167. })
  168. describe('when the user is not an admin', function () {
  169. beforeEach(async function (ctx) {
  170. ctx.UserGetter.promises.getUser = sinon
  171. .stub()
  172. .resolves({ isAdmin: false })
  173. await ctx.LaunchpadController.launchpadPage(
  174. ctx.req,
  175. ctx.res,
  176. ctx.next
  177. )
  178. })
  179. it('should redirect to restricted page', function (ctx) {
  180. ctx.res.redirect.callCount.should.equal(1)
  181. ctx.res.redirect.calledWith('/restricted').should.equal(true)
  182. })
  183. })
  184. })
  185. })
  186. describe('_atLeastOneAdminExists', function () {
  187. describe('when there are no admins', function () {
  188. beforeEach(function (ctx) {
  189. ctx.UserGetter.promises.getUser = sinon.stub().resolves(null)
  190. })
  191. it('should callback with false', async function (ctx) {
  192. const exists = await ctx.LaunchpadController._atLeastOneAdminExists()
  193. expect(exists).to.equal(false)
  194. })
  195. })
  196. describe('when there are some admins', function () {
  197. beforeEach(function (ctx) {
  198. ctx.UserGetter.promises.getUser = sinon.stub().resolves({ _id: 'abcd' })
  199. })
  200. it('should callback with true', async function (ctx) {
  201. const exists = await ctx.LaunchpadController._atLeastOneAdminExists()
  202. expect(exists).to.equal(true)
  203. })
  204. })
  205. describe('when getUser produces an error', function () {
  206. beforeEach(function (ctx) {
  207. ctx.UserGetter.promises.getUser = sinon
  208. .stub()
  209. .rejects(new Error('woops'))
  210. })
  211. it('should produce an error', async function (ctx) {
  212. await expect(ctx.LaunchpadController._atLeastOneAdminExists()).rejected
  213. })
  214. })
  215. })
  216. describe('sendTestEmail', function () {
  217. beforeEach(function (ctx) {
  218. ctx.EmailHandler.promises.sendEmail = sinon.stub().resolves()
  219. ctx.req.body.email = 'someone@example.com'
  220. })
  221. it('should produce a 200 response', async function (ctx) {
  222. await ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
  223. ctx.res.json.calledWith({ message: 'email_sent' }).should.equal(true)
  224. })
  225. it('should not call next with an error', function (ctx) {
  226. ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
  227. ctx.next.callCount.should.equal(0)
  228. })
  229. it('should have called sendEmail', async function (ctx) {
  230. await ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
  231. ctx.EmailHandler.promises.sendEmail.callCount.should.equal(1)
  232. ctx.EmailHandler.promises.sendEmail
  233. .calledWith('testEmail')
  234. .should.equal(true)
  235. })
  236. describe('when sendEmail produces an error', function () {
  237. beforeEach(function (ctx) {
  238. ctx.EmailHandler.promises.sendEmail = sinon
  239. .stub()
  240. .rejects(new Error('woops'))
  241. })
  242. it('should call next with an error', function (ctx) {
  243. return new Promise(resolve => {
  244. ctx.next = sinon.stub().callsFake(err => {
  245. expect(err).to.be.instanceof(Error)
  246. ctx.next.callCount.should.equal(1)
  247. resolve()
  248. })
  249. ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
  250. })
  251. })
  252. })
  253. describe('when no email address is supplied', function () {
  254. beforeEach(function (ctx) {
  255. ctx.req.body.email = undefined
  256. })
  257. it('should produce a 400 response', function (ctx) {
  258. ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
  259. ctx.res.status.calledWith(400).should.equal(true)
  260. ctx.res.json
  261. .calledWith({
  262. message: 'no email address supplied',
  263. })
  264. .should.equal(true)
  265. })
  266. })
  267. })
  268. describe('registerAdmin', function () {
  269. beforeEach(function (ctx) {
  270. ctx.LaunchpadController._mocks._atLeastOneAdminExists = sinon.stub()
  271. ctx._atLeastOneAdminExists =
  272. ctx.LaunchpadController._mocks._atLeastOneAdminExists
  273. })
  274. describe('when all goes well', function () {
  275. beforeEach(async function (ctx) {
  276. ctx._atLeastOneAdminExists.resolves(false)
  277. ctx.email = 'someone@example.com'
  278. ctx.password = 'a_really_bad_password'
  279. ctx.req.body.email = ctx.email
  280. ctx.req.body.password = ctx.password
  281. ctx.user = {
  282. _id: 'abcdef',
  283. email: ctx.email,
  284. }
  285. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  286. .stub()
  287. .resolves(ctx.user)
  288. ctx.User.updateOne = sinon
  289. .stub()
  290. .returns({ exec: sinon.stub().resolves() })
  291. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  292. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  293. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  294. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  295. })
  296. it('should send back a json response', function (ctx) {
  297. ctx.res.json.callCount.should.equal(1)
  298. expect(ctx.res.json).to.have.been.calledWith({ redir: '/launchpad' })
  299. })
  300. it('should have checked for existing admins', function (ctx) {
  301. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  302. })
  303. it('should have called registerNewUser', function (ctx) {
  304. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  305. 1
  306. )
  307. ctx.UserRegistrationHandler.promises.registerNewUser
  308. .calledWith({ email: ctx.email, password: ctx.password })
  309. .should.equal(true)
  310. })
  311. it('should have updated the user to make them an admin', function (ctx) {
  312. ctx.User.updateOne.callCount.should.equal(1)
  313. ctx.User.updateOne
  314. .calledWithMatch(
  315. { _id: ctx.user._id },
  316. {
  317. $set: {
  318. isAdmin: true,
  319. emails: [
  320. { email: ctx.user.email, reversedHostname: 'moc.elpmaxe' },
  321. ],
  322. },
  323. }
  324. )
  325. .should.equal(true)
  326. })
  327. })
  328. describe('when no email is supplied', function () {
  329. beforeEach(async function (ctx) {
  330. ctx._atLeastOneAdminExists.resolves(false)
  331. ctx.email = undefined
  332. ctx.password = 'a_really_bad_password'
  333. ctx.req.body.email = ctx.email
  334. ctx.req.body.password = ctx.password
  335. ctx.user = {
  336. _id: 'abcdef',
  337. email: ctx.email,
  338. }
  339. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  340. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  341. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  342. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  343. })
  344. it('should send a 400 response', function (ctx) {
  345. ctx.res.sendStatus.callCount.should.equal(1)
  346. ctx.res.sendStatus.calledWith(400).should.equal(true)
  347. })
  348. it('should not check for existing admins', function (ctx) {
  349. ctx._atLeastOneAdminExists.callCount.should.equal(0)
  350. })
  351. it('should not call registerNewUser', function (ctx) {
  352. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  353. 0
  354. )
  355. })
  356. })
  357. describe('when no password is supplied', function () {
  358. beforeEach(async function (ctx) {
  359. ctx._atLeastOneAdminExists.resolves(false)
  360. ctx.email = 'someone@example.com'
  361. ctx.password = undefined
  362. ctx.req.body.email = ctx.email
  363. ctx.req.body.password = ctx.password
  364. ctx.user = {
  365. _id: 'abcdef',
  366. email: ctx.email,
  367. }
  368. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  369. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  370. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  371. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  372. })
  373. it('should send a 400 response', function (ctx) {
  374. ctx.res.sendStatus.callCount.should.equal(1)
  375. ctx.res.sendStatus.calledWith(400).should.equal(true)
  376. })
  377. it('should not check for existing admins', function (ctx) {
  378. ctx._atLeastOneAdminExists.callCount.should.equal(0)
  379. })
  380. it('should not call registerNewUser', function (ctx) {
  381. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  382. 0
  383. )
  384. })
  385. })
  386. describe('when an invalid email is supplied', function () {
  387. beforeEach(async function (ctx) {
  388. ctx._atLeastOneAdminExists.resolves(false)
  389. ctx.email = 'someone@example.com'
  390. ctx.password = 'invalid password'
  391. ctx.req.body.email = ctx.email
  392. ctx.req.body.password = ctx.password
  393. ctx.user = {
  394. _id: 'abcdef',
  395. email: ctx.email,
  396. }
  397. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  398. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  399. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  400. ctx.AuthenticationManager.validateEmail = sinon
  401. .stub()
  402. .returns(new Error('bad email'))
  403. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  404. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  405. })
  406. it('should send a 400 response', function (ctx) {
  407. ctx.res.status.callCount.should.equal(1)
  408. ctx.res.status.calledWith(400).should.equal(true)
  409. ctx.res.json.calledWith({
  410. message: { type: 'error', text: 'bad email' },
  411. })
  412. })
  413. it('should not call registerNewUser', function (ctx) {
  414. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  415. 0
  416. )
  417. })
  418. })
  419. describe('when an invalid password is supplied', function () {
  420. beforeEach(async function (ctx) {
  421. ctx._atLeastOneAdminExists.resolves(false)
  422. ctx.email = 'someone@example.com'
  423. ctx.password = 'invalid password'
  424. ctx.req.body.email = ctx.email
  425. ctx.req.body.password = ctx.password
  426. ctx.user = {
  427. _id: 'abcdef',
  428. email: ctx.email,
  429. }
  430. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  431. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  432. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  433. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  434. ctx.AuthenticationManager.validatePassword = sinon
  435. .stub()
  436. .returns(new Error('bad password'))
  437. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  438. })
  439. it('should send a 400 response', function (ctx) {
  440. ctx.res.status.callCount.should.equal(1)
  441. ctx.res.status.calledWith(400).should.equal(true)
  442. ctx.res.json.calledWith({
  443. message: { type: 'error', text: 'bad password' },
  444. })
  445. })
  446. it('should not call registerNewUser', function (ctx) {
  447. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  448. 0
  449. )
  450. })
  451. })
  452. describe('when there are already existing admins', function () {
  453. beforeEach(async function (ctx) {
  454. ctx._atLeastOneAdminExists.resolves(true)
  455. ctx.email = 'someone@example.com'
  456. ctx.password = 'a_really_bad_password'
  457. ctx.req.body.email = ctx.email
  458. ctx.req.body.password = ctx.password
  459. ctx.user = {
  460. _id: 'abcdef',
  461. email: ctx.email,
  462. }
  463. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  464. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  465. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  466. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  467. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  468. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  469. })
  470. it('should send a 403 response', function (ctx) {
  471. ctx.res.status.callCount.should.equal(1)
  472. ctx.res.status.calledWith(403).should.equal(true)
  473. })
  474. it('should not call registerNewUser', function (ctx) {
  475. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  476. 0
  477. )
  478. })
  479. })
  480. describe('when checking admins produces an error', function () {
  481. beforeEach(async function (ctx) {
  482. ctx._atLeastOneAdminExists.rejects(new Error('woops'))
  483. ctx.email = 'someone@example.com'
  484. ctx.password = 'a_really_bad_password'
  485. ctx.req.body.email = ctx.email
  486. ctx.req.body.password = ctx.password
  487. ctx.user = {
  488. _id: 'abcdef',
  489. email: ctx.email,
  490. }
  491. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  492. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  493. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  494. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  495. })
  496. it('should call next with an error', function (ctx) {
  497. ctx.next.callCount.should.equal(1)
  498. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  499. })
  500. it('should have checked for existing admins', function (ctx) {
  501. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  502. })
  503. it('should not call registerNewUser', function (ctx) {
  504. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  505. 0
  506. )
  507. })
  508. })
  509. describe('when registerNewUser produces an error', function () {
  510. beforeEach(async function (ctx) {
  511. ctx._atLeastOneAdminExists.resolves(false)
  512. ctx.email = 'someone@example.com'
  513. ctx.password = 'a_really_bad_password'
  514. ctx.req.body.email = ctx.email
  515. ctx.req.body.password = ctx.password
  516. ctx.user = {
  517. _id: 'abcdef',
  518. email: ctx.email,
  519. }
  520. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  521. .stub()
  522. .rejects(new Error('woops'))
  523. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  524. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  525. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  526. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  527. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  528. })
  529. it('should call next with an error', function (ctx) {
  530. ctx.next.callCount.should.equal(1)
  531. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  532. })
  533. it('should have checked for existing admins', function (ctx) {
  534. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  535. })
  536. it('should have called registerNewUser', function (ctx) {
  537. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  538. 1
  539. )
  540. ctx.UserRegistrationHandler.promises.registerNewUser
  541. .calledWith({ email: ctx.email, password: ctx.password })
  542. .should.equal(true)
  543. })
  544. it('should not call update', function (ctx) {
  545. ctx.User.updateOne.callCount.should.equal(0)
  546. })
  547. })
  548. describe('when user update produces an error', function () {
  549. beforeEach(async function (ctx) {
  550. ctx._atLeastOneAdminExists.resolves(false)
  551. ctx.email = 'someone@example.com'
  552. ctx.password = 'a_really_bad_password'
  553. ctx.req.body.email = ctx.email
  554. ctx.req.body.password = ctx.password
  555. ctx.user = {
  556. _id: 'abcdef',
  557. email: ctx.email,
  558. }
  559. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  560. .stub()
  561. .resolves(ctx.user)
  562. ctx.User.updateOne = sinon.stub().returns({
  563. exec: sinon.stub().rejects(new Error('woops')),
  564. })
  565. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  566. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  567. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  568. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  569. })
  570. it('should call next with an error', function (ctx) {
  571. ctx.next.callCount.should.equal(1)
  572. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  573. })
  574. it('should have checked for existing admins', function (ctx) {
  575. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  576. })
  577. it('should have called registerNewUser', function (ctx) {
  578. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  579. 1
  580. )
  581. ctx.UserRegistrationHandler.promises.registerNewUser
  582. .calledWith({ email: ctx.email, password: ctx.password })
  583. .should.equal(true)
  584. })
  585. })
  586. describe('when overleaf', function () {
  587. beforeEach(async function (ctx) {
  588. ctx.Settings.overleaf = { one: 1 }
  589. ctx._atLeastOneAdminExists.resolves(false)
  590. ctx.email = 'someone@example.com'
  591. ctx.password = 'a_really_bad_password'
  592. ctx.req.body.email = ctx.email
  593. ctx.req.body.password = ctx.password
  594. ctx.user = {
  595. _id: 'abcdef',
  596. email: ctx.email,
  597. }
  598. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  599. .stub()
  600. .resolves(ctx.user)
  601. ctx.User.updateOne = sinon
  602. .stub()
  603. .returns({ exec: sinon.stub().resolves() })
  604. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  605. ctx.AuthenticationManager.validateEmail = sinon.stub().returns(null)
  606. ctx.AuthenticationManager.validatePassword = sinon.stub().returns(null)
  607. ctx.UserGetter.promises.getUser = sinon.stub().resolves({ _id: '1234' })
  608. await ctx.LaunchpadController.registerAdmin(ctx.req, ctx.res, ctx.next)
  609. })
  610. it('should send back a json response', function (ctx) {
  611. ctx.res.json.callCount.should.equal(1)
  612. expect(ctx.res.json).to.have.been.calledWith({ redir: '/launchpad' })
  613. })
  614. it('should have checked for existing admins', function (ctx) {
  615. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  616. })
  617. it('should have called registerNewUser', function (ctx) {
  618. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  619. 1
  620. )
  621. ctx.UserRegistrationHandler.promises.registerNewUser
  622. .calledWith({ email: ctx.email, password: ctx.password })
  623. .should.equal(true)
  624. })
  625. it('should have updated the user to make them an admin', function (ctx) {
  626. ctx.User.updateOne
  627. .calledWith(
  628. { _id: ctx.user._id },
  629. {
  630. $set: {
  631. isAdmin: true,
  632. emails: [
  633. { email: ctx.user.email, reversedHostname: 'moc.elpmaxe' },
  634. ],
  635. },
  636. }
  637. )
  638. .should.equal(true)
  639. })
  640. })
  641. })
  642. describe('registerExternalAuthAdmin', function () {
  643. beforeEach(function (ctx) {
  644. ctx.Settings.ldap = { one: 1 }
  645. ctx.LaunchpadController._mocks._atLeastOneAdminExists = sinon.stub()
  646. ctx._atLeastOneAdminExists =
  647. ctx.LaunchpadController._mocks._atLeastOneAdminExists
  648. })
  649. describe('when all goes well', function () {
  650. beforeEach(async function (ctx) {
  651. ctx._atLeastOneAdminExists.resolves(false)
  652. ctx.email = 'someone@example.com'
  653. ctx.req.body.email = ctx.email
  654. ctx.user = {
  655. _id: 'abcdef',
  656. email: ctx.email,
  657. }
  658. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  659. .stub()
  660. .resolves(ctx.user)
  661. ctx.User.updateOne = sinon
  662. .stub()
  663. .returns({ exec: sinon.stub().resolves() })
  664. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  665. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  666. ctx.req,
  667. ctx.res,
  668. ctx.next
  669. )
  670. })
  671. it('should send back a json response', function (ctx) {
  672. ctx.res.json.callCount.should.equal(1)
  673. expect(ctx.res.json.lastCall.args[0].email).to.equal(ctx.email)
  674. })
  675. it('should have checked for existing admins', function (ctx) {
  676. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  677. })
  678. it('should have called registerNewUser', function (ctx) {
  679. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  680. 1
  681. )
  682. ctx.UserRegistrationHandler.promises.registerNewUser
  683. .calledWith({
  684. email: ctx.email,
  685. password: 'password_here',
  686. first_name: ctx.email,
  687. last_name: '',
  688. })
  689. .should.equal(true)
  690. })
  691. it('should have updated the user to make them an admin', function (ctx) {
  692. ctx.User.updateOne.callCount.should.equal(1)
  693. ctx.User.updateOne
  694. .calledWith(
  695. { _id: ctx.user._id },
  696. {
  697. $set: {
  698. isAdmin: true,
  699. emails: [
  700. { email: ctx.user.email, reversedHostname: 'moc.elpmaxe' },
  701. ],
  702. },
  703. }
  704. )
  705. .should.equal(true)
  706. })
  707. it('should have set a redirect in session', function (ctx) {
  708. ctx.AuthenticationController.setRedirectInSession.callCount.should.equal(
  709. 1
  710. )
  711. ctx.AuthenticationController.setRedirectInSession
  712. .calledWith(ctx.req, '/launchpad')
  713. .should.equal(true)
  714. })
  715. })
  716. describe('when the authMethod is invalid', function () {
  717. beforeEach(async function (ctx) {
  718. ctx._atLeastOneAdminExists.resolves(false)
  719. ctx.email = undefined
  720. ctx.req.body.email = ctx.email
  721. ctx.user = {
  722. _id: 'abcdef',
  723. email: ctx.email,
  724. }
  725. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  726. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  727. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  728. await ctx.LaunchpadController.registerExternalAuthAdmin(
  729. 'NOTAVALIDAUTHMETHOD'
  730. )(ctx.req, ctx.res, ctx.next)
  731. })
  732. it('should send a 403 response', function (ctx) {
  733. ctx.res.sendStatus.callCount.should.equal(1)
  734. ctx.res.sendStatus.calledWith(403).should.equal(true)
  735. })
  736. it('should not check for existing admins', function (ctx) {
  737. ctx._atLeastOneAdminExists.callCount.should.equal(0)
  738. })
  739. it('should not call registerNewUser', function (ctx) {
  740. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  741. 0
  742. )
  743. })
  744. })
  745. describe('when no email is supplied', function () {
  746. beforeEach(async function (ctx) {
  747. ctx._atLeastOneAdminExists.resolves(false)
  748. ctx.email = undefined
  749. ctx.req.body.email = ctx.email
  750. ctx.user = {
  751. _id: 'abcdef',
  752. email: ctx.email,
  753. }
  754. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  755. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  756. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  757. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  758. ctx.req,
  759. ctx.res,
  760. ctx.next
  761. )
  762. })
  763. it('should send a 400 response', function (ctx) {
  764. ctx.res.sendStatus.callCount.should.equal(1)
  765. ctx.res.sendStatus.calledWith(400).should.equal(true)
  766. })
  767. it('should not check for existing admins', function (ctx) {
  768. ctx._atLeastOneAdminExists.callCount.should.equal(0)
  769. })
  770. it('should not call registerNewUser', function (ctx) {
  771. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  772. 0
  773. )
  774. })
  775. })
  776. describe('when there are already existing admins', function () {
  777. beforeEach(async function (ctx) {
  778. ctx._atLeastOneAdminExists.resolves(true)
  779. ctx.email = 'someone@example.com'
  780. ctx.req.body.email = ctx.email
  781. ctx.user = {
  782. _id: 'abcdef',
  783. email: ctx.email,
  784. }
  785. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  786. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  787. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  788. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  789. ctx.req,
  790. ctx.res,
  791. ctx.next
  792. )
  793. })
  794. it('should send a 403 response', function (ctx) {
  795. ctx.res.sendStatus.callCount.should.equal(1)
  796. ctx.res.sendStatus.calledWith(403).should.equal(true)
  797. })
  798. it('should not call registerNewUser', function (ctx) {
  799. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  800. 0
  801. )
  802. })
  803. })
  804. describe('when checking admins produces an error', function () {
  805. beforeEach(async function (ctx) {
  806. ctx._atLeastOneAdminExists.rejects(new Error('woops'))
  807. ctx.email = 'someone@example.com'
  808. ctx.req.body.email = ctx.email
  809. ctx.user = {
  810. _id: 'abcdef',
  811. email: ctx.email,
  812. }
  813. ctx.UserRegistrationHandler.promises.registerNewUser = sinon.stub()
  814. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  815. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  816. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  817. ctx.req,
  818. ctx.res,
  819. ctx.next
  820. )
  821. })
  822. it('should call next with an error', function (ctx) {
  823. ctx.next.callCount.should.equal(1)
  824. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  825. })
  826. it('should have checked for existing admins', function (ctx) {
  827. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  828. })
  829. it('should not call registerNewUser', function (ctx) {
  830. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  831. 0
  832. )
  833. })
  834. })
  835. describe('when registerNewUser produces an error', function () {
  836. beforeEach(async function (ctx) {
  837. ctx._atLeastOneAdminExists.resolves(false)
  838. ctx.email = 'someone@example.com'
  839. ctx.req.body.email = ctx.email
  840. ctx.user = {
  841. _id: 'abcdef',
  842. email: ctx.email,
  843. }
  844. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  845. .stub()
  846. .rejects(new Error('woops'))
  847. ctx.User.updateOne = sinon.stub().returns({ exec: sinon.stub() })
  848. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  849. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  850. ctx.req,
  851. ctx.res,
  852. ctx.next
  853. )
  854. })
  855. it('should call next with an error', function (ctx) {
  856. ctx.next.callCount.should.equal(1)
  857. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  858. })
  859. it('should have checked for existing admins', function (ctx) {
  860. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  861. })
  862. it('should have called registerNewUser', function (ctx) {
  863. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  864. 1
  865. )
  866. ctx.UserRegistrationHandler.promises.registerNewUser
  867. .calledWith({
  868. email: ctx.email,
  869. password: 'password_here',
  870. first_name: ctx.email,
  871. last_name: '',
  872. })
  873. .should.equal(true)
  874. })
  875. it('should not call update', function (ctx) {
  876. ctx.User.updateOne.callCount.should.equal(0)
  877. })
  878. })
  879. describe('when user update produces an error', function () {
  880. beforeEach(async function (ctx) {
  881. ctx._atLeastOneAdminExists.resolves(false)
  882. ctx.email = 'someone@example.com'
  883. ctx.req.body.email = ctx.email
  884. ctx.user = {
  885. _id: 'abcdef',
  886. email: ctx.email,
  887. }
  888. ctx.UserRegistrationHandler.promises.registerNewUser = sinon
  889. .stub()
  890. .resolves(ctx.user)
  891. ctx.User.updateOne = sinon.stub().returns({
  892. exec: sinon.stub().rejects(new Error('woops')),
  893. })
  894. ctx.AuthenticationController.setRedirectInSession = sinon.stub()
  895. await ctx.LaunchpadController.registerExternalAuthAdmin('ldap')(
  896. ctx.req,
  897. ctx.res,
  898. ctx.next
  899. )
  900. })
  901. it('should call next with an error', function (ctx) {
  902. ctx.next.callCount.should.equal(1)
  903. expect(ctx.next.lastCall.args[0]).to.be.instanceof(Error)
  904. })
  905. it('should have checked for existing admins', function (ctx) {
  906. ctx._atLeastOneAdminExists.callCount.should.equal(1)
  907. })
  908. it('should have called registerNewUser', function (ctx) {
  909. ctx.UserRegistrationHandler.promises.registerNewUser.callCount.should.equal(
  910. 1
  911. )
  912. ctx.UserRegistrationHandler.promises.registerNewUser
  913. .calledWith({
  914. email: ctx.email,
  915. password: 'password_here',
  916. first_name: ctx.email,
  917. last_name: '',
  918. })
  919. .should.equal(true)
  920. })
  921. })
  922. })
  923. })