diff --git a/frontend/src/component/maintenance/Maintenance.tsx b/frontend/src/component/maintenance/Maintenance.tsx index 2fbc0337aa..862daa96a5 100644 --- a/frontend/src/component/maintenance/Maintenance.tsx +++ b/frontend/src/component/maintenance/Maintenance.tsx @@ -25,8 +25,9 @@ const Maintenance = () => { Maintenance Mode!

- Any changes you make during maintenance mode will not be saved. - We apologize for any inconvenience this may cause. + During maintenance mode, any changes made will not be saved and + you may receive an error. We apologize for any inconvenience + this may cause.

); diff --git a/frontend/src/hooks/api/getters/useAuth/useAuthPermissions.ts b/frontend/src/hooks/api/getters/useAuth/useAuthPermissions.ts index 084a113034..47b4849b16 100644 --- a/frontend/src/hooks/api/getters/useAuth/useAuthPermissions.ts +++ b/frontend/src/hooks/api/getters/useAuth/useAuthPermissions.ts @@ -15,9 +15,14 @@ const getPermissions = ( uiConfig: IUiConfig ): IPermission[] | undefined => { let permissions = - auth.data && 'permissions' in auth.data && !uiConfig?.flags?.maintenance + auth.data && 'permissions' in auth.data ? auth.data.permissions : undefined; + if (permissions && uiConfig?.flags?.maintenance) { + permissions = permissions.filter( + permission => permission.permission === 'ADMIN' + ); + } return permissions; }; diff --git a/src/lib/app.ts b/src/lib/app.ts index 0c39f246f9..aab8048495 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -25,6 +25,7 @@ import { findPublicFolder } from './util/findPublicFolder'; import { conditionalMiddleware } from './middleware/conditional-middleware'; import patMiddleware from './middleware/pat-middleware'; import { Knex } from 'knex'; +import maintenanceMiddleware from './middleware/maintenance-middleware'; export default async function getApp( config: IUnleashConfig, @@ -138,6 +139,8 @@ export default async function getApp( rbacMiddleware(config, stores, services.accessService), ); + app.use('/api/admin', maintenanceMiddleware(config)); + if (typeof config.preRouterHook === 'function') { config.preRouterHook(app, config, services, stores, db); } diff --git a/src/lib/middleware/maintenance-middleware.ts b/src/lib/middleware/maintenance-middleware.ts new file mode 100644 index 0000000000..595aee9bbb --- /dev/null +++ b/src/lib/middleware/maintenance-middleware.ts @@ -0,0 +1,21 @@ +import { IUnleashConfig } from '../types'; +import { Request } from 'express'; + +const maintenanceMiddleware = ({ + getLogger, + flagResolver, +}: Pick): any => { + const logger = getLogger('/middleware/maintenance-middleware.ts'); + logger.debug('Enabling Maintenance middleware'); + + return async (req: Request, res, next) => { + const writeMethod = ['POST', 'PUT', 'DELETE'].includes(req.method); + if (writeMethod && flagResolver.isEnabled('maintenance')) { + res.status(503).send({}); + } else { + next(); + } + }; +}; + +export default maintenanceMiddleware; diff --git a/src/lib/routes/admin-api/public-signup.test.ts b/src/lib/routes/admin-api/public-signup.test.ts index 945f36711e..8462330180 100644 --- a/src/lib/routes/admin-api/public-signup.test.ts +++ b/src/lib/routes/admin-api/public-signup.test.ts @@ -14,11 +14,6 @@ describe('Public Signup API', () => { preRouterHook: perms.hook, }); - config.flagResolver = { - isEnabled: jest.fn().mockResolvedValue(true), - getAll: jest.fn(), - }; - stores.accessStore = { ...stores.accessStore, addUserToRole: jest.fn(), diff --git a/src/lib/routes/public-invite.test.ts b/src/lib/routes/public-invite.test.ts index 1e4412394f..e7015194e6 100644 --- a/src/lib/routes/public-invite.test.ts +++ b/src/lib/routes/public-invite.test.ts @@ -14,11 +14,6 @@ describe('Public Signup API', () => { preRouterHook: perms.hook, }); - config.flagResolver = { - isEnabled: jest.fn().mockResolvedValue(true), - getAll: jest.fn(), - }; - stores.accessStore = { ...stores.accessStore, addUserToRole: jest.fn(), diff --git a/src/test/e2e/api/admin/feature.e2e.test.ts b/src/test/e2e/api/admin/feature.e2e.test.ts index c31e73fd3f..0d2a62e1b1 100644 --- a/src/test/e2e/api/admin/feature.e2e.test.ts +++ b/src/test/e2e/api/admin/feature.e2e.test.ts @@ -837,3 +837,21 @@ test('should have access to the get all features endpoint even if api is disable .get('/api/admin/features') .expect(200); }); + +test('should not allow creation of feature toggle in maintenance mode', async () => { + const appWithMaintenanceMode = await setupAppWithCustomConfig(db.stores, { + experimental: { + flags: { + maintenance: true, + }, + }, + }); + + return appWithMaintenanceMode.request + .post('/api/admin/features') + .send({ + name: 'maintenance-feature', + }) + .set('Content-Type', 'application/json') + .expect(503); +}); diff --git a/src/test/e2e/api/admin/public-signup-token.e2e.test.ts b/src/test/e2e/api/admin/public-signup-token.e2e.test.ts index b0b274301e..27ce35c89e 100644 --- a/src/test/e2e/api/admin/public-signup-token.e2e.test.ts +++ b/src/test/e2e/api/admin/public-signup-token.e2e.test.ts @@ -7,15 +7,6 @@ import { PublicSignupTokenCreateSchema } from '../../../../lib/openapi/spec/publ let stores; let db; -jest.mock('../../../../lib/util/flag-resolver', () => { - return jest.fn().mockImplementation(() => { - return { - getAll: jest.fn(), - isEnabled: jest.fn().mockResolvedValue(true), - }; - }); -}); - beforeEach(async () => { db = await dbInit('test', getLogger); stores = db.stores;