LaunchpadControllerTests.mjs 35 KB

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