LaunchpadController.test.mjs 34 KB

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