Procházet zdrojové kódy

feat: update access token parser to include specific scopes for page management routes

reiji-h před 1 rokem
rodič
revize
47febad3ae

+ 2 - 1
apps/app/src/server/routes/apiv3/page/check-page-existence.ts

@@ -6,6 +6,7 @@ import type { ValidationChain } from 'express-validator';
 import { query } from 'express-validator';
 import { query } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
@@ -39,7 +40,7 @@ export const checkPageExistenceHandlersFactory: CreatePageHandlersFactory = (cro
   ];
   ];
 
 
   return [
   return [
-    accessTokenParser(), loginRequired,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequired,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: Req, res: ApiV3Response) => {
     async(req: Req, res: ApiV3Response) => {
       const { path } = req.query;
       const { path } = req.query;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -16,6 +16,7 @@ import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
 import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
 import type { IOptionsForCreate } from '~/interfaces/page';
 import type { IOptionsForCreate } from '~/interfaces/page';
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
@@ -217,7 +218,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
   const addActivity = generateAddActivityMiddleware();
   const addActivity = generateAddActivityMiddleware();
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly, excludeReadOnlyUser, addActivity,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, excludeReadOnlyUser, addActivity,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: CreatePageRequest, res: ApiV3Response) => {
     async(req: CreatePageRequest, res: ApiV3Response) => {
       const {
       const {

+ 2 - 1
apps/app/src/server/routes/apiv3/page/get-yjs-data.ts

@@ -5,6 +5,7 @@ import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
@@ -34,7 +35,7 @@ export const getYjsDataHandlerFactory: GetYjsDataHandlerFactory = (crowi) => {
   ];
   ];
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly,
+    accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequiredStrictly,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: Req, res: ApiV3Response) => {
     async(req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;
       const { pageId } = req.params;

+ 73 - 66
apps/app/src/server/routes/apiv3/page/index.ts

@@ -16,6 +16,7 @@ import sanitize from 'sanitize-filename';
 
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import type { IPageGrantData } from '~/interfaces/page';
 import type { IPageGrantData } from '~/interfaces/page';
+import { SCOPE } from '~/interfaces/scope';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
@@ -211,7 +212,7 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/Page'
    *                  $ref: '#/components/schemas/Page'
    */
    */
-  router.get('/', certifySharedPage, accessTokenParser(), loginRequired, validator.getPage, apiV3FormValidator, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.BASE.PAGE]), certifySharedPage, loginRequired, validator.getPage, apiV3FormValidator, async(req, res) => {
     const { user, isSharedPage } = req;
     const { user, isSharedPage } = req;
     const {
     const {
       pageId, path, findAll, revisionId, shareLinkId, includeEmpty,
       pageId, path, findAll, revisionId, shareLinkId, includeEmpty,
@@ -442,7 +443,7 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/Page'
    *                  $ref: '#/components/schemas/Page'
    */
    */
-  router.put('/likes', accessTokenParser(), loginRequiredStrictly, addActivity, validator.likes, apiV3FormValidator, async(req, res) => {
+  router.put('/likes', accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, addActivity, validator.likes, apiV3FormValidator, async(req, res) => {
     const { pageId, bool: isLiked } = req.body;
     const { pageId, bool: isLiked } = req.body;
 
 
     let page;
     let page;
@@ -512,7 +513,7 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            description: Internal server error.
    *            description: Internal server error.
    */
    */
-  router.get('/info', certifySharedPage, loginRequired, validator.info, apiV3FormValidator, async(req, res) => {
+  router.get('/info', accessTokenParser([SCOPE.READ.BASE.PAGE]), certifySharedPage, loginRequired, validator.info, apiV3FormValidator, async(req, res) => {
     const { user, isSharedPage } = req;
     const { user, isSharedPage } = req;
     const { pageId } = req.query;
     const { pageId } = req.query;
 
 
@@ -562,7 +563,7 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            description: Internal server error.
    *            description: Internal server error.
    */
    */
-  router.get('/grant-data', loginRequiredStrictly, validator.getGrantData, apiV3FormValidator, async(req, res) => {
+  router.get('/grant-data', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequiredStrictly, validator.getGrantData, apiV3FormValidator, async(req, res) => {
     const { pageId } = req.query;
     const { pageId } = req.query;
 
 
     const Page = mongoose.model<IPage, PageModel>('Page');
     const Page = mongoose.model<IPage, PageModel>('Page');
@@ -664,7 +665,8 @@ module.exports = (crowi) => {
    *         500:
    *         500:
    *           description: Internal server error.
    *           description: Internal server error.
    */
    */
-  router.get('/non-user-related-groups-granted', loginRequiredStrictly, validator.nonUserRelatedGroupsGranted, apiV3FormValidator,
+  router.get('/non-user-related-groups-granted', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequiredStrictly,
+    validator.nonUserRelatedGroupsGranted, apiV3FormValidator,
     async(req, res: ApiV3Response) => {
     async(req, res: ApiV3Response) => {
       const { user } = req;
       const { user } = req;
       const path = normalizePath(req.query.path);
       const path = normalizePath(req.query.path);
@@ -734,28 +736,29 @@ module.exports = (crowi) => {
    *         500:
    *         500:
    *           description: Internal server error.
    *           description: Internal server error.
    */
    */
-  router.get('/applicable-grant', loginRequiredStrictly, validator.applicableGrant, apiV3FormValidator, async(req, res) => {
-    const { pageId } = req.query;
+  router.get('/applicable-grant', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequiredStrictly, validator.applicableGrant, apiV3FormValidator,
+    async(req, res) => {
+      const { pageId } = req.query;
 
 
-    const Page = crowi.model('Page');
-    const page = await Page.findByIdAndViewer(pageId, req.user, null);
+      const Page = crowi.model('Page');
+      const page = await Page.findByIdAndViewer(pageId, req.user, null);
 
 
-    if (page == null) {
+      if (page == null) {
       // Empty page should not be related to grant API
       // Empty page should not be related to grant API
-      return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
-    }
+        return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
+      }
 
 
-    let data;
-    try {
-      data = await crowi.pageGrantService.calcApplicableGrantData(page, req.user);
-    }
-    catch (err) {
-      logger.error('Error occurred while processing calcApplicableGrantData.', err);
-      return res.apiv3Err(err, 500);
-    }
+      let data;
+      try {
+        data = await crowi.pageGrantService.calcApplicableGrantData(page, req.user);
+      }
+      catch (err) {
+        logger.error('Error occurred while processing calcApplicableGrantData.', err);
+        return res.apiv3Err(err, 500);
+      }
 
 
-    return res.apiv3(data);
-  });
+      return res.apiv3(data);
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -794,32 +797,34 @@ module.exports = (crowi) => {
    *               schema:
    *               schema:
    *                 $ref: '#/components/schemas/Page'
    *                 $ref: '#/components/schemas/Page'
    */
    */
-  router.put('/:pageId/grant', loginRequiredStrictly, excludeReadOnlyUser, validator.updateGrant, apiV3FormValidator, async(req, res) => {
-    const { pageId } = req.params;
-    const { grant, userRelatedGrantedGroups } = req.body;
+  router.put('/:pageId/grant', accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, excludeReadOnlyUser,
+    validator.updateGrant, apiV3FormValidator,
+    async(req, res) => {
+      const { pageId } = req.params;
+      const { grant, userRelatedGrantedGroups } = req.body;
 
 
-    const Page = crowi.model('Page');
+      const Page = crowi.model('Page');
 
 
-    const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
+      const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
 
 
-    if (page == null) {
+      if (page == null) {
       // Empty page should not be related to grant API
       // Empty page should not be related to grant API
-      return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
-    }
+        return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
+      }
 
 
-    let data;
-    try {
-      const shouldUseV4Process = false;
-      const grantData = { grant, userRelatedGrantedGroups };
-      data = await crowi.pageService.updateGrant(page, req.user, grantData, shouldUseV4Process);
-    }
-    catch (err) {
-      logger.error('Error occurred while processing calcApplicableGrantData.', err);
-      return res.apiv3Err(err, 500);
-    }
+      let data;
+      try {
+        const shouldUseV4Process = false;
+        const grantData = { grant, userRelatedGrantedGroups };
+        data = await crowi.pageService.updateGrant(page, req.user, grantData, shouldUseV4Process);
+      }
+      catch (err) {
+        logger.error('Error occurred while processing calcApplicableGrantData.', err);
+        return res.apiv3Err(err, 500);
+      }
 
 
-    return res.apiv3(data);
-  });
+      return res.apiv3(data);
+    });
 
 
   /**
   /**
   * @swagger
   * @swagger
@@ -834,7 +839,7 @@ module.exports = (crowi) => {
   *          200:
   *          200:
   *            description: Return page's markdown
   *            description: Return page's markdown
   */
   */
-  router.get('/export/:pageId', loginRequiredStrictly, validator.export, async(req, res) => {
+  router.get('/export/:pageId', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequiredStrictly, validator.export, async(req, res) => {
     const pageId: string = req.params.pageId;
     const pageId: string = req.params.pageId;
     const { format, revisionId = null } = req.query;
     const { format, revisionId = null } = req.query;
     let revision;
     let revision;
@@ -958,7 +963,7 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            description: Internal server error.
    *            description: Internal server error.
    */
    */
-  router.get('/exist-paths', loginRequired, validator.exist, apiV3FormValidator, async(req, res) => {
+  router.get('/exist-paths', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, validator.exist, apiV3FormValidator, async(req, res) => {
     const { fromPath, toPath } = req.query;
     const { fromPath, toPath } = req.query;
 
 
     try {
     try {
@@ -1012,31 +1017,33 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            description: Internal server error.
    *            description: Internal server error.
    */
    */
-  router.put('/subscribe', accessTokenParser(), loginRequiredStrictly, addActivity, validator.subscribe, apiV3FormValidator, async(req, res) => {
-    const { pageId, status } = req.body;
-    const userId = req.user._id;
+  router.put('/subscribe', accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, addActivity,
+    validator.subscribe, apiV3FormValidator,
+    async(req, res) => {
+      const { pageId, status } = req.body;
+      const userId = req.user._id;
 
 
-    try {
-      const subscription = await Subscription.subscribeByPageId(userId, pageId, status);
+      try {
+        const subscription = await Subscription.subscribeByPageId(userId, pageId, status);
 
 
-      const parameters = {};
-      if (SubscriptionStatusType.SUBSCRIBE === status) {
-        Object.assign(parameters, { action: SupportedAction.ACTION_PAGE_SUBSCRIBE });
-      }
-      else if (SubscriptionStatusType.UNSUBSCRIBE === status) {
-        Object.assign(parameters, { action: SupportedAction.ACTION_PAGE_UNSUBSCRIBE });
+        const parameters = {};
+        if (SubscriptionStatusType.SUBSCRIBE === status) {
+          Object.assign(parameters, { action: SupportedAction.ACTION_PAGE_SUBSCRIBE });
+        }
+        else if (SubscriptionStatusType.UNSUBSCRIBE === status) {
+          Object.assign(parameters, { action: SupportedAction.ACTION_PAGE_UNSUBSCRIBE });
+        }
+        if ('action' in parameters) {
+          activityEvent.emit('update', res.locals.activity._id, parameters);
+        }
+
+        return res.apiv3({ subscription });
       }
       }
-      if ('action' in parameters) {
-        activityEvent.emit('update', res.locals.activity._id, parameters);
+      catch (err) {
+        logger.error('Failed to update subscribe status', err);
+        return res.apiv3Err(err, 500);
       }
       }
-
-      return res.apiv3({ subscription });
-    }
-    catch (err) {
-      logger.error('Failed to update subscribe status', err);
-      return res.apiv3Err(err, 500);
-    }
-  });
+    });
 
 
 
 
   /**
   /**
@@ -1072,7 +1079,7 @@ module.exports = (crowi) => {
    *                   page:
    *                   page:
    *                     $ref: '#/components/schemas/Page'
    *                     $ref: '#/components/schemas/Page'
    */
    */
-  router.put('/:pageId/content-width', accessTokenParser(), loginRequiredStrictly, excludeReadOnlyUser,
+  router.put('/:pageId/content-width', accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, excludeReadOnlyUser,
     validator.contentWidth, apiV3FormValidator, async(req, res) => {
     validator.contentWidth, apiV3FormValidator, async(req, res) => {
       const { pageId } = req.params;
       const { pageId } = req.params;
       const { expandContentWidth } = req.body;
       const { expandContentWidth } = req.body;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/publish-page.ts

@@ -5,6 +5,7 @@ import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
@@ -38,7 +39,7 @@ export const publishPageHandlersFactory: PublishPageHandlersFactory = (crowi) =>
   ];
   ];
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: Req, res: ApiV3Response) => {
     async(req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;
       const { pageId } = req.params;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts

@@ -5,6 +5,7 @@ import type { ValidationChain } from 'express-validator';
 import { param, body } from 'express-validator';
 import { param, body } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
@@ -39,7 +40,7 @@ export const syncLatestRevisionBodyToYjsDraftHandlerFactory: SyncLatestRevisionB
   ];
   ];
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: Req, res: ApiV3Response) => {
     async(req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;
       const { pageId } = req.params;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/unpublish-page.ts

@@ -5,6 +5,7 @@ import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
@@ -38,7 +39,7 @@ export const unpublishPageHandlersFactory: UnpublishPageHandlersFactory = (crowi
   ];
   ];
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: Req, res: ApiV3Response) => {
     async(req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;
       const { pageId } = req.params;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -15,6 +15,7 @@ import { isAiEnabled } from '~/features/openai/server/services';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { type IApiv3PageUpdateParams, PageUpdateErrorCode } from '~/interfaces/apiv3';
 import { type IApiv3PageUpdateParams, PageUpdateErrorCode } from '~/interfaces/apiv3';
 import type { IOptionsForUpdate } from '~/interfaces/page';
 import type { IOptionsForUpdate } from '~/interfaces/page';
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
@@ -133,7 +134,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
   const addActivity = generateAddActivityMiddleware();
   const addActivity = generateAddActivityMiddleware();
 
 
   return [
   return [
-    accessTokenParser(), loginRequiredStrictly, excludeReadOnlyUser, addActivity,
+    accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, excludeReadOnlyUser, addActivity,
     validator, apiV3FormValidator,
     validator, apiV3FormValidator,
     async(req: UpdatePageRequest, res: ApiV3Response) => {
     async(req: UpdatePageRequest, res: ApiV3Response) => {
       const {
       const {

+ 341 - 322
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -2,8 +2,11 @@ import { ConfigSource } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import xss from 'xss';
 import xss from 'xss';
 
 
+
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
 import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
+import { SCOPE } from '~/interfaces/scope';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import ShareLink from '~/server/models/share-link';
 import ShareLink from '~/server/models/share-link';
@@ -345,7 +348,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: security params
    *                      description: security params
    */
    */
-  router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     const securityParams = {
     const securityParams = {
       generalSetting: {
       generalSetting: {
@@ -483,7 +486,7 @@ module.exports = (crowi) => {
    *                  description: updated param
    *                  description: updated param
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/authentication/enabled', loginRequiredStrictly, adminRequired, addActivity, validator.authenticationSetting, apiV3FormValidator, async(req, res) => {
+  router.put('/authentication/enabled', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, validator.authenticationSetting, apiV3FormValidator, async(req, res) => {
     const { isEnabled, authId } = req.body;
     const { isEnabled, authId } = req.body;
 
 
     let setupStrategies = await crowi.passportService.getSetupStrategies();
     let setupStrategies = await crowi.passportService.getSetupStrategies();
@@ -592,7 +595,7 @@ module.exports = (crowi) => {
    *                        description: setup strategie
    *                        description: setup strategie
    *                      example: ["local"]
    *                      example: ["local"]
    */
    */
-  router.get('/authentication/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/authentication/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => {
     const setupStrategies = await crowi.passportService.getSetupStrategies();
     const setupStrategies = await crowi.passportService.getSetupStrategies();
 
 
     return res.apiv3({ setupStrategies });
     return res.apiv3({ setupStrategies });
@@ -619,70 +622,72 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/GeneralSetting'
    *                  $ref: '#/components/schemas/GeneralSetting'
    */
    */
-  router.put('/general-setting', loginRequiredStrictly, adminRequired, addActivity, validator.generalSetting, apiV3FormValidator, async(req, res) => {
-    const updateData = {
-      'security:sessionMaxAge': parseInt(req.body.sessionMaxAge),
-      'security:restrictGuestMode': req.body.restrictGuestMode,
-      'security:pageDeletionAuthority': req.body.pageDeletionAuthority,
-      'security:pageRecursiveDeletionAuthority': req.body.pageRecursiveDeletionAuthority,
-      'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority,
-      'security:pageRecursiveCompleteDeletionAuthority': req.body.pageRecursiveCompleteDeletionAuthority,
-      'security:isAllGroupMembershipRequiredForPageCompleteDeletion': req.body.isAllGroupMembershipRequiredForPageCompleteDeletion,
-      'security:list-policy:hideRestrictedByOwner': req.body.hideRestrictedByOwner,
-      'security:list-policy:hideRestrictedByGroup': req.body.hideRestrictedByGroup,
-      'security:user-homepage-deletion:isEnabled': req.body.isUsersHomepageDeletionEnabled,
-      // Validate user-homepage-deletion config
-      'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': req.body.isUsersHomepageDeletionEnabled
-        ? req.body.isForceDeleteUserHomepageOnUserDeletion
-        : false,
-      'security:isRomUserAllowedToComment': req.body.isRomUserAllowedToComment,
-    };
+  router.put('/general-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.generalSetting, apiV3FormValidator,
+    async(req, res) => {
+      const updateData = {
+        'security:sessionMaxAge': parseInt(req.body.sessionMaxAge),
+        'security:restrictGuestMode': req.body.restrictGuestMode,
+        'security:pageDeletionAuthority': req.body.pageDeletionAuthority,
+        'security:pageRecursiveDeletionAuthority': req.body.pageRecursiveDeletionAuthority,
+        'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority,
+        'security:pageRecursiveCompleteDeletionAuthority': req.body.pageRecursiveCompleteDeletionAuthority,
+        'security:isAllGroupMembershipRequiredForPageCompleteDeletion': req.body.isAllGroupMembershipRequiredForPageCompleteDeletion,
+        'security:list-policy:hideRestrictedByOwner': req.body.hideRestrictedByOwner,
+        'security:list-policy:hideRestrictedByGroup': req.body.hideRestrictedByGroup,
+        'security:user-homepage-deletion:isEnabled': req.body.isUsersHomepageDeletionEnabled,
+        // Validate user-homepage-deletion config
+        'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': req.body.isUsersHomepageDeletionEnabled
+          ? req.body.isForceDeleteUserHomepageOnUserDeletion
+          : false,
+        'security:isRomUserAllowedToComment': req.body.isRomUserAllowedToComment,
+      };
 
 
-    // Validate delete config
-    const [singleAuthority1, recursiveAuthority1] = prepareDeleteConfigValuesForCalc(req.body.pageDeletionAuthority, req.body.pageRecursiveDeletionAuthority);
-    // eslint-disable-next-line max-len
-    const [singleAuthority2, recursiveAuthority2] = prepareDeleteConfigValuesForCalc(req.body.pageCompleteDeletionAuthority, req.body.pageRecursiveCompleteDeletionAuthority);
-    const isDeleteConfigNormalized = validateDeleteConfigs(singleAuthority1, recursiveAuthority1)
+      // Validate delete config
+      const [singleAuthority1, recursiveAuthority1] = prepareDeleteConfigValuesForCalc(req.body.pageDeletionAuthority, req.body.pageRecursiveDeletionAuthority);
+      // eslint-disable-next-line max-len
+      const [singleAuthority2, recursiveAuthority2] = prepareDeleteConfigValuesForCalc(req.body.pageCompleteDeletionAuthority, req.body.pageRecursiveCompleteDeletionAuthority);
+      const isDeleteConfigNormalized = validateDeleteConfigs(singleAuthority1, recursiveAuthority1)
       && validateDeleteConfigs(singleAuthority2, recursiveAuthority2);
       && validateDeleteConfigs(singleAuthority2, recursiveAuthority2);
-    if (!isDeleteConfigNormalized) {
-      return res.apiv3Err(new ErrorV3('Delete config values are not correct.', 'delete_config_not_normalized'));
-    }
+      if (!isDeleteConfigNormalized) {
+        return res.apiv3Err(new ErrorV3('Delete config values are not correct.', 'delete_config_not_normalized'));
+      }
 
 
-    const wikiMode = await configManager.getConfig('security:wikiMode');
-    if (wikiMode === 'private' || wikiMode === 'public') {
-      logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
-      delete updateData['security:restrictGuestMode'];
-    }
-    try {
-      await configManager.updateConfigs(updateData);
-      const securitySettingParams = {
-        sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'),
-        restrictGuestMode: await configManager.getConfig('security:restrictGuestMode'),
-        pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'),
-        pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'),
-        pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'),
-        pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'),
-        isAllGroupMembershipRequiredForPageCompleteDeletion:
+      const wikiMode = await configManager.getConfig('security:wikiMode');
+      if (wikiMode === 'private' || wikiMode === 'public') {
+        logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
+        delete updateData['security:restrictGuestMode'];
+      }
+      try {
+        await configManager.updateConfigs(updateData);
+        const securitySettingParams = {
+          sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'),
+          restrictGuestMode: await configManager.getConfig('security:restrictGuestMode'),
+          pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'),
+          pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'),
+          pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'),
+          pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'),
+          isAllGroupMembershipRequiredForPageCompleteDeletion:
         await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
         await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
-        hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'),
-        hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'),
-        isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'),
-        isForceDeleteUserHomepageOnUserDeletion:
+          hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'),
+          hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'),
+          isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'),
+          isForceDeleteUserHomepageOnUserDeletion:
         await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
         await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
-        isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'),
-      };
+          isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'),
+        };
 
 
-      const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating security setting';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed'));
-    }
-  });
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating security setting';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -705,26 +710,28 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/ShareLinkSetting'
    *                  $ref: '#/components/schemas/ShareLinkSetting'
    */
    */
-  router.put('/share-link-setting', loginRequiredStrictly, adminRequired, addActivity, validator.generalSetting, apiV3FormValidator, async(req, res) => {
-    const updateData = {
-      'security:disableLinkSharing': req.body.disableLinkSharing,
-    };
-    try {
-      await configManager.updateConfigs(updateData);
-      const securitySettingParams = {
-        disableLinkSharing: configManager.getConfig('security:disableLinkSharing'),
+  router.put('/share-link-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.generalSetting, apiV3FormValidator,
+    async(req, res) => {
+      const updateData = {
+        'security:disableLinkSharing': req.body.disableLinkSharing,
       };
       };
-      // eslint-disable-next-line max-len
-      const parameters = { action: updateData['security:disableLinkSharing'] ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating security setting';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed'));
-    }
-  });
+      try {
+        await configManager.updateConfigs(updateData);
+        const securitySettingParams = {
+          disableLinkSharing: configManager.getConfig('security:disableLinkSharing'),
+        };
+        // eslint-disable-next-line max-len
+        const parameters = { action: updateData['security:disableLinkSharing'] ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating security setting';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed'));
+      }
+    });
 
 
 
 
   /**
   /**
@@ -745,7 +752,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: suceed to get all share links
    *                      description: suceed to get all share links
    */
    */
-  router.get('/all-share-links/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/all-share-links/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => {
     const page = parseInt(req.query.page) || 1;
     const page = parseInt(req.query.page) || 1;
     const limit = 10;
     const limit = 10;
     const linkQuery = {};
     const linkQuery = {};
@@ -782,7 +789,7 @@ module.exports = (crowi) => {
    *            description: succeed to delete all share links
    *            description: succeed to delete all share links
    */
    */
 
 
-  router.delete('/all-share-links/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.delete('/all-share-links/', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => {
     try {
     try {
       const removedAct = await ShareLink.remove({});
       const removedAct = await ShareLink.remove({});
       const removeTotal = await removedAct.n;
       const removeTotal = await removedAct.n;
@@ -816,36 +823,38 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/LocalSetting'
    *                  $ref: '#/components/schemas/LocalSetting'
    */
    */
-  router.put('/local-setting', loginRequiredStrictly, adminRequired, addActivity, validator.localSetting, apiV3FormValidator, async(req, res) => {
-    try {
-      const sanitizedRegistrationWhitelist = req.body.registrationWhitelist
-        .map(line => xss(line, { stripIgnoreTag: true }));
-
-      const requestParams = {
-        'security:registrationMode': req.body.registrationMode,
-        'security:registrationWhitelist': sanitizedRegistrationWhitelist,
-        'security:passport-local:isPasswordResetEnabled': req.body.isPasswordResetEnabled,
-        'security:passport-local:isEmailAuthenticationEnabled': req.body.isEmailAuthenticationEnabled,
-      };
-
-      await updateAndReloadStrategySettings('local', requestParams);
-
-      const localSettingParams = {
-        registrationMode: await configManager.getConfig('security:registrationMode'),
-        registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'),
-        isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'),
-        isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'),
-      };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ localSettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating local setting';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-local-setting failed'));
-    }
-  });
+  router.put('/local-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.localSetting, apiV3FormValidator,
+    async(req, res) => {
+      try {
+        const sanitizedRegistrationWhitelist = req.body.registrationWhitelist
+          .map(line => xss(line, { stripIgnoreTag: true }));
+
+        const requestParams = {
+          'security:registrationMode': req.body.registrationMode,
+          'security:registrationWhitelist': sanitizedRegistrationWhitelist,
+          'security:passport-local:isPasswordResetEnabled': req.body.isPasswordResetEnabled,
+          'security:passport-local:isEmailAuthenticationEnabled': req.body.isEmailAuthenticationEnabled,
+        };
+
+        await updateAndReloadStrategySettings('local', requestParams);
+
+        const localSettingParams = {
+          registrationMode: await configManager.getConfig('security:registrationMode'),
+          registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'),
+          isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'),
+          isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ localSettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating local setting';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-local-setting failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -868,49 +877,51 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/LdapAuthSetting'
    *                  $ref: '#/components/schemas/LdapAuthSetting'
    */
    */
-  router.put('/ldap', loginRequiredStrictly, adminRequired, addActivity, validator.ldapAuth, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
-      'security:passport-ldap:serverUrl': req.body.serverUrl,
-      'security:passport-ldap:isUserBind': req.body.isUserBind,
-      'security:passport-ldap:bindDN': req.body.ldapBindDN,
-      'security:passport-ldap:bindDNPassword': req.body.ldapBindDNPassword,
-      'security:passport-ldap:searchFilter': req.body.ldapSearchFilter,
-      'security:passport-ldap:attrMapUsername': req.body.ldapAttrMapUsername,
-      'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
-      'security:passport-ldap:attrMapMail': req.body.ldapAttrMapMail,
-      'security:passport-ldap:attrMapName': req.body.ldapAttrMapName,
-      'security:passport-ldap:groupSearchBase': req.body.ldapGroupSearchBase,
-      'security:passport-ldap:groupSearchFilter': req.body.ldapGroupSearchFilter,
-      'security:passport-ldap:groupDnProperty': req.body.ldapGroupDnProperty,
-    };
-
-    try {
-      await updateAndReloadStrategySettings('ldap', requestParams);
-
-      const securitySettingParams = {
-        serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'),
-        isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'),
-        ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'),
-        ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'),
-        ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'),
-        ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
-        ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'),
-        ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'),
-        ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'),
-        ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'),
-        ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'),
+  router.put('/ldap', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.ldapAuth, apiV3FormValidator,
+    async(req, res) => {
+      const requestParams = {
+        'security:passport-ldap:serverUrl': req.body.serverUrl,
+        'security:passport-ldap:isUserBind': req.body.isUserBind,
+        'security:passport-ldap:bindDN': req.body.ldapBindDN,
+        'security:passport-ldap:bindDNPassword': req.body.ldapBindDNPassword,
+        'security:passport-ldap:searchFilter': req.body.ldapSearchFilter,
+        'security:passport-ldap:attrMapUsername': req.body.ldapAttrMapUsername,
+        'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
+        'security:passport-ldap:attrMapMail': req.body.ldapAttrMapMail,
+        'security:passport-ldap:attrMapName': req.body.ldapAttrMapName,
+        'security:passport-ldap:groupSearchBase': req.body.ldapGroupSearchBase,
+        'security:passport-ldap:groupSearchFilter': req.body.ldapGroupSearchFilter,
+        'security:passport-ldap:groupDnProperty': req.body.ldapGroupDnProperty,
       };
       };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating SAML setting';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
-    }
-  });
+
+      try {
+        await updateAndReloadStrategySettings('ldap', requestParams);
+
+        const securitySettingParams = {
+          serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'),
+          isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'),
+          ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'),
+          ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'),
+          ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'),
+          ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'),
+          isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
+          ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'),
+          ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'),
+          ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'),
+          ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'),
+          ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating SAML setting';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -933,78 +944,80 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    */
    */
-  router.put('/saml', loginRequiredStrictly, adminRequired, addActivity, validator.samlAuth, apiV3FormValidator, async(req, res) => {
-    const { t } = await getTranslation({ lang: req.user.lang, ns: ['translation', 'admin'] });
-
-    //  For the value of each mandatory items,
-    //  check whether it from the environment variables is empty and form value to update it is empty
-    //  validate the syntax of a attribute - based login control rule
-    const invalidValues = [];
-    for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
-      const key = configKey.replace('security:passport-saml:', '');
-      const formValue = req.body[key];
-      if (configManager.getConfig(configKey, ConfigSource.env) == null && formValue == null) {
-        const formItemName = t(`security_settings.form_item_name.${key}`);
-        invalidValues.push(t('input_validation.message.required', { param: formItemName }));
+  router.put('/saml', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.samlAuth, apiV3FormValidator,
+    async(req, res) => {
+      const { t } = await getTranslation({ lang: req.user.lang, ns: ['translation', 'admin'] });
+
+      //  For the value of each mandatory items,
+      //  check whether it from the environment variables is empty and form value to update it is empty
+      //  validate the syntax of a attribute - based login control rule
+      const invalidValues = [];
+      for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
+        const key = configKey.replace('security:passport-saml:', '');
+        const formValue = req.body[key];
+        if (configManager.getConfig(configKey, ConfigSource.env) == null && formValue == null) {
+          const formItemName = t(`security_settings.form_item_name.${key}`);
+          invalidValues.push(t('input_validation.message.required', { param: formItemName }));
+        }
+      }
+      if (invalidValues.length !== 0) {
+        return res.apiv3Err(t('input_validation.message.error_message'), 400, invalidValues);
       }
       }
-    }
-    if (invalidValues.length !== 0) {
-      return res.apiv3Err(t('input_validation.message.error_message'), 400, invalidValues);
-    }
 
 
-    const rule = req.body.ABLCRule;
-    // Empty string disables attribute-based login control.
-    // So, when rule is empty string, validation is passed.
-    if (rule != null) {
+      const rule = req.body.ABLCRule;
+      // Empty string disables attribute-based login control.
+      // So, when rule is empty string, validation is passed.
+      if (rule != null) {
+        try {
+          crowi.passportService.parseABLCRule(rule);
+        }
+        catch (err) {
+          return res.apiv3Err(t('input_validation.message.invalid_syntax', { syntax: t('security_settings.form_item_name.ABLCRule') }), 400);
+        }
+      }
+
+      const requestParams = {
+        'security:passport-saml:entryPoint': req.body.entryPoint,
+        'security:passport-saml:issuer': req.body.issuer,
+        'security:passport-saml:cert': req.body.cert,
+        'security:passport-saml:attrMapId': req.body.attrMapId,
+        'security:passport-saml:attrMapUsername': req.body.attrMapUsername,
+        'security:passport-saml:attrMapMail': req.body.attrMapMail,
+        'security:passport-saml:attrMapFirstName': req.body.attrMapFirstName,
+        'security:passport-saml:attrMapLastName': req.body.attrMapLastName,
+        'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
+        'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+        'security:passport-saml:ABLCRule': req.body.ABLCRule,
+      };
+
       try {
       try {
-        crowi.passportService.parseABLCRule(rule);
+        await updateAndReloadStrategySettings('saml', requestParams);
+
+        const securitySettingParams = {
+          missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
+          samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db),
+          samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db),
+          samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db),
+          samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db),
+          samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db),
+          samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db),
+          samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db),
+          samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db),
+          isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
+          isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+          samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
       }
       }
       catch (err) {
       catch (err) {
-        return res.apiv3Err(t('input_validation.message.invalid_syntax', { syntax: t('security_settings.form_item_name.ABLCRule') }), 400);
+        const msg = 'Error occurred in updating SAML setting';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
       }
       }
-    }
-
-    const requestParams = {
-      'security:passport-saml:entryPoint': req.body.entryPoint,
-      'security:passport-saml:issuer': req.body.issuer,
-      'security:passport-saml:cert': req.body.cert,
-      'security:passport-saml:attrMapId': req.body.attrMapId,
-      'security:passport-saml:attrMapUsername': req.body.attrMapUsername,
-      'security:passport-saml:attrMapMail': req.body.attrMapMail,
-      'security:passport-saml:attrMapFirstName': req.body.attrMapFirstName,
-      'security:passport-saml:attrMapLastName': req.body.attrMapLastName,
-      'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
-      'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
-      'security:passport-saml:ABLCRule': req.body.ABLCRule,
-    };
-
-    try {
-      await updateAndReloadStrategySettings('saml', requestParams);
-
-      const securitySettingParams = {
-        missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
-        samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db),
-        samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db),
-        samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db),
-        samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db),
-        samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db),
-        samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db),
-        samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db),
-        samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
-        samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule'),
-      };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating SAML setting';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
-    }
-  });
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -1027,61 +1040,63 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/OidcAuthSetting'
    *                  $ref: '#/components/schemas/OidcAuthSetting'
    */
    */
-  router.put('/oidc', loginRequiredStrictly, adminRequired, addActivity, validator.oidcAuth, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
-      'security:passport-oidc:providerName': req.body.oidcProviderName,
-      'security:passport-oidc:issuerHost': req.body.oidcIssuerHost,
-      'security:passport-oidc:authorizationEndpoint': req.body.oidcAuthorizationEndpoint,
-      'security:passport-oidc:tokenEndpoint': req.body.oidcTokenEndpoint,
-      'security:passport-oidc:revocationEndpoint': req.body.oidcRevocationEndpoint,
-      'security:passport-oidc:introspectionEndpoint': req.body.oidcIntrospectionEndpoint,
-      'security:passport-oidc:userInfoEndpoint': req.body.oidcUserInfoEndpoint,
-      'security:passport-oidc:endSessionEndpoint': req.body.oidcEndSessionEndpoint,
-      'security:passport-oidc:registrationEndpoint': req.body.oidcRegistrationEndpoint,
-      'security:passport-oidc:jwksUri': req.body.oidcJWKSUri,
-      'security:passport-oidc:clientId': req.body.oidcClientId,
-      'security:passport-oidc:clientSecret': req.body.oidcClientSecret,
-      'security:passport-oidc:attrMapId': req.body.oidcAttrMapId,
-      'security:passport-oidc:attrMapUserName': req.body.oidcAttrMapUserName,
-      'security:passport-oidc:attrMapName': req.body.oidcAttrMapName,
-      'security:passport-oidc:attrMapMail': req.body.oidcAttrMapEmail,
-      'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
-      'security:passport-oidc:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
-    };
-
-    try {
-      await updateAndReloadStrategySettings('oidc', requestParams);
-
-      const securitySettingParams = {
-        oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'),
-        oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'),
-        oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'),
-        oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'),
-        oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'),
-        oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'),
-        oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'),
-        oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'),
-        oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'),
-        oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'),
-        oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'),
-        oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'),
-        oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'),
-        oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'),
-        oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'),
-        oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
+  router.put('/oidc', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.oidcAuth, apiV3FormValidator,
+    async(req, res) => {
+      const requestParams = {
+        'security:passport-oidc:providerName': req.body.oidcProviderName,
+        'security:passport-oidc:issuerHost': req.body.oidcIssuerHost,
+        'security:passport-oidc:authorizationEndpoint': req.body.oidcAuthorizationEndpoint,
+        'security:passport-oidc:tokenEndpoint': req.body.oidcTokenEndpoint,
+        'security:passport-oidc:revocationEndpoint': req.body.oidcRevocationEndpoint,
+        'security:passport-oidc:introspectionEndpoint': req.body.oidcIntrospectionEndpoint,
+        'security:passport-oidc:userInfoEndpoint': req.body.oidcUserInfoEndpoint,
+        'security:passport-oidc:endSessionEndpoint': req.body.oidcEndSessionEndpoint,
+        'security:passport-oidc:registrationEndpoint': req.body.oidcRegistrationEndpoint,
+        'security:passport-oidc:jwksUri': req.body.oidcJWKSUri,
+        'security:passport-oidc:clientId': req.body.oidcClientId,
+        'security:passport-oidc:clientSecret': req.body.oidcClientSecret,
+        'security:passport-oidc:attrMapId': req.body.oidcAttrMapId,
+        'security:passport-oidc:attrMapUserName': req.body.oidcAttrMapUserName,
+        'security:passport-oidc:attrMapName': req.body.oidcAttrMapName,
+        'security:passport-oidc:attrMapMail': req.body.oidcAttrMapEmail,
+        'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
+        'security:passport-oidc:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
       };
       };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating OpenIDConnect';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-OpenIDConnect-failed'));
-    }
-  });
+
+      try {
+        await updateAndReloadStrategySettings('oidc', requestParams);
+
+        const securitySettingParams = {
+          oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'),
+          oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'),
+          oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'),
+          oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'),
+          oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'),
+          oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'),
+          oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'),
+          oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'),
+          oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'),
+          oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'),
+          oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'),
+          oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'),
+          oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'),
+          oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'),
+          oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'),
+          oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'),
+          isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
+          isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating OpenIDConnect';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-OpenIDConnect-failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -1104,32 +1119,34 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/GoogleOAuthSetting'
    *                  $ref: '#/components/schemas/GoogleOAuthSetting'
    */
    */
-  router.put('/google-oauth', loginRequiredStrictly, adminRequired, addActivity, validator.googleOAuth, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
-      'security:passport-google:clientId': req.body.googleClientId,
-      'security:passport-google:clientSecret': req.body.googleClientSecret,
-      'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
-    };
-
+  router.put('/google-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.googleOAuth, apiV3FormValidator,
+    async(req, res) => {
+      const requestParams = {
+        'security:passport-google:clientId': req.body.googleClientId,
+        'security:passport-google:clientSecret': req.body.googleClientSecret,
+        'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+      };
 
 
-    try {
-      await updateAndReloadStrategySettings('google', requestParams);
 
 
-      const securitySettingParams = {
-        googleClientId: await configManager.getConfig('security:passport-google:clientId'),
-        googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'),
-      };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating googleOAuth';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-googleOAuth-failed'));
-    }
-  });
+      try {
+        await updateAndReloadStrategySettings('google', requestParams);
+
+        const securitySettingParams = {
+          googleClientId: await configManager.getConfig('security:passport-google:clientId'),
+          googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'),
+          isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating googleOAuth';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-googleOAuth-failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -1152,33 +1169,35 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/GitHubOAuthSetting'
    *                  $ref: '#/components/schemas/GitHubOAuthSetting'
    */
    */
-  router.put('/github-oauth', loginRequiredStrictly, adminRequired, addActivity, validator.githubOAuth, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
-      'security:passport-github:clientId': req.body.githubClientId,
-      'security:passport-github:clientSecret': req.body.githubClientSecret,
-      'security:passport-github:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
-    };
-
-    try {
-      await updateAndReloadStrategySettings('github', requestParams);
-
-      const securitySettingParams = {
-        githubClientId: await configManager.getConfig('security:passport-github:clientId'),
-        githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
+  router.put('/github-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.githubOAuth, apiV3FormValidator,
+    async(req, res) => {
+      const requestParams = {
+        'security:passport-github:clientId': req.body.githubClientId,
+        'security:passport-github:clientSecret': req.body.githubClientSecret,
+        'security:passport-github:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
       };
       };
-      const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ securitySettingParams });
-    }
-    catch (err) {
+
+      try {
+        await updateAndReloadStrategySettings('github', requestParams);
+
+        const securitySettingParams = {
+          githubClientId: await configManager.getConfig('security:passport-github:clientId'),
+          githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'),
+          isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
+        };
+        const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ securitySettingParams });
+      }
+      catch (err) {
       // reset strategy
       // reset strategy
-      await crowi.passportService.resetGitHubStrategy();
-      const msg = 'Error occurred in updating githubOAuth';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-githubOAuth-failed'));
-    }
-  });
+        await crowi.passportService.resetGitHubStrategy();
+        const msg = 'Error occurred in updating githubOAuth';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-githubOAuth-failed'));
+      }
+    });
 
 
   return router;
   return router;
 };
 };

+ 122 - 114
apps/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -10,13 +10,13 @@ import {
 } from '@growi/slack/dist/utils/check-communicable';
 } from '@growi/slack/dist/utils/check-communicable';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
+import { SCOPE } from '~/interfaces/scope';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 
-
 const axios = require('axios');
 const axios = require('axios');
 const express = require('express');
 const express = require('express');
 const { body, param } = require('express-validator');
 const { body, param } = require('express-validator');
@@ -169,7 +169,7 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: Succeeded to get info.
    *            description: Succeeded to get info.
    */
    */
-  router.get('/', accessTokenParser(), loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     const { configManager, slackIntegrationService } = crowi;
     const { configManager, slackIntegrationService } = crowi;
     const currentBotType = configManager.getConfig('slackbot:currentBotType');
     const currentBotType = configManager.getConfig('slackbot:currentBotType');
@@ -305,7 +305,7 @@ module.exports = (crowi) => {
    *             description: Succeeded to put botType setting.
    *             description: Succeeded to put botType setting.
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/bot-type', accessTokenParser(), loginRequiredStrictly, adminRequired, addActivity, validator.botType, apiV3FormValidator, async(req, res) => {
+  router.put('/bot-type', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.botType, apiV3FormValidator, async(req, res) => {
     const { currentBotType } = req.body;
     const { currentBotType } = req.body;
 
 
     if (currentBotType == null) {
     if (currentBotType == null) {
@@ -342,18 +342,19 @@ module.exports = (crowi) => {
    *           200:
    *           200:
    *             description: Succeeded to delete botType setting.
    *             description: Succeeded to delete botType setting.
    */
    */
-  router.delete('/bot-type', accessTokenParser(), loginRequiredStrictly, adminRequired, addActivity, apiV3FormValidator, async(req, res) => {
-    try {
-      await handleBotTypeChanging(req, res, null);
+  router.delete('/bot-type', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, apiV3FormValidator,
+    async(req, res) => {
+      try {
+        await handleBotTypeChanging(req, res, null);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_BOT_TYPE_DELETE });
-    }
-    catch (error) {
-      const msg = 'Error occured in resetting all';
-      logger.error('Error', error);
-      return res.apiv3Err(new ErrorV3(msg, 'resetting-all-failed'), 500);
-    }
-  });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_BOT_TYPE_DELETE });
+      }
+      catch (error) {
+        const msg = 'Error occured in resetting all';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'resetting-all-failed'), 500);
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -368,32 +369,33 @@ module.exports = (crowi) => {
    *           200:
    *           200:
    *             description: Succeeded to put CustomBotWithoutProxy setting.
    *             description: Succeeded to put CustomBotWithoutProxy setting.
    */
    */
-  router.put('/without-proxy/update-settings', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
-    if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
-      const msg = 'Not CustomBotWithoutProxy';
-      return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
-    }
+  router.put('/without-proxy/update-settings', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity,
+    async(req, res) => {
+      const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
+      if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
+        const msg = 'Not CustomBotWithoutProxy';
+        return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
+      }
 
 
-    const { slackSigningSecret, slackBotToken } = req.body;
-    const requestParams = {
-      'slackbot:withoutProxy:signingSecret': slackSigningSecret,
-      'slackbot:withoutProxy:botToken': slackBotToken,
-    };
-    try {
-      await updateSlackBotSettings(requestParams);
-      crowi.slackIntegrationService.publishUpdatedMessage();
+      const { slackSigningSecret, slackBotToken } = req.body;
+      const requestParams = {
+        'slackbot:withoutProxy:signingSecret': slackSigningSecret,
+        'slackbot:withoutProxy:botToken': slackBotToken,
+      };
+      try {
+        await updateSlackBotSettings(requestParams);
+        crowi.slackIntegrationService.publishUpdatedMessage();
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE });
 
 
-      return res.apiv3();
-    }
-    catch (error) {
-      const msg = 'Error occured in updating Custom bot setting';
-      logger.error('Error', error);
-      return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
-    }
-  });
+        return res.apiv3();
+      }
+      catch (error) {
+        const msg = 'Error occured in updating Custom bot setting';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -409,7 +411,7 @@ module.exports = (crowi) => {
    *             description: Succeeded to put CustomBotWithoutProxy permissions.
    *             description: Succeeded to put CustomBotWithoutProxy permissions.
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/without-proxy/update-permissions', loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithoutProxy, async(req, res) => {
+  router.put('/without-proxy/update-permissions', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithoutProxy, async(req, res) => {
     const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Not CustomBotWithoutProxy';
       const msg = 'Not CustomBotWithoutProxy';
@@ -451,42 +453,43 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: Succeeded to create slack app integration
    *            description: Succeeded to create slack app integration
    */
    */
-  router.post('/slack-app-integrations', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const SlackAppIntegrationRecordsNum = await SlackAppIntegration.countDocuments();
-    if (SlackAppIntegrationRecordsNum >= 10) {
-      const msg = 'Not be able to create more than 10 slack workspace integration settings';
-      logger.error('Error', msg);
-      return res.apiv3Err(new ErrorV3(msg, 'create-slackAppIntegeration-failed'), 500);
-    }
-
-    const count = await SlackAppIntegration.count();
-
-    const { tokenGtoP, tokenPtoG } = await SlackAppIntegration.generateUniqueAccessTokens();
-    try {
-      const initialSupportedCommandsForBroadcastUse = new Map(defaultSupportedCommandsNameForBroadcastUse.map(command => [command, true]));
-      const initialSupportedCommandsForSingleUse = new Map(defaultSupportedCommandsNameForSingleUse.map(command => [command, true]));
-      const initialPermissionsForSlackEventActions = new Map(defaultSupportedSlackEventActions.map(action => [action, true]));
-
-      const slackAppTokens = await SlackAppIntegration.create({
-        tokenGtoP,
-        tokenPtoG,
-        permissionsForBroadcastUseCommands: initialSupportedCommandsForBroadcastUse,
-        permissionsForSingleUseCommands: initialSupportedCommandsForSingleUse,
-        permissionsForSlackEvents: initialPermissionsForSlackEventActions,
-        isPrimary: count === 0,
-      });
+  router.post('/slack-app-integrations', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity,
+    async(req, res) => {
+      const SlackAppIntegrationRecordsNum = await SlackAppIntegration.countDocuments();
+      if (SlackAppIntegrationRecordsNum >= 10) {
+        const msg = 'Not be able to create more than 10 slack workspace integration settings';
+        logger.error('Error', msg);
+        return res.apiv3Err(new ErrorV3(msg, 'create-slackAppIntegeration-failed'), 500);
+      }
 
 
-      const parameters = { action: SupportedAction.ACTION_ADMIN_SLACK_WORKSPACE_CREATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+      const count = await SlackAppIntegration.count();
 
 
-      return res.apiv3(slackAppTokens, 200);
-    }
-    catch (error) {
-      const msg = 'Error occurred during creating slack integration settings procedure';
-      logger.error('Error', error);
-      return res.apiv3Err(new ErrorV3(msg, 'creating-slack-integration-settings-procedure-failed'), 500);
-    }
-  });
+      const { tokenGtoP, tokenPtoG } = await SlackAppIntegration.generateUniqueAccessTokens();
+      try {
+        const initialSupportedCommandsForBroadcastUse = new Map(defaultSupportedCommandsNameForBroadcastUse.map(command => [command, true]));
+        const initialSupportedCommandsForSingleUse = new Map(defaultSupportedCommandsNameForSingleUse.map(command => [command, true]));
+        const initialPermissionsForSlackEventActions = new Map(defaultSupportedSlackEventActions.map(action => [action, true]));
+
+        const slackAppTokens = await SlackAppIntegration.create({
+          tokenGtoP,
+          tokenPtoG,
+          permissionsForBroadcastUseCommands: initialSupportedCommandsForBroadcastUse,
+          permissionsForSingleUseCommands: initialSupportedCommandsForSingleUse,
+          permissionsForSlackEvents: initialPermissionsForSlackEventActions,
+          isPrimary: count === 0,
+        });
+
+        const parameters = { action: SupportedAction.ACTION_ADMIN_SLACK_WORKSPACE_CREATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3(slackAppTokens, 200);
+      }
+      catch (error) {
+        const msg = 'Error occurred during creating slack integration settings procedure';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'creating-slack-integration-settings-procedure-failed'), 500);
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -501,7 +504,8 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: Succeeded to delete access tokens for slack
    *            description: Succeeded to delete access tokens for slack
    */
    */
-  router.delete('/slack-app-integrations/:id', loginRequiredStrictly, adminRequired, validator.deleteIntegration, apiV3FormValidator, addActivity,
+  router.delete('/slack-app-integrations/:id', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired,
+    validator.deleteIntegration, apiV3FormValidator, addActivity,
     async(req, res) => {
     async(req, res) => {
       const { id } = req.params;
       const { id } = req.params;
 
 
@@ -525,26 +529,28 @@ module.exports = (crowi) => {
       }
       }
     });
     });
 
 
-  router.put('/proxy-uri', loginRequiredStrictly, adminRequired, addActivity, validator.proxyUri, apiV3FormValidator, async(req, res) => {
-    const { proxyUri } = req.body;
+  router.put('/proxy-uri', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.proxyUri, apiV3FormValidator,
+    async(req, res) => {
+      const { proxyUri } = req.body;
 
 
-    const requestParams = { 'slackbot:proxyUri': proxyUri };
+      const requestParams = { 'slackbot:proxyUri': proxyUri };
 
 
-    try {
-      await updateSlackBotSettings(requestParams);
-      crowi.slackIntegrationService.publishUpdatedMessage();
+      try {
+        await updateSlackBotSettings(requestParams);
+        crowi.slackIntegrationService.publishUpdatedMessage();
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_PROXY_URI_UPDATE });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_PROXY_URI_UPDATE });
 
 
-      return res.apiv3({});
-    }
-    catch (error) {
-      const msg = 'Error occured in updating Custom bot setting';
-      logger.error('Error', error);
-      return res.apiv3Err(new ErrorV3(msg, 'delete-SlackAppIntegration-failed'), 500);
-    }
+        return res.apiv3({});
+      }
+      catch (error) {
+        const msg = 'Error occured in updating Custom bot setting';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'delete-SlackAppIntegration-failed'), 500);
+      }
 
 
-  });
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -560,7 +566,7 @@ module.exports = (crowi) => {
    *            description: Succeeded to make it primary
    *            description: Succeeded to make it primary
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/slack-app-integrations/:id/make-primary', loginRequiredStrictly, adminRequired, addActivity, validator.makePrimary, apiV3FormValidator, async(req, res) => {
+  router.put('/slack-app-integrations/:id/make-primary', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.makePrimary, apiV3FormValidator, async(req, res) => {
 
 
     const { id } = req.params;
     const { id } = req.params;
 
 
@@ -607,7 +613,7 @@ module.exports = (crowi) => {
    *            description: Succeeded to regenerate slack app tokens
    *            description: Succeeded to regenerate slack app tokens
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/slack-app-integrations/:id/regenerate-tokens', loginRequiredStrictly, adminRequired, addActivity, validator.regenerateTokens, apiV3FormValidator, async(req, res) => {
+  router.put('/slack-app-integrations/:id/regenerate-tokens', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.regenerateTokens, apiV3FormValidator, async(req, res) => {
 
 
     const { id } = req.params;
     const { id } = req.params;
 
 
@@ -640,7 +646,7 @@ module.exports = (crowi) => {
    *            description: Succeeded to update supported commands
    *            description: Succeeded to update supported commands
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/slack-app-integrations/:id/permissions', loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithProxy, apiV3FormValidator, async(req, res) => {
+  router.put('/slack-app-integrations/:id/permissions', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithProxy, apiV3FormValidator, async(req, res) => {
     // TODO: look here 78975
     // TODO: look here 78975
     const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands, permissionsForSlackEventActions } = req.body;
     const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands, permissionsForSlackEventActions } = req.body;
     const { id } = req.params;
     const { id } = req.params;
@@ -699,7 +705,7 @@ module.exports = (crowi) => {
    *             description: Succeeded to delete botType setting.
    *             description: Succeeded to delete botType setting.
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.post('/slack-app-integrations/:id/relation-test', loginRequiredStrictly, adminRequired, addActivity, validator.relationTest, apiV3FormValidator, async(req, res) => {
+  router.post('/slack-app-integrations/:id/relation-test', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity, validator.relationTest, apiV3FormValidator, async(req, res) => {
     const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Not Proxy Type';
       const msg = 'Not Proxy Type';
@@ -776,32 +782,34 @@ module.exports = (crowi) => {
    *           200:
    *           200:
    *             description: Succeeded to connect to slack work space.
    *             description: Succeeded to connect to slack work space.
    */
    */
-  router.post('/without-proxy/test', loginRequiredStrictly, adminRequired, addActivity, validator.slackChannel, apiV3FormValidator, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
-    if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
-      const msg = 'Select Without Proxy Type';
-      return res.apiv3Err(new ErrorV3(msg, 'select-not-proxy-type'), 400);
-    }
+  router.post('/without-proxy/test', accessTokenParser([SCOPE.WRITE.ADMIN.SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.slackChannel, apiV3FormValidator,
+    async(req, res) => {
+      const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
+      if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
+        const msg = 'Select Without Proxy Type';
+        return res.apiv3Err(new ErrorV3(msg, 'select-not-proxy-type'), 400);
+      }
 
 
-    const slackBotToken = crowi.configManager.getConfig('slackbot:withoutProxy:botToken');
-    const status = await getConnectionStatus(slackBotToken);
-    if (status.error != null) {
-      return res.apiv3Err(new ErrorV3(`Error occured while getting connection. ${status.error}`, 'send-message-failed'));
-    }
+      const slackBotToken = crowi.configManager.getConfig('slackbot:withoutProxy:botToken');
+      const status = await getConnectionStatus(slackBotToken);
+      if (status.error != null) {
+        return res.apiv3Err(new ErrorV3(`Error occured while getting connection. ${status.error}`, 'send-message-failed'));
+      }
 
 
-    const { channel } = req.body;
-    const appSiteURL = crowi.configManager.getConfig('app:siteUrl');
-    try {
-      await sendSuccessMessage(slackBotToken, channel, appSiteURL);
-    }
-    catch (error) {
-      return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
-    }
+      const { channel } = req.body;
+      const appSiteURL = crowi.configManager.getConfig('app:siteUrl');
+      try {
+        await sendSuccessMessage(slackBotToken, channel, appSiteURL);
+      }
+      catch (error) {
+        return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
+      }
 
 
-    activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST });
+      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST });
 
 
-    return res.apiv3();
-  });
+      return res.apiv3();
+    });
 
 
   return router;
   return router;
 };
 };

+ 3 - 1
apps/app/src/server/routes/apiv3/user-group-relation.js

@@ -1,6 +1,8 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 
 
+import { SCOPE } from '~/interfaces/scope';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -43,7 +45,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: contains arrays user objects related
    *                      description: contains arrays user objects related
    */
    */
-  router.get('/', loginRequiredStrictly, adminRequired, validator.list, async(req, res) => {
+  router.get('/', accessTokenParser[SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT], loginRequiredStrictly, adminRequired, validator.list, async(req, res) => {
     const { query } = req;
     const { query } = req;
 
 
     try {
     try {

+ 149 - 137
apps/app/src/server/routes/apiv3/users.js

@@ -1,4 +1,3 @@
-
 import path from 'path';
 import path from 'path';
 
 
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
@@ -11,6 +10,7 @@ import { isEmail } from 'validator';
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import { deleteUserAiAssistant } from '~/features/openai/server/services/delete-ai-assistant';
 import { deleteUserAiAssistant } from '~/features/openai/server/services/delete-ai-assistant';
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
+import { SCOPE } from '~/interfaces/scope';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import Activity from '~/server/models/activity';
 import Activity from '~/server/models/activity';
 import ExternalAccount from '~/server/models/external-account';
 import ExternalAccount from '~/server/models/external-account';
@@ -246,7 +246,7 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/PaginateResult'
    *                      $ref: '#/components/schemas/PaginateResult'
    */
    */
 
 
-  router.get('/', accessTokenParser(), loginRequired, validator.statusList, apiV3FormValidator, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.USER.INFO]), loginRequired, validator.statusList, apiV3FormValidator, async(req, res) => {
 
 
     const page = parseInt(req.query.page) || 1;
     const page = parseInt(req.query.page) || 1;
     // status
     // status
@@ -351,7 +351,7 @@ module.exports = (crowi) => {
    *                    paginateResult:
    *                    paginateResult:
    *                      $ref: '#/components/schemas/PaginateResult'
    *                      $ref: '#/components/schemas/PaginateResult'
    */
    */
-  router.get('/:id/recent', accessTokenParser(), loginRequired, validator.recentCreatedByUser, apiV3FormValidator, async(req, res) => {
+  router.get('/:id/recent', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, validator.recentCreatedByUser, apiV3FormValidator, async(req, res) => {
     const { id } = req.params;
     const { id } = req.params;
 
 
     let user;
     let user;
@@ -437,35 +437,37 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: Users email that failed to create or send email
    *                      description: Users email that failed to create or send email
    */
    */
-  router.post('/invite', loginRequiredStrictly, adminRequired, addActivity, validator.inviteEmail, apiV3FormValidator, async(req, res) => {
-
-    // Delete duplicate email addresses
-    const emailList = Array.from(new Set(req.body.shapedEmailList));
-    let failedEmailList = [];
-
-    // Create users
-    const createUser = await User.createUsersByEmailList(emailList);
-    if (createUser.failedToCreateUserEmailList.length > 0) {
-      failedEmailList = failedEmailList.concat(createUser.failedToCreateUserEmailList);
-    }
+  router.post('/invite', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity,
+    validator.inviteEmail, apiV3FormValidator,
+    async(req, res) => {
+
+      // Delete duplicate email addresses
+      const emailList = Array.from(new Set(req.body.shapedEmailList));
+      let failedEmailList = [];
+
+      // Create users
+      const createUser = await User.createUsersByEmailList(emailList);
+      if (createUser.failedToCreateUserEmailList.length > 0) {
+        failedEmailList = failedEmailList.concat(createUser.failedToCreateUserEmailList);
+      }
 
 
-    // Send email
-    if (req.body.sendEmail) {
-      const sendEmail = await sendEmailByUserList(createUser.createdUserList);
-      if (sendEmail.failedToSendEmailList.length > 0) {
-        failedEmailList = failedEmailList.concat(sendEmail.failedToSendEmailList);
+      // Send email
+      if (req.body.sendEmail) {
+        const sendEmail = await sendEmailByUserList(createUser.createdUserList);
+        if (sendEmail.failedToSendEmailList.length > 0) {
+          failedEmailList = failedEmailList.concat(sendEmail.failedToSendEmailList);
+        }
       }
       }
-    }
 
 
-    const parameters = { action: SupportedAction.ACTION_ADMIN_USERS_INVITE };
-    activityEvent.emit('update', res.locals.activity._id, parameters);
+      const parameters = { action: SupportedAction.ACTION_ADMIN_USERS_INVITE };
+      activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-    return res.apiv3({
-      createdUserList: createUser.createdUserList,
-      existingEmailList: createUser.existingEmailList,
-      failedEmailList,
-    }, 201);
-  });
+      return res.apiv3({
+        createdUserList: createUser.createdUserList,
+        existingEmailList: createUser.existingEmailList,
+        failedEmailList,
+      }, 201);
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -495,7 +497,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of admin user
    *                      description: data of admin user
    */
    */
-  router.put('/:id/grant-admin', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
+  router.put('/:id/grant-admin', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     const { id } = req.params;
     const { id } = req.params;
 
 
     try {
     try {
@@ -542,24 +544,26 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of revoked admin user
    *                      description: data of revoked admin user
    */
    */
-  router.put('/:id/revoke-admin', loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity, async(req, res) => {
-    const { id } = req.params;
+  router.put('/:id/revoke-admin', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]),
+    loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
 
 
-    try {
-      const userData = await User.findById(id);
-      await userData.revokeAdmin();
+      try {
+        const userData = await User.findById(id);
+        await userData.revokeAdmin();
 
 
-      const serializedUserData = serializeUserSecurely(userData);
+        const serializedUserData = serializeUserSecurely(userData);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REVOKE_ADMIN });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REVOKE_ADMIN });
 
 
-      return res.apiv3({ userData: serializedUserData });
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
+        return res.apiv3({ userData: serializedUserData });
+      }
+      catch (err) {
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(err));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -589,29 +593,30 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of read only
    *                      description: data of read only
    */
    */
-  router.put('/:id/grant-read-only', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const { id } = req.params;
+  router.put('/:id/grant-read-only', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
 
 
-    try {
-      const userData = await User.findById(id);
+      try {
+        const userData = await User.findById(id);
 
 
-      if (userData == null) {
-        return res.apiv3Err(new ErrorV3('User not found'), 404);
-      }
+        if (userData == null) {
+          return res.apiv3Err(new ErrorV3('User not found'), 404);
+        }
 
 
-      await userData.grantReadOnly();
+        await userData.grantReadOnly();
 
 
-      const serializedUserData = serializeUserSecurely(userData);
+        const serializedUserData = serializeUserSecurely(userData);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_GRANT_READ_ONLY });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_GRANT_READ_ONLY });
 
 
-      return res.apiv3({ userData: serializedUserData });
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
+        return res.apiv3({ userData: serializedUserData });
+      }
+      catch (err) {
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(err));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -641,29 +646,30 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of revoke read only
    *                      description: data of revoke read only
    */
    */
-  router.put('/:id/revoke-read-only', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const { id } = req.params;
+  router.put('/:id/revoke-read-only', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
 
 
-    try {
-      const userData = await User.findById(id);
+      try {
+        const userData = await User.findById(id);
 
 
-      if (userData == null) {
-        return res.apiv3Err(new ErrorV3('User not found'), 404);
-      }
+        if (userData == null) {
+          return res.apiv3Err(new ErrorV3('User not found'), 404);
+        }
 
 
-      await userData.revokeReadOnly();
+        await userData.revokeReadOnly();
 
 
-      const serializedUserData = serializeUserSecurely(userData);
+        const serializedUserData = serializeUserSecurely(userData);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REVOKE_READ_ONLY });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REVOKE_READ_ONLY });
 
 
-      return res.apiv3({ userData: serializedUserData });
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
+        return res.apiv3({ userData: serializedUserData });
+      }
+      catch (err) {
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(err));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -693,7 +699,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of activate user
    *                      description: data of activate user
    */
    */
-  router.put('/:id/activate', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
+  router.put('/:id/activate', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     // check user upper limit
     // check user upper limit
     const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
     const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
     if (isUserCountExceedsUpperLimit) {
     if (isUserCountExceedsUpperLimit) {
@@ -747,24 +753,26 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of deactivate user
    *                      description: data of deactivate user
    */
    */
-  router.put('/:id/deactivate', loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity, async(req, res) => {
-    const { id } = req.params;
+  router.put('/:id/deactivate', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]),
+    loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
 
 
-    try {
-      const userData = await User.findById(id);
-      await userData.statusSuspend();
+      try {
+        const userData = await User.findById(id);
+        await userData.statusSuspend();
 
 
-      const serializedUserData = serializeUserSecurely(userData);
+        const serializedUserData = serializeUserSecurely(userData);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_DEACTIVATE });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_DEACTIVATE });
 
 
-      return res.apiv3({ userData: serializedUserData });
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
+        return res.apiv3({ userData: serializedUserData });
+      }
+      catch (err) {
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(err));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -794,39 +802,41 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: data of deleted user
    *                      description: data of deleted user
    */
    */
-  router.delete('/:id/remove', loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity, async(req, res) => {
-    const { id } = req.params;
-    const isUsersHomepageDeletionEnabled = configManager.getConfig('security:user-homepage-deletion:isEnabled');
-    const isForceDeleteUserHomepageOnUserDeletion = configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion');
+  router.delete('/:id/remove', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]),
+    loginRequiredStrictly, adminRequired, certifyUserOperationOtherThenYourOwn, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
+      const isUsersHomepageDeletionEnabled = configManager.getConfig('security:user-homepage-deletion:isEnabled');
+      const isForceDeleteUserHomepageOnUserDeletion = configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion');
 
 
-    try {
-      const user = await User.findById(id);
-      // !! DO NOT MOVE homepagePath FROM THIS POSITION !! -- 05.31.2023
-      // catch username before delete user because username will be change to deleted_at_*
-      const homepagePath = userHomepagePath(user);
+      try {
+        const user = await User.findById(id);
+        // !! DO NOT MOVE homepagePath FROM THIS POSITION !! -- 05.31.2023
+        // catch username before delete user because username will be change to deleted_at_*
+        const homepagePath = userHomepagePath(user);
 
 
-      await UserGroupRelation.remove({ relatedUser: user });
-      await ExternalUserGroupRelation.remove({ relatedUser: user });
-      await user.statusDelete();
-      await ExternalAccount.remove({ user });
+        await UserGroupRelation.remove({ relatedUser: user });
+        await ExternalUserGroupRelation.remove({ relatedUser: user });
+        await user.statusDelete();
+        await ExternalAccount.remove({ user });
 
 
-      deleteUserAiAssistant(user);
+        deleteUserAiAssistant(user);
 
 
-      const serializedUser = serializeUserSecurely(user);
+        const serializedUser = serializeUserSecurely(user);
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REMOVE });
+        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_USERS_REMOVE });
 
 
-      if (isUsersHomepageDeletionEnabled && isForceDeleteUserHomepageOnUserDeletion) {
-        deleteCompletelyUserHomeBySystem(homepagePath, crowi.pageService);
-      }
+        if (isUsersHomepageDeletionEnabled && isForceDeleteUserHomepageOnUserDeletion) {
+          deleteCompletelyUserHomeBySystem(homepagePath, crowi.pageService);
+        }
 
 
-      return res.apiv3({ user: serializedUser });
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
+        return res.apiv3({ user: serializedUser });
+      }
+      catch (err) {
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(err));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -848,7 +858,7 @@ module.exports = (crowi) => {
    *                    paginateResult:
    *                    paginateResult:
    *                      $ref: '#/components/schemas/PaginateResult'
    *                      $ref: '#/components/schemas/PaginateResult'
    */
    */
-  router.get('/external-accounts/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/external-accounts/', accessTokenParser([SCOPE.READ.USER.EXTERNAL_ACCOUNT]), loginRequiredStrictly, adminRequired, async(req, res) => {
     const page = parseInt(req.query.page) || 1;
     const page = parseInt(req.query.page) || 1;
     try {
     try {
       const paginateResult = await ExternalAccount.findAllWithPagination({ page });
       const paginateResult = await ExternalAccount.findAllWithPagination({ page });
@@ -889,20 +899,22 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: A result of `ExtenralAccount.findByIdAndRemove`
    *                      description: A result of `ExtenralAccount.findByIdAndRemove`
    */
    */
-  router.delete('/external-accounts/:id/remove', loginRequiredStrictly, adminRequired, apiV3FormValidator, async(req, res) => {
-    const { id } = req.params;
+  router.delete('/external-accounts/:id/remove', accessTokenParser([SCOPE.WRITE.USER.EXTERNAL_ACCOUNT]),
+    loginRequiredStrictly, adminRequired, apiV3FormValidator,
+    async(req, res) => {
+      const { id } = req.params;
 
 
-    try {
-      const externalAccount = await ExternalAccount.findByIdAndRemove(id);
+      try {
+        const externalAccount = await ExternalAccount.findByIdAndRemove(id);
 
 
-      return res.apiv3({ externalAccount });
-    }
-    catch (err) {
-      const msg = 'Error occurred in deleting a external account  ';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg + err.message, 'extenral-account-delete-failed'));
-    }
-  });
+        return res.apiv3({ externalAccount });
+      }
+      catch (err) {
+        const msg = 'Error occurred in deleting a external account  ';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg + err.message, 'extenral-account-delete-failed'));
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -931,7 +943,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: users updated with imageUrlCached
    *                      description: users updated with imageUrlCached
    */
    */
-  router.put('/update.imageUrlCache', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.put('/update.imageUrlCache', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, async(req, res) => {
     try {
     try {
       const userIds = req.body.userIds;
       const userIds = req.body.userIds;
       const users = await User.find({ _id: { $in: userIds }, imageUrlCached: null });
       const users = await User.find({ _id: { $in: userIds }, imageUrlCached: null });
@@ -980,7 +992,7 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: success reset password
    *            description: success reset password
    */
    */
-  router.put('/reset-password', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
+  router.put('/reset-password', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     const { id } = req.body;
     const { id } = req.body;
 
 
     try {
     try {
@@ -1021,7 +1033,7 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: success send new password email
    *            description: success send new password email
    */
    */
-  router.put('/reset-password-email', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
+  router.put('/reset-password-email', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     const { id } = req.body;
     const { id } = req.body;
 
 
     try {
     try {
@@ -1073,7 +1085,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: email and reasons for email sending failure
    *                      description: email and reasons for email sending failure
    */
    */
-  router.put('/send-invitation-email', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
+  router.put('/send-invitation-email', accessTokenParser([SCOPE.WRITE.ADMIN.USER_MANAGEMENT]), loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     const { id } = req.body;
     const { id } = req.body;
 
 
     try {
     try {
@@ -1131,7 +1143,7 @@ module.exports = (crowi) => {
    *            500:
    *            500:
    *              $ref: '#/components/responses/500'
    *              $ref: '#/components/responses/500'
    */
    */
-  router.get('/list', accessTokenParser(), loginRequired, async(req, res) => {
+  router.get('/list', accessTokenParser([SCOPE.READ.USER.INFO]), loginRequired, async(req, res) => {
     const userIds = req.query.userIds ?? null;
     const userIds = req.query.userIds ?? null;
 
 
     let userFetcher;
     let userFetcher;
@@ -1160,7 +1172,7 @@ module.exports = (crowi) => {
     return res.apiv3(data);
     return res.apiv3(data);
   });
   });
 
 
-  router.get('/usernames', accessTokenParser(), loginRequired, validator.usernames, apiV3FormValidator, async(req, res) => {
+  router.get('/usernames', accessTokenParser([SCOPE.READ.USER.INFO]), loginRequired, validator.usernames, apiV3FormValidator, async(req, res) => {
     const q = req.query.q;
     const q = req.query.q;
     const offset = +req.query.offset || 0;
     const offset = +req.query.offset || 0;
     const limit = +req.query.limit || 10;
     const limit = +req.query.limit || 10;

+ 4 - 1
apps/app/src/server/routes/attachment/get.ts

@@ -8,7 +8,9 @@ import type {
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 import type { CrowiProperties, CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiProperties, CrowiRequest } from '~/interfaces/crowi-request';
+import { SCOPE } from '~/interfaces/scope';
 import { ResponseMode, type ExpressHttpHeader, type RespondOptions } from '~/server/interfaces/attachment';
 import { ResponseMode, type ExpressHttpHeader, type RespondOptions } from '~/server/interfaces/attachment';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import {
 import {
   type FileUploader,
   type FileUploader,
   toExpressHttpHeaders, ContentHeaders, applyHeaders,
   toExpressHttpHeaders, ContentHeaders, applyHeaders,
@@ -174,7 +176,8 @@ export const getRouterFactory = (crowi: Crowi): Router => {
 
 
   // note: retrieveAttachmentFromIdParam requires `req.params.id`
   // note: retrieveAttachmentFromIdParam requires `req.params.id`
   router.get<{ id: string }>('/:id([0-9a-z]{24})',
   router.get<{ id: string }>('/:id([0-9a-z]{24})',
-    certifySharedPageAttachmentMiddleware, loginRequired,
+    certifySharedPageAttachmentMiddleware,
+    loginRequired,
     retrieveAttachmentFromIdParam,
     retrieveAttachmentFromIdParam,
 
 
     (req: GetRequest, res: GetResponse) => {
     (req: GetRequest, res: GetResponse) => {

+ 36 - 34
apps/app/src/server/routes/index.js

@@ -2,6 +2,7 @@ import csrf from 'csurf';
 import express from 'express';
 import express from 'express';
 
 
 import { middlewareFactory as rateLimiterFactory } from '~/features/rate-limiter';
 import { middlewareFactory as rateLimiterFactory } from '~/features/rate-limiter';
+import { SCOPE } from '~/interfaces/scope';
 
 
 import { accessTokenParser } from '../middlewares/access-token-parser';
 import { accessTokenParser } from '../middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../middlewares/add-activity';
@@ -21,6 +22,7 @@ import * as forgotPassword from './forgot-password';
 import nextFactory from './next';
 import nextFactory from './next';
 import * as userActivation from './user-activation';
 import * as userActivation from './user-activation';
 
 
+
 const multer = require('multer');
 const multer = require('multer');
 const autoReap = require('multer-autoreap');
 const autoReap = require('multer-autoreap');
 
 
@@ -69,7 +71,7 @@ module.exports = function(crowi, app) {
 
 
   app.get('/_next/*'                  , next.delegateToNext);
   app.get('/_next/*'                  , next.delegateToNext);
 
 
-  app.get('/'                         , applicationInstalled, unavailableWhenMaintenanceMode, loginRequired, autoReconnectToSearch, next.delegateToNext);
+  app.get('/'                         , accessTokenParser([SCOPE.READ.BASE.PAGE]), applicationInstalled, unavailableWhenMaintenanceMode, loginRequired, autoReconnectToSearch, next.delegateToNext);
 
 
   app.get('/login/error/:reason'      , applicationInstalled, next.delegateToNext);
   app.get('/login/error/:reason'      , applicationInstalled, next.delegateToNext);
   app.get('/login'                    , applicationInstalled, login.preLogin, next.delegateToNext);
   app.get('/login'                    , applicationInstalled, login.preLogin, next.delegateToNext);
@@ -77,10 +79,10 @@ module.exports = function(crowi, app) {
   // app.post('/login'                   , applicationInstalled, loginFormValidator.loginRules(), loginFormValidator.loginValidation, csrfProtection,  addActivity, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
   // app.post('/login'                   , applicationInstalled, loginFormValidator.loginRules(), loginFormValidator.loginValidation, csrfProtection,  addActivity, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
 
 
   // NOTE: get method "/admin/export/:fileName" should be loaded before "/admin/*"
   // NOTE: get method "/admin/export/:fileName" should be loaded before "/admin/*"
-  app.get('/admin/export/:fileName'   , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
+  app.get('/admin/export/:fileName'   , accessTokenParser([SCOPE.READ.ADMIN.EXPORT_DATA]), loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
 
 
-  app.get('/admin/*'                  , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
-  app.get('/admin'                    , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
+  app.get('/admin/*'                  , accessTokenParser([SCOPE.READ.ADMIN.ALL]), applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
+  app.get('/admin'                    , accessTokenParser([SCOPE.READ.ADMIN.ALL]), applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
 
 
   // installer
   // installer
   app.get('/installer',
   app.get('/installer',
@@ -101,12 +103,12 @@ module.exports = function(crowi, app) {
   app.post('/_api/login/testLdap'    , loginRequiredStrictly , loginFormValidator.loginRules() , loginFormValidator.loginValidation , loginPassport.testLdapCredentials);
   app.post('/_api/login/testLdap'    , loginRequiredStrictly , loginFormValidator.loginRules() , loginFormValidator.loginValidation , loginPassport.testLdapCredentials);
 
 
   // importer management for admin
   // importer management for admin
-  app.post('/_api/admin/settings/importerEsa'   , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.importer.api.validators.importer.esa(),admin.api.importerSettingEsa);
-  app.post('/_api/admin/settings/importerQiita' , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.importer.api.validators.importer.qiita(), admin.api.importerSettingQiita);
-  app.post('/_api/admin/import/esa'             , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.importDataFromEsa);
-  app.post('/_api/admin/import/testEsaAPI'      , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.testEsaAPI);
-  app.post('/_api/admin/import/qiita'           , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.importDataFromQiita);
-  app.post('/_api/admin/import/testQiitaAPI'    , loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.testQiitaAPI);
+  app.post('/_api/admin/settings/importerEsa'   , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.importer.api.validators.importer.esa(),admin.api.importerSettingEsa);
+  app.post('/_api/admin/settings/importerQiita' , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.importer.api.validators.importer.qiita(), admin.api.importerSettingQiita);
+  app.post('/_api/admin/import/esa'             , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.importDataFromEsa);
+  app.post('/_api/admin/import/testEsaAPI'      , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.testEsaAPI);
+  app.post('/_api/admin/import/qiita'           , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.importDataFromQiita);
+  app.post('/_api/admin/import/testQiitaAPI'    , accessTokenParser([SCOPE.WRITE.ADMIN.IMPORT_DATA]), loginRequiredStrictly , adminRequired , csrfProtection, addActivity, admin.api.testQiitaAPI);
 
 
   // brand logo
   // brand logo
   app.use('/attachment', attachment.getBrandLogoRouterFactory(crowi));
   app.use('/attachment', attachment.getBrandLogoRouterFactory(crowi));
@@ -120,39 +122,39 @@ module.exports = function(crowi, app) {
 
 
   const apiV1Router = express.Router();
   const apiV1Router = express.Router();
 
 
-  apiV1Router.get('/search'                        , accessTokenParser() , loginRequired , search.api.search);
+  apiV1Router.get('/search'                        , accessTokenParser([SCOPE.READ.BASE.PAGE]) , loginRequired , search.api.search);
 
 
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
-  apiV1Router.get('/pages.updatePost'    , accessTokenParser(), loginRequired, page.api.getUpdatePost);
-  apiV1Router.get('/pages.getPageTag'    , accessTokenParser() , loginRequired , page.api.getPageTag);
+  apiV1Router.get('/pages.updatePost'    , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, page.api.getUpdatePost);
+  apiV1Router.get('/pages.getPageTag'    , accessTokenParser([SCOPE.READ.BASE.PAGE]) , loginRequired , page.api.getPageTag);
   // allow posting to guests because the client doesn't know whether the user logged in
   // allow posting to guests because the client doesn't know whether the user logged in
-  apiV1Router.post('/pages.remove'       , loginRequiredStrictly , excludeReadOnlyUser, page.validator.remove, apiV1FormValidator, page.api.remove); // (Avoid from API Token)
-  apiV1Router.post('/pages.revertRemove' , loginRequiredStrictly , excludeReadOnlyUser, page.validator.revertRemove, apiV1FormValidator, page.api.revertRemove); // (Avoid from API Token)
-  apiV1Router.post('/pages.unlink'       , loginRequiredStrictly , excludeReadOnlyUser, page.api.unlink); // (Avoid from API Token)
-  apiV1Router.get('/tags.list'           , accessTokenParser(), loginRequired, tag.api.list);
-  apiV1Router.get('/tags.search'         , accessTokenParser(), loginRequired, tag.api.search);
-  apiV1Router.post('/tags.update'        , accessTokenParser(), loginRequiredStrictly, excludeReadOnlyUser, addActivity, tag.api.update);
-  apiV1Router.get('/comments.get'        , accessTokenParser() , loginRequired , comment.api.get);
-  apiV1Router.post('/comments.add'       , comment.api.validators.add(), accessTokenParser() , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.add);
-  apiV1Router.post('/comments.update'    , comment.api.validators.add(), accessTokenParser() , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.update);
-  apiV1Router.post('/comments.remove'    , accessTokenParser() , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.remove);
-
-  apiV1Router.post('/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser(), loginRequiredStrictly , excludeReadOnlyUser, attachmentApi.uploadProfileImage);
-  apiV1Router.post('/attachments.remove'               , accessTokenParser() , loginRequiredStrictly , excludeReadOnlyUser, addActivity ,attachmentApi.remove);
-  apiV1Router.post('/attachments.removeProfileImage'   , accessTokenParser() , loginRequiredStrictly , excludeReadOnlyUser, attachmentApi.removeProfileImage);
+  apiV1Router.post('/pages.remove'       , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly , excludeReadOnlyUser, page.validator.remove, apiV1FormValidator, page.api.remove); // (Avoid from API Token)
+  apiV1Router.post('/pages.revertRemove' , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly , excludeReadOnlyUser, page.validator.revertRemove, apiV1FormValidator, page.api.revertRemove); // (Avoid from API Token)
+  apiV1Router.post('/pages.unlink'       , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly , excludeReadOnlyUser, page.api.unlink); // (Avoid from API Token)
+  apiV1Router.get('/tags.list'           , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, tag.api.list);
+  apiV1Router.get('/tags.search'         , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, tag.api.search);
+  apiV1Router.post('/tags.update'        , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly, excludeReadOnlyUser, addActivity, tag.api.update);
+  apiV1Router.get('/comments.get'        , accessTokenParser([SCOPE.READ.BASE.PAGE]) , loginRequired , comment.api.get);
+  apiV1Router.post('/comments.add'       , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), comment.api.validators.add(), loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.add);
+  apiV1Router.post('/comments.update'    , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), comment.api.validators.add(), loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.update);
+  apiV1Router.post('/comments.remove'    , accessTokenParser([SCOPE.WRITE.BASE.PAGE]), loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.remove);
+
+  apiV1Router.post('/attachments.uploadProfileImage'   , accessTokenParser([SCOPE.WRITE.BASE.ATTACHMENT]), uploads.single('file'), autoReap, loginRequiredStrictly , excludeReadOnlyUser, attachmentApi.uploadProfileImage);
+  apiV1Router.post('/attachments.remove'               , accessTokenParser([SCOPE.WRITE.BASE.ATTACHMENT]), loginRequiredStrictly , excludeReadOnlyUser, addActivity ,attachmentApi.remove);
+  apiV1Router.post('/attachments.removeProfileImage'   , accessTokenParser([SCOPE.WRITE.BASE.ATTACHMENT]), loginRequiredStrictly , excludeReadOnlyUser, attachmentApi.removeProfileImage);
 
 
   // API v1
   // API v1
   app.use('/_api', unavailableWhenMaintenanceModeForApi, apiV1Router);
   app.use('/_api', unavailableWhenMaintenanceModeForApi, apiV1Router);
 
 
   app.use(unavailableWhenMaintenanceMode);
   app.use(unavailableWhenMaintenanceMode);
 
 
-  app.get('/me'                                   , loginRequiredStrictly, next.delegateToNext);
-  app.get('/me/*'                                 , loginRequiredStrictly, next.delegateToNext);
+  app.get('/me'                                   , accessTokenParser([SCOPE.READ.USER.INFO]), loginRequiredStrictly, next.delegateToNext);
+  app.get('/me/*'                                 , accessTokenParser([SCOPE.READ.USER.INFO]), loginRequiredStrictly, next.delegateToNext);
 
 
-  app.use('/attachment', attachment.getRouterFactory(crowi));
-  app.use('/download', attachment.downloadRouterFactory(crowi));
+  app.use('/attachment', accessTokenParser([SCOPE.READ.BASE.ATTACHMENT]), attachment.getRouterFactory(crowi));
+  app.use('/download', accessTokenParser([SCOPE.READ.BASE.ATTACHMENT]), attachment.downloadRouterFactory(crowi));
 
 
-  app.get('/_search'                            , loginRequired, next.delegateToNext);
+  app.get('/_search'                            , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, next.delegateToNext);
 
 
   app.use('/forgot-password', express.Router()
   app.use('/forgot-password', express.Router()
     .use(forgotPassword.checkForgotPasswordEnabledMiddlewareFactory(crowi))
     .use(forgotPassword.checkForgotPasswordEnabledMiddlewareFactory(crowi))
@@ -171,7 +173,7 @@ module.exports = function(crowi, app) {
 
 
   app.use('/ogp', express.Router().get('/:pageId([0-9a-z]{0,})', loginRequired, ogp.pageIdRequired, ogp.ogpValidator, ogp.renderOgp));
   app.use('/ogp', express.Router().get('/:pageId([0-9a-z]{0,})', loginRequired, ogp.pageIdRequired, ogp.ogpValidator, ogp.renderOgp));
 
 
-  app.get('/*/$'                   , loginRequired, next.delegateToNext);
-  app.get('/*'                     , loginRequired, autoReconnectToSearch, next.delegateToNext);
+  app.get('/*/$'                   , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, next.delegateToNext);
+  app.get('/*'                     , accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, autoReconnectToSearch, next.delegateToNext);
 
 
 };
 };