LaunchpadController.test.mjs 35 KB

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