From 1c8a95ac3e6246f0e27ce7153256430200cf0eb4 Mon Sep 17 00:00:00 2001 From: Guy Harwood Date: Tue, 21 Mar 2023 13:55:25 +0000 Subject: [PATCH] Feature/58072 mtc closed guard (#2465) * basic working model * hook into dfe signin process * tests and implementation --------- Co-authored-by: Mohsen Qureshi --- admin/app.js | 24 +++++++++++++++++++ admin/error-types/system-unavailable-error.js | 17 +++++++++++++ admin/services/dfe-signin.service.js | 6 +++++ .../service/dfe-signin.service.spec.js | 9 +++++++ 4 files changed, 56 insertions(+) create mode 100644 admin/error-types/system-unavailable-error.js diff --git a/admin/app.js b/admin/app.js index b80e560f70..76a403c009 100644 --- a/admin/app.js +++ b/admin/app.js @@ -42,6 +42,7 @@ const authModes = require('./lib/consts/auth-modes') const dfeSignInStrategy = require('./authentication/dfe-signin-strategy') const redisCacheService = require('./services/data-access/redis-cache.service') const { CheckWindowPhaseService } = require('./services/check-window-phase/check-window-phase.service') +const checkWindowPhaseConsts = require('./lib/consts/check-window-phase') const logger = require('./services/log.service').getLogger() const sqlService = require('./services/data-access/sql.service') @@ -124,6 +125,7 @@ const results = require('./routes/results') const pupilStatus = require('./routes/pupil-status') const websiteOffline = require('./routes/website-offline') const techSupport = require('./routes/tech-support') +const roles = require('./lib/consts/roles') setupBrowserSecurity(app) @@ -341,6 +343,22 @@ if (WEBSITE_OFFLINE) { app.use('/tech-support', techSupport) } +app.use(async function (req, res, next) { + try { + if (req.isAuthenticated() === false) return next() + if (!req.user) { + return next() + } + // if user is a teacher and system is unavailable, short circuit to the unavailable page + if (global.checkWindowPhase === checkWindowPhaseConsts.unavailable && req.user.role === roles.teacher) { + res.locals.pageTitle = 'The service is currently closed' + return res.render('availability/admin-window-unavailable', {}) + } + } catch (error) { + next(error) + } +}) + // catch 404 and forward to error handler app.use(function (req, res, next) { const err = new Error('Not Found') @@ -358,6 +376,12 @@ app.use(function (err, req, res, next) { // catch CSRF errors and redirect to the previous location if (err.code === 'EBADCSRFTOKEN') return res.redirect('back') + // catch system unavailable errors and redirect to the relevant page + if (err.code === 'SYSTEM_UNAVAILABLE') { + res.locals.pageTitle = 'The service is currently closed' + return res.render('availability/admin-window-unavailable', {}) + } + // render the error page res.locals.message = 'An error occurred' res.locals.userMessage = err.userMessage diff --git a/admin/error-types/system-unavailable-error.js b/admin/error-types/system-unavailable-error.js new file mode 100644 index 0000000000..074ba7ce6c --- /dev/null +++ b/admin/error-types/system-unavailable-error.js @@ -0,0 +1,17 @@ +'use strict' + +const { mtcError } = require('./mtc-error') + +class SystemUnavailableError extends mtcError { + constructor () { + const message = 'The system is unavailable at this time' + super(message) + this.name = 'SystemUnavailableError' + this.userMessage = message + this.code = 'SYSTEM_UNAVAILABLE' + } +} + +module.exports = { + SystemUnavailableError +} diff --git a/admin/services/dfe-signin.service.js b/admin/services/dfe-signin.service.js index 94a8c0b39b..0aab78eb33 100644 --- a/admin/services/dfe-signin.service.js +++ b/admin/services/dfe-signin.service.js @@ -8,6 +8,8 @@ const roles = require('../lib/consts/roles') const dfeSigninDataService = require('./data-access/dfe-signin.data.service') const adminLogonEventDataService = require('./data-access/admin-logon-event.data.service') const { DsiSchoolNotFoundError } = require('../error-types/DsiSchoolNotFoundError') +const checkWindowPhaseConsts = require('../lib/consts/check-window-phase') +const { SystemUnavailableError } = require('../error-types/system-unavailable-error') const service = { /** @@ -34,6 +36,10 @@ const service = { let schoolRecord // lookup school if in teacher or headteacher role if (dfeUser.role === roles.teacher) { + // short circuit out of this if the check window is closed + if (global.checkWindowPhase === checkWindowPhaseConsts.unavailable) { + throw new SystemUnavailableError() + } if (dfeUser.organisation && dfeUser.organisation.urn) { schoolRecord = await schoolDataService.sqlFindOneByUrn(dfeUser.organisation.urn) if (!schoolRecord) { diff --git a/admin/spec/back-end/service/dfe-signin.service.spec.js b/admin/spec/back-end/service/dfe-signin.service.spec.js index d3206bc13b..1130a86c61 100644 --- a/admin/spec/back-end/service/dfe-signin.service.spec.js +++ b/admin/spec/back-end/service/dfe-signin.service.spec.js @@ -6,6 +6,7 @@ let sut, schoolDataService, userDataService, roleService, dfeDataService, adminL const token = { id_token: 'the-token' } const config = require('../../../config') const roles = require('../../../lib/consts/roles') +const checkWindowPhaseConsts = require('../../../lib/consts/check-window-phase') describe('dfe-signin.service', () => { beforeEach(() => { @@ -15,6 +16,7 @@ describe('dfe-signin.service', () => { roleService = require('../../../services/role.service') dfeDataService = require('../../../services/data-access/dfe-signin.data.service') adminLogonEventDataService = require('../../../services/data-access/admin-logon-event.data.service') + global.checkWindowPhase = checkWindowPhaseConsts.officialCheck }) afterEach(() => { @@ -46,6 +48,13 @@ describe('dfe-signin.service', () => { .toThrowError('user.organisation or user.organisation.urn not found on dfeUser object') }) + test('throws system unavailable error if teacher logging on when system is not available', async () => { + jest.spyOn(dfeDataService, 'getDfeRole').mockResolvedValue('mtc_teacher') + jest.spyOn(roleService, 'findByTitle').mockResolvedValue({ id: 3, title: roles.teacher }) + global.checkWindowPhase = checkWindowPhaseConsts.unavailable + await expect(sut.initialiseUser({ organisation: { urn: 12345 } }, token)).rejects.toThrowError('The system is unavailable at this time') + }) + test('does look up school if role is teacher', async () => { jest.spyOn(dfeDataService, 'getDfeRole').mockResolvedValue('mtc_teacher') jest.spyOn(roleService, 'findByTitle').mockResolvedValue({ id: 3, title: roles.teacher })