From 1637d609ba5624befc173f106ba01316d1977fcf Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Mon, 3 Jul 2023 15:13:08 +0530 Subject: [PATCH 1/3] add middleware for securing routes according to privs --- src/common/helpers/error.js | 18 ++++++++++++++++++ src/server/helpers/auth.js | 21 +++++++++++++++++++++ src/server/routes/adminPanel.js | 8 ++++++-- src/server/routes/editor.js | 5 ++++- src/server/routes/entity/author.js | 19 +++++++++++-------- src/server/routes/entity/edition-group.js | 18 ++++++++++-------- src/server/routes/entity/edition.ts | 19 +++++++++++-------- src/server/routes/entity/publisher.js | 19 +++++++++++-------- src/server/routes/entity/series.js | 19 +++++++++++-------- src/server/routes/entity/work.js | 19 +++++++++++-------- src/server/routes/merge.ts | 12 +++++++----- src/server/routes/reviews.js | 5 ++++- src/server/routes/unifiedform.ts | 9 ++++++--- 13 files changed, 131 insertions(+), 60 deletions(-) diff --git a/src/common/helpers/error.js b/src/common/helpers/error.js index 3b03d2983e..08fc3b71f4 100644 --- a/src/common/helpers/error.js +++ b/src/common/helpers/error.js @@ -126,6 +126,24 @@ export class PermissionDeniedError extends PathError { } } +export class NotAuthorizedError extends PathError { + static get defaultMessage() { + return 'You do not have permission to access this route'; + } + + static get status() { + return status.FORBIDDEN; + } + + static detailedMessage(req) { + return [ + `You do not have permission to access the following path: + ${req.path}`, + 'Please make sure you have the privileges to access the route!' + ]; + } +} + function _logError(err) { log.error(err); } diff --git a/src/server/helpers/auth.js b/src/server/helpers/auth.js index 412fc9184b..44432d29f6 100644 --- a/src/server/helpers/auth.js +++ b/src/server/helpers/auth.js @@ -172,3 +172,24 @@ export function isAuthenticatedForCollectionView(req, res, next) { 'You do not have permission to view this collection', req ); } + +export function isAuthorized(flag) { + return async (req, res, next) => { + try { + const {Editor} = req.app.locals.orm; + const latestPrivs = await Editor.query({where: {id: req.user.id}}) + .fetch({require: true}) + .then(editor => editor.get('privs')); + /* eslint-disable no-bitwise */ + if (latestPrivs & flag) { + return next(); + } + throw new error.NotAuthorizedError( + 'You do not have the privilege to access this route', req + ); + } + catch (err) { + return next(err); + } + }; +} diff --git a/src/server/routes/adminPanel.js b/src/server/routes/adminPanel.js index 947e98abe1..fbf5671b55 100644 --- a/src/server/routes/adminPanel.js +++ b/src/server/routes/adminPanel.js @@ -16,6 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +import * as auth from '../helpers/auth'; import * as commonUtils from '../../common/helpers/utils'; import * as handler from '../helpers/handler'; import * as propHelpers from '../../client/helpers/props'; @@ -25,19 +26,22 @@ import {snakeCase as _snakeCase, isNil} from 'lodash'; import {escapeProps, generateProps} from '../helpers/props'; import AdminPanelSearchPage from '../../client/components/pages/admin-panel-search'; import Layout from '../../client/containers/layout'; +import {PrivilegeTypes} from '../../common/helpers/privileges-utils'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import express from 'express'; import target from '../templates/target'; +const ADMIN = PrivilegeTypes.ADMIN_PRIV.value; + const router = express.Router(); /** * Generates React markup for the search page that is rendered by the user's * browser. */ -router.get('/', async (req, res, next) => { +router.get('/', auth.isAuthenticated, auth.isAuthorized(ADMIN), async (req, res, next) => { const {orm} = req.app.locals; const query = req.query.q ?? ''; const type = 'editor'; @@ -90,7 +94,7 @@ router.get('/', async (req, res, next) => { } }); -router.get('/search', (req, res) => { +router.get('/search', auth.isAuthenticated, auth.isAuthorized(ADMIN), (req, res) => { const {orm} = req.app.locals; const query = req.query.q; const type = 'editor'; diff --git a/src/server/routes/editor.js b/src/server/routes/editor.js index 55bf944964..2cadb34e95 100644 --- a/src/server/routes/editor.js +++ b/src/server/routes/editor.js @@ -31,6 +31,7 @@ import CollectionsPage from '../../client/components/pages/collections'; import EditorContainer from '../../client/containers/editor'; import EditorRevisionPage from '../../client/components/pages/editor-revision'; import Layout from '../../client/containers/layout'; +import {PrivilegeTypes} from '../../common/helpers/privileges-utils'; import ProfileForm from '../../client/components/forms/profile'; import ProfileTab from '../../client/components/pages/parts/editor-profile'; import React from 'react'; @@ -42,6 +43,8 @@ import {getOrderedRevisionForEditorPage} from '../helpers/revisions'; import target from '../templates/target'; +const ADMIN = PrivilegeTypes.ADMIN_PRIV.value; + const router = express.Router(); router.get('/edit', auth.isAuthenticated, async (req, res, next) => { @@ -174,7 +177,7 @@ router.post('/edit/handler', auth.isAuthenticatedForHandler, (req, res) => { handler.sendPromiseResult(res, runAsync(), search.indexEntity); }); -router.post('/privs/edit/handler', auth.isAuthenticatedForHandler, async (req, res, next) => { +router.post('/privs/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ADMIN), async (req, res, next) => { const {Editor} = req.app.locals.orm; try { const editor = await Editor diff --git a/src/server/routes/entity/author.js b/src/server/routes/entity/author.js index 0ee99b3a1c..02025c87e7 100644 --- a/src/server/routes/entity/author.js +++ b/src/server/routes/entity/author.js @@ -30,6 +30,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; import express from 'express'; @@ -86,6 +87,8 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'author', transformNewForm, additionalAuthorProps, true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + /** **************************** *********** Routes ************ *******************************/ @@ -94,7 +97,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadGenders, middleware.loadLanguages, middleware.loadAuthorTypes, middleware.loadRelationshipTypes, async (req, res) => { @@ -134,7 +137,7 @@ router.get( ); router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadGenders, middleware.loadLanguages, middleware.loadAuthorTypes, middleware.loadRelationshipTypes, async (req, res) => { @@ -173,7 +176,7 @@ router.post( } ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); /* If the route specifies a BBID, make sure it does not redirect to another bbid then load the corresponding entity */ @@ -203,7 +206,7 @@ router.get('/:bbid', middleware.loadEntityRelationships, middleware.loadWikipedi entityRoutes.displayEntity(req, res); }); -router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -212,7 +215,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req, res) => { const {orm} = req.app.locals; const {AuthorHeader, AuthorRevision} = orm; @@ -320,7 +323,7 @@ export function authorToFormState(author) { router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadGenders, middleware.loadLanguages, middleware.loadAuthorTypes, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, @@ -341,10 +344,10 @@ router.get( ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); export default router; diff --git a/src/server/routes/entity/edition-group.js b/src/server/routes/entity/edition-group.js index 1bcd8e8894..2462444e2d 100644 --- a/src/server/routes/entity/edition-group.js +++ b/src/server/routes/entity/edition-group.js @@ -30,6 +30,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; import express from 'express'; @@ -82,6 +83,7 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'editionGroup', transformNewForm, 'typeId', true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; /** **************************** *********** Routes ************ @@ -91,7 +93,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadEditionGroupTypes, middleware.loadRelationshipTypes, async (req, res) => { @@ -130,7 +132,7 @@ router.get( router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadEditionGroupTypes, middleware.loadRelationshipTypes, async (req, res) => { const entity = await utils.parseInitialState(req, 'editionGroup'); @@ -155,7 +157,7 @@ router.post( } ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); /* If the route specifies a BBID, make sure it does not redirect to another bbid then load the corresponding entity */ @@ -194,7 +196,7 @@ router.get('/:bbid', middleware.loadEntityRelationships, middleware.loadWikipedi entityRoutes.displayEntity(req, res); }); -router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -203,7 +205,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req, res) => { const {orm} = req.app.locals; const {EditionGroupHeader, EditionGroupRevision} = orm; @@ -324,7 +326,7 @@ export function editionGroupToFormState(editionGroup) { } router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadEditionGroupTypes, middleware.loadLanguages, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, (req, res) => { @@ -341,10 +343,10 @@ router.get( } ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); export default router; diff --git a/src/server/routes/entity/edition.ts b/src/server/routes/entity/edition.ts index fb645d00be..516c69bd5d 100644 --- a/src/server/routes/entity/edition.ts +++ b/src/server/routes/entity/edition.ts @@ -31,6 +31,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import {RelationshipTypes} from '../../../client/entity-editor/relationship-editor/types'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; @@ -132,6 +133,8 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'edition', transformNewForm, additionalEditionProps, true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + /** **************************** *********** Routes ************* *******************************/ @@ -140,7 +143,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadEditionStatuses, middleware.loadEditionFormats, middleware.loadLanguages, middleware.loadRelationshipTypes, (req:PassportRequest, res, next) => { @@ -247,7 +250,7 @@ router.get( ); router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadEditionStatuses, middleware.loadEditionFormats, middleware.loadLanguages, middleware.loadRelationshipTypes, async (req:PassportRequest, res, next) => { @@ -296,7 +299,7 @@ router.post( } ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); /* If the route specifies a BBID, make sure it does not redirect to another bbid then load the corresponding entity */ @@ -349,7 +352,7 @@ router.get('/:bbid/revisions/revisions', (req:PassportRequest, res, next) => { }); -router.get('/:bbid/delete', auth.isAuthenticated, (req:PassportRequest, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req:PassportRequest, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -358,7 +361,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req:PassportRequest, res, nex }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req:PassportRequest, res) => { const {orm} = req.app.locals; const {EditionHeader, EditionRevision} = orm; @@ -498,7 +501,7 @@ export function editionToFormState(edition) { } router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadEditionStatuses, middleware.loadEditionFormats, middleware.loadLanguages, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, @@ -516,10 +519,10 @@ router.get( } ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); export default router; diff --git a/src/server/routes/entity/publisher.js b/src/server/routes/entity/publisher.js index c32775676d..59ddf45639 100644 --- a/src/server/routes/entity/publisher.js +++ b/src/server/routes/entity/publisher.js @@ -30,6 +30,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; import express from 'express'; @@ -82,6 +83,8 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'publisher', transformNewForm, additionalPublisherProps, true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + /** **************************** *********** Routes ************* *******************************/ @@ -91,7 +94,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadPublisherTypes, middleware.loadRelationshipTypes, async (req, res) => { @@ -130,7 +133,7 @@ router.get( router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadPublisherTypes, middleware.loadRelationshipTypes, async (req, res) => { @@ -163,7 +166,7 @@ router.post( ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); @@ -215,7 +218,7 @@ router.get('/:bbid', middleware.loadEntityRelationships, middleware.loadWikipedi .catch(next); }); -router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -224,7 +227,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req, res) => { const {orm} = req.app.locals; const {PublisherHeader, PublisherRevision} = orm; @@ -328,7 +331,7 @@ export function publisherToFormState(publisher) { } router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadPublisherTypes, middleware.loadLanguages, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, (req, res) => { @@ -346,10 +349,10 @@ router.get( ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); export default router; diff --git a/src/server/routes/entity/series.js b/src/server/routes/entity/series.js index 4c167fd3d7..7579a911e3 100644 --- a/src/server/routes/entity/series.js +++ b/src/server/routes/entity/series.js @@ -30,6 +30,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; import express from 'express'; @@ -79,6 +80,8 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'series', transformNewForm, additionalSeriesProps, true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + /** **************************** *********** Routes ************ *******************************/ @@ -87,7 +90,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadRelationshipTypes, middleware.loadSeriesOrderingTypes, async (req, res) => { @@ -125,7 +128,7 @@ router.get( ); router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadRelationshipTypes, middleware.loadSeriesOrderingTypes, async (req, res) => { const entity = await utils.parseInitialState(req, 'series'); @@ -163,10 +166,10 @@ router.post( } ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); /* If the route specifies a BBID, make sure it does not redirect to another bbid then load the corresponding entity */ @@ -202,7 +205,7 @@ router.get('/:bbid', middleware.loadEntityRelationships, middleware.loadSeriesIt entityRoutes.displayEntity(req, res); }); -router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -211,7 +214,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req, res) => { const {orm} = req.app.locals; const {SeriesHeader, SeriesRevision} = orm; @@ -318,7 +321,7 @@ export function seriesToFormState(series) { } router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadSeriesOrderingTypes, middleware.loadLanguages, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, (req, res) => { @@ -335,7 +338,7 @@ router.get( } ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); export default router; diff --git a/src/server/routes/entity/work.js b/src/server/routes/entity/work.js index c2dbcd0e92..82638b1f76 100644 --- a/src/server/routes/entity/work.js +++ b/src/server/routes/entity/work.js @@ -31,6 +31,7 @@ import { } from '../../helpers/entityRouteUtils'; import {ConflictError} from '../../../common/helpers/error'; +import {PrivilegeTypes} from '../../../common/helpers/privileges-utils'; import {RelationshipTypes} from '../../../client/entity-editor/relationship-editor/types'; import _ from 'lodash'; import {escapeProps} from '../../helpers/props'; @@ -80,6 +81,8 @@ const mergeHandler = makeEntityCreateOrEditHandler( 'work', transformNewForm, 'typeId', true ); +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + /** **************************** *********** Routes ************* *******************************/ @@ -89,7 +92,7 @@ const router = express.Router(); // Creation router.get( - '/create', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/create', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadWorkTypes, middleware.loadRelationshipTypes, (req, res, next) => { @@ -162,7 +165,7 @@ router.get( ); router.post( - '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, middleware.loadIdentifierTypes, + '/create', entityRoutes.displayPreview, auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadWorkTypes, middleware.loadRelationshipTypes, async (req, res, next) => { @@ -196,7 +199,7 @@ router.post( } ); -router.post('/create/handler', auth.isAuthenticatedForHandler, +router.post('/create/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); @@ -227,7 +230,7 @@ router.get('/:bbid', middleware.loadEntityRelationships, middleware.loadWikipedi entityRoutes.displayEntity(req, res); }); -router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { +router.get('/:bbid/delete', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); } @@ -236,7 +239,7 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { }); router.post( - '/:bbid/delete/handler', auth.isAuthenticatedForHandler, + '/:bbid/delete/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), (req, res) => { const {orm} = req.app.locals; const {WorkHeader, WorkRevision} = orm; @@ -339,7 +342,7 @@ export function workToFormState(work) { } router.get( - '/:bbid/edit', auth.isAuthenticated, middleware.loadIdentifierTypes, + '/:bbid/edit', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadWorkTypes, middleware.loadLanguages, middleware.loadEntityRelationships, middleware.loadRelationshipTypes, (req, res) => { @@ -356,10 +359,10 @@ router.get( } ); -router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), createOrEditHandler); -router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, +router.post('/:bbid/merge/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ENTITY_EDITOR), mergeHandler); export default router; diff --git a/src/server/routes/merge.ts b/src/server/routes/merge.ts index f362ece797..beec4cf9bb 100644 --- a/src/server/routes/merge.ts +++ b/src/server/routes/merge.ts @@ -30,7 +30,7 @@ import { entityMergeMarkup, generateEntityMergeProps } from '../helpers/entityRouteUtils'; - +import {PrivilegeTypes} from '../../common/helpers/privileges-utils'; import _ from 'lodash'; import {escapeProps} from '../helpers/props'; import express from 'express'; @@ -43,6 +43,8 @@ type PassportRequest = express.Request & { session: any }; +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + const router = express.Router(); function entitiesToFormState(entities: any[]) { @@ -256,7 +258,7 @@ async function getEntityByBBID(orm, transacting, bbid) { } -router.get('/add/:bbid', auth.isAuthenticated, +router.get('/add/:bbid', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), async (req: PassportRequest, res, next) => { const {orm}: {orm?: any} = req.app.locals; let {mergeQueue} = req.session; @@ -312,7 +314,7 @@ router.get('/add/:bbid', auth.isAuthenticated, return res.redirect(req.headers.referer); }); -router.get('/remove/:bbid', auth.isAuthenticated, +router.get('/remove/:bbid', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req: PassportRequest, res) => { const {mergeQueue} = req.session; if (!mergeQueue || _.isNil(req.params.bbid)) { @@ -331,13 +333,13 @@ router.get('/remove/:bbid', auth.isAuthenticated, res.redirect(req.headers.referer); }); -router.get('/cancel', auth.isAuthenticated, +router.get('/cancel', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), (req: PassportRequest, res) => { req.session.mergeQueue = null; res.redirect(req.headers.referer); }); -router.get('/submit/:targetBBID?', auth.isAuthenticated, +router.get('/submit/:targetBBID?', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadLanguages, middleware.loadRelationshipTypes, async (req: PassportRequest, res, next) => { diff --git a/src/server/routes/reviews.js b/src/server/routes/reviews.js index 616d72af8a..cf5d539029 100644 --- a/src/server/routes/reviews.js +++ b/src/server/routes/reviews.js @@ -19,9 +19,12 @@ import * as auth from '../helpers/auth'; import * as cbHelper from '../helpers/critiquebrainz'; +import {PrivilegeTypes} from '../../common/helpers/privileges-utils'; import express from 'express'; +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + const router = express.Router(); router.get('/:entityType/:bbid/reviews', async (req, res) => { @@ -30,7 +33,7 @@ router.get('/:entityType/:bbid/reviews', async (req, res) => { res.json(reviews); }); -router.post('/:entityType/:bbid/reviews', auth.isAuthenticated, async (req, res) => { +router.post('/:entityType/:bbid/reviews', auth.isAuthenticated, auth.isAuthorized(ENTITY_EDITOR), async (req, res) => { const editorId = req.user.id; const {orm} = req.app.locals; diff --git a/src/server/routes/unifiedform.ts b/src/server/routes/unifiedform.ts index d50e195a64..7148a4694c 100644 --- a/src/server/routes/unifiedform.ts +++ b/src/server/routes/unifiedform.ts @@ -1,15 +1,18 @@ import * as middleware from '../helpers/middleware'; import {createEntitiesHandler, generateUnifiedProps, unifiedFormMarkup} from '../helpers/entityRouteUtils'; -import {isAuthenticated, isAuthenticatedForHandler} from '../helpers/auth'; +import {isAuthenticated, isAuthenticatedForHandler, isAuthorized} from '../helpers/auth'; +import {PrivilegeTypes} from '../../common/helpers/privileges-utils'; import {escapeProps} from '../helpers/props'; import express from 'express'; import target from '../templates/target'; +const ENTITY_EDITOR = PrivilegeTypes.ENTITY_EDITING_PRIV.value; + type PassportRequest = express.Request & {user: any, session: any}; const router = express.Router(); -router.get('/create', isAuthenticated, middleware.loadIdentifierTypes, +router.get('/create', isAuthenticated, isAuthorized(ENTITY_EDITOR), middleware.loadIdentifierTypes, middleware.loadEditionStatuses, middleware.loadEditionFormats, middleware.loadEditionGroupTypes, middleware.loadSeriesOrderingTypes, middleware.loadLanguages, middleware.loadWorkTypes, middleware.loadGenders, middleware.loadPublisherTypes, middleware.loadAuthorTypes, middleware.loadRelationshipTypes, (req:PassportRequest, res:express.Response) => { @@ -27,6 +30,6 @@ router.get('/create', isAuthenticated, middleware.loadIdentifierTypes, })); }); -router.post('/create/handler', isAuthenticatedForHandler, createEntitiesHandler); +router.post('/create/handler', isAuthenticatedForHandler, isAuthorized(ENTITY_EDITOR), createEntitiesHandler); export default router; From 6952c13a0483d2210c736bd4e0f8f1ad51a456a0 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Tue, 4 Jul 2023 00:35:21 +0530 Subject: [PATCH 2/3] add tests for middleware --- test/src/server/routes/editor.js | 59 +++++++++++++++++++ test/src/server/routes/entity/author.js | 44 +++++++++++++- .../src/server/routes/entity/edition-group.js | 44 +++++++++++++- test/src/server/routes/entity/edition.js | 42 +++++++++++++ test/src/server/routes/entity/publisher.js | 42 +++++++++++++ test/src/server/routes/entity/series.js | 42 +++++++++++++ test/src/server/routes/entity/work.js | 42 +++++++++++++ test/src/server/routes/merge.js | 41 ++++++++++++- test/src/server/routes/unifiedform.js | 28 ++++++++- test/test-helpers/create-entities.js | 3 +- 10 files changed, 382 insertions(+), 5 deletions(-) diff --git a/test/src/server/routes/editor.js b/test/src/server/routes/editor.js index a82733ff52..0111290a1e 100644 --- a/test/src/server/routes/editor.js +++ b/test/src/server/routes/editor.js @@ -1,6 +1,7 @@ /* eslint-disable sort-keys */ import {createEditor, truncateEntities} from '../../../test-helpers/create-entities'; +import app from '../../../../src/server/app'; import chai from 'chai'; import chaiHttp from 'chai-http'; import {getEditorActivity} from '../../../../src/server/routes/editor.js'; @@ -9,6 +10,11 @@ import orm from '../../../bookbrainz-data'; chai.use(chaiHttp); const {expect} = chai; +const {Editor} = orm; + +const targetUserId = 1; +const oldPrivs = 1; +const newPrivs = 3; describe('getEditorActivity', () => { @@ -132,3 +138,56 @@ describe('getEditorActivity', () => { } }); }); + +describe('Editor with Administrator priv', () => { + let agent; + beforeEach(async () => { + await createEditor(123456, 16); + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + afterEach(truncateEntities); + + it('should be able to edit privs of an editor', async () => { + await createEditor(targetUserId, oldPrivs); + const data = { + targetUserId, + newPrivs + }; + + const res = await agent.post('/editor/privs/edit/handler').send(data); + const latestPrivsOfTargetUser = await Editor.query({where: {id: targetUserId}}) + .fetch({require: true}) + .then(editor => editor.get('privs')); + expect(res.ok).to.be.true; + expect(res).to.have.status(200); + expect(latestPrivsOfTargetUser).to.be.equal(newPrivs); + }); +}); + +describe('Editor without Administrator priv', () => { + let agent; + beforeEach(async () => { + await createEditor(123456, 15); + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + afterEach(truncateEntities); + + it('should not be able to edit privs of an editor', async () => { + await createEditor(targetUserId, oldPrivs); + const data = { + targetUserId, + newPrivs + }; + + const res = await agent.post('/editor/privs/edit/handler').send(data); + const latestPrivsOfTargetUser = await Editor.query({where: {id: targetUserId}}) + .fetch({require: true}) + .then(editor => editor.get('privs')); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + expect(latestPrivsOfTargetUser).to.be.equal(oldPrivs); + }); +}); diff --git a/test/src/server/routes/entity/author.js b/test/src/server/routes/entity/author.js index 28f83aab17..ecd59561e3 100644 --- a/test/src/server/routes/entity/author.js +++ b/test/src/server/routes/entity/author.js @@ -8,7 +8,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Author routes', () => { +describe('Author routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent; @@ -75,4 +75,46 @@ describe('Author routes', () => { }); }); +describe('Author routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createAuthor(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open author create page', async () => { + const res = await agent + .get('/author/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing author', async () => { + const res = await agent + .get(`/author/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing author', async () => { + const res = await agent + .get(`/author/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding author', async () => { + const data = seedInitialState; + const res = await agent.post('/author/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); + diff --git a/test/src/server/routes/entity/edition-group.js b/test/src/server/routes/entity/edition-group.js index 6f5e41b3df..759ef9e757 100644 --- a/test/src/server/routes/entity/edition-group.js +++ b/test/src/server/routes/entity/edition-group.js @@ -8,7 +8,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Edition Group routes', () => { +describe('Edition Group routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent; @@ -71,3 +71,45 @@ describe('Edition Group routes', () => { expect(res).to.have.status(200); }); }); + +describe('Edition Group routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createEditionGroup(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open edition-group create page', async () => { + const res = await agent + .get('/edition-group/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing edition-group', async () => { + const res = await agent + .get(`/edition-group/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing edition-group', async () => { + const res = await agent + .get(`/edition-group/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding edition-group', async () => { + const data = seedInitialState; + const res = await agent.post('/edition-group/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); diff --git a/test/src/server/routes/entity/edition.js b/test/src/server/routes/entity/edition.js index 9bfb0ad734..270eab4faa 100644 --- a/test/src/server/routes/entity/edition.js +++ b/test/src/server/routes/entity/edition.js @@ -77,3 +77,45 @@ describe('Edition routes', () => { }); }); +describe('Edition routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createEdition(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open edition create page', async () => { + const res = await agent + .get('/edition/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing edition', async () => { + const res = await agent + .get(`/edition/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing edition', async () => { + const res = await agent + .get(`/edition/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding edition', async () => { + const data = seedInitialState; + const res = await agent.post('/edition/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); + diff --git a/test/src/server/routes/entity/publisher.js b/test/src/server/routes/entity/publisher.js index d3932270e9..87a76af9e7 100644 --- a/test/src/server/routes/entity/publisher.js +++ b/test/src/server/routes/entity/publisher.js @@ -74,3 +74,45 @@ describe('Publisher routes', () => { expect(res).to.have.status(200); }); }); + +describe('Publisher routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createPublisher(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open publisher create page', async () => { + const res = await agent + .get('/publisher/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing publisher', async () => { + const res = await agent + .get(`/publisher/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing publisher', async () => { + const res = await agent + .get(`/publisher/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding publisher', async () => { + const data = seedInitialState; + const res = await agent.post('/publisher/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); diff --git a/test/src/server/routes/entity/series.js b/test/src/server/routes/entity/series.js index de1b0db285..ecd4eef792 100644 --- a/test/src/server/routes/entity/series.js +++ b/test/src/server/routes/entity/series.js @@ -91,3 +91,45 @@ describe('Series routes', () => { expect(res).to.have.status(200); }); }); + +describe('Series routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createSeries(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open series create page', async () => { + const res = await agent + .get('/series/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing series', async () => { + const res = await agent + .get(`/series/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing series', async () => { + const res = await agent + .get(`/series/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding series', async () => { + const data = seedInitialState; + const res = await agent.post('/series/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); diff --git a/test/src/server/routes/entity/work.js b/test/src/server/routes/entity/work.js index 28c6f3dbdf..b220bbc593 100644 --- a/test/src/server/routes/entity/work.js +++ b/test/src/server/routes/entity/work.js @@ -73,3 +73,45 @@ describe('Work routes', () => { expect(res).to.have.status(200); }); }); + +describe('Work routes without entity editing priv', () => { + const aBBID = getRandomUUID(); + let agent; + before(async () => { + await createWork(aBBID); + await createEditor(123456, 0); + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw an error if trying to open work create page', async () => { + const res = await agent + .get('/work/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error trying to edit an existing work', async () => { + const res = await agent + .get(`/work/${aBBID}/edit`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an error when trying to delete an existing work', async () => { + const res = await agent + .get(`/work/${aBBID}/delete`); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw not authorized error while seeding work', async () => { + const data = seedInitialState; + const res = await agent.post('/work/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); diff --git a/test/src/server/routes/merge.js b/test/src/server/routes/merge.js index dae2b0054e..b35ab436b6 100644 --- a/test/src/server/routes/merge.js +++ b/test/src/server/routes/merge.js @@ -8,7 +8,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Merge routes', () => { +describe('Merge routes with entity editing priv', () => { describe('/add route', () => { const aBBID = getRandomUUID(); const bBBID = getRandomUUID(); @@ -172,3 +172,42 @@ describe('Merge routes', () => { }); }); }); + +describe('Merge routes without entity editing priv', () => { + describe('/add route', () => { + const aBBID = getRandomUUID(); + const bBBID = getRandomUUID(); + const nonExistingEntity = getRandomUUID(); + const inValidBBID = 'have-you-seen-the-fnords'; + let agent; + before(async () => { + await createAuthor(aBBID); + await createWork(bBBID); + await createEditor(123456, 0); + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + after(truncateEntities); + + it('should throw a 403 error if adding an invalid BBID', async () => { + const res = await agent.get(`/merge/add/${inValidBBID}`); + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw an 404 error if BBID is absent (invalid route)', async () => { + const res = await agent.get('/merge/add/'); + expect(res).to.have.status(404); + }); + it('should throw a 403 error if entity does not exist', async () => { + const res = await agent.get(`/merge/add/${nonExistingEntity}`); + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + it('should throw 403 error when trying to add an entity to the merge queue', async () => { + const res = await agent.get(`/merge/add/${aBBID}`) + .set('referer', `/author/${aBBID}`); + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); + }); +}); diff --git a/test/src/server/routes/unifiedform.js b/test/src/server/routes/unifiedform.js index 48fdf5f442..2fd16470fe 100644 --- a/test/src/server/routes/unifiedform.js +++ b/test/src/server/routes/unifiedform.js @@ -34,7 +34,7 @@ function testDefaultAlias(entity, languageId) { return areKeysEqual(expectedDefaultAlias, actualDefaultAlias); } -describe('Unified form routes', () => { +describe('Unified form routes with entity editing priv', () => { let agent; let newLanguage; let newRelationshipType; @@ -309,3 +309,29 @@ describe('Unified form routes', () => { expect(res).to.have.status(400); }); }); + + +describe('Unified form routes without entity editing priv', () => { + let agent; + before(async () => { + try { + await createEditor(123456, 0); + } + catch (error) { + // console.log(error); + } + // Log in; use agent to use logged in session + agent = await chai.request.agent(app); + await agent.get('/cb'); + }); + + after(truncateEntities); + + it('should throw an error if trying to open unified form create page', async () => { + const res = await agent + .get('/create'); + expect(res.ok).to.be.false; + expect(res).to.have.status(403); + expect(res.res.statusMessage).to.equal('You do not have the privilege to access this route'); + }); +}); diff --git a/test/test-helpers/create-entities.js b/test/test-helpers/create-entities.js index a62e1881aa..4b12c02b96 100644 --- a/test/test-helpers/create-entities.js +++ b/test/test-helpers/create-entities.js @@ -144,7 +144,7 @@ const entityAttribs = { revisionId: 1 }; -export function createEditor(editorId) { +export function createEditor(editorId, privs = 1) { return orm.bookshelf.knex.transaction(async (transacting) => { const editorType = await new EditorType(editorTypeAttribs) .save(null, {method: 'insert', transacting}); @@ -155,6 +155,7 @@ export function createEditor(editorId) { editorAttribs.genderId = gender.id; editorAttribs.typeId = editorType.id; editorAttribs.name = internet.userName(); + editorAttribs.privs = privs; editorAttribs.metabrainzUserId = random.number(); editorAttribs.cachedMetabrainzName = editorAttribs.name; From 5d567d52b5b3dcbd06d43b9357b203900399df48 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Tue, 4 Jul 2023 22:34:28 +0530 Subject: [PATCH 3/3] expect a 403 error on privs edit modal --- src/client/components/pages/parts/privs-edit-modal.js | 4 +++- src/common/helpers/error.js | 2 +- src/server/helpers/auth.js | 7 +++---- test/src/server/routes/entity/edition.js | 2 +- test/src/server/routes/entity/publisher.js | 2 +- test/src/server/routes/entity/series.js | 2 +- test/src/server/routes/entity/work.js | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/client/components/pages/parts/privs-edit-modal.js b/src/client/components/pages/parts/privs-edit-modal.js index 516764eec9..f60aea3749 100644 --- a/src/client/components/pages/parts/privs-edit-modal.js +++ b/src/client/components/pages/parts/privs-edit-modal.js @@ -61,8 +61,10 @@ class PrivsEditModal extends React.Component { }, method: 'POST' }); - if (!response.ok) { + if (response.status === 403) { + throw new Error(response.statusText); + } const {error} = await response.json(); throw new Error(error ?? response.statusText); } diff --git a/src/common/helpers/error.js b/src/common/helpers/error.js index 08fc3b71f4..f02f069f64 100644 --- a/src/common/helpers/error.js +++ b/src/common/helpers/error.js @@ -138,7 +138,7 @@ export class NotAuthorizedError extends PathError { static detailedMessage(req) { return [ `You do not have permission to access the following path: - ${req.path}`, + ${req.originalUrl}`, 'Please make sure you have the privileges to access the route!' ]; } diff --git a/src/server/helpers/auth.js b/src/server/helpers/auth.js index 44432d29f6..4b85d08e13 100644 --- a/src/server/helpers/auth.js +++ b/src/server/helpers/auth.js @@ -177,11 +177,10 @@ export function isAuthorized(flag) { return async (req, res, next) => { try { const {Editor} = req.app.locals.orm; - const latestPrivs = await Editor.query({where: {id: req.user.id}}) - .fetch({require: true}) - .then(editor => editor.get('privs')); + const editor = await Editor.query({where: {id: req.user.id}}) + .fetch({require: true}); /* eslint-disable no-bitwise */ - if (latestPrivs & flag) { + if (editor.get('privs') & flag) { return next(); } throw new error.NotAuthorizedError( diff --git a/test/src/server/routes/entity/edition.js b/test/src/server/routes/entity/edition.js index 270eab4faa..2436e0de8c 100644 --- a/test/src/server/routes/entity/edition.js +++ b/test/src/server/routes/entity/edition.js @@ -7,7 +7,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Edition routes', () => { +describe('Edition routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent; diff --git a/test/src/server/routes/entity/publisher.js b/test/src/server/routes/entity/publisher.js index 87a76af9e7..0656930f38 100644 --- a/test/src/server/routes/entity/publisher.js +++ b/test/src/server/routes/entity/publisher.js @@ -8,7 +8,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Publisher routes', () => { +describe('Publisher routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent; diff --git a/test/src/server/routes/entity/series.js b/test/src/server/routes/entity/series.js index ecd4eef792..246db746f0 100644 --- a/test/src/server/routes/entity/series.js +++ b/test/src/server/routes/entity/series.js @@ -27,7 +27,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Series routes', () => { +describe('Series routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent; diff --git a/test/src/server/routes/entity/work.js b/test/src/server/routes/entity/work.js index b220bbc593..15a9f3a886 100644 --- a/test/src/server/routes/entity/work.js +++ b/test/src/server/routes/entity/work.js @@ -8,7 +8,7 @@ import chaiHttp from 'chai-http'; chai.use(chaiHttp); const {expect} = chai; -describe('Work routes', () => { +describe('Work routes with entity editing priv', () => { const aBBID = getRandomUUID(); const inValidBBID = 'have-you-seen-the-fnords'; let agent;