Quellcode durchsuchen

feat: enhance access token parser with specific scopes for attachment and page management routes

reiji-h vor 1 Jahr
Ursprung
Commit
8f8cab5542

+ 3 - 2
apps/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -7,6 +7,7 @@ import express from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 import multer from 'multer';
 import multer from 'multer';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { isG2GTransferError } from '~/server/models/vo/g2g-transfer-error';
 import { isG2GTransferError } from '~/server/models/vo/g2g-transfer-error';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
@@ -269,7 +270,7 @@ module.exports = (crowi: Crowi): Router => {
   });
   });
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  receiveRouter.post('/generate-key', accessTokenParser(), adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
+  receiveRouter.post('/generate-key', accessTokenParser([SCOPE.WRITE.ADMIN.EXPORET_DATA]), adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
     const appSiteUrl = req.body.appSiteUrl ?? configManager.getConfig('app:siteUrl');
     const appSiteUrl = req.body.appSiteUrl ?? configManager.getConfig('app:siteUrl');
 
 
     let appSiteUrlOrigin: string;
     let appSiteUrlOrigin: string;
@@ -295,7 +296,7 @@ module.exports = (crowi: Crowi): Router => {
   });
   });
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  pushRouter.post('/transfer', accessTokenParser(), loginRequiredStrictly, adminRequired, validator.transfer, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  pushRouter.post('/transfer', accessTokenParser([SCOPE.WRITE.ADMIN.EXPORET_DATA]), loginRequiredStrictly, adminRequired, validator.transfer, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const { transferKey, collections, optionsMap } = req.body;
     const { transferKey, collections, optionsMap } = req.body;
 
 
     // Parse transfer key
     // Parse transfer key

+ 10 - 10
apps/app/src/server/routes/apiv3/notification-setting.js

@@ -176,7 +176,7 @@ const validator = {
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const Strictly = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
@@ -208,7 +208,7 @@ module.exports = (crowi) => {
    *                      description: notification params
    *                      description: notification params
    *                      $ref: '#/components/schemas/NotificationParams'
    *                      $ref: '#/components/schemas/NotificationParams'
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]), loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]), Strictly, adminRequired, async(req, res) => {
 
 
     const notificationParams = {
     const notificationParams = {
       // status of slack intagration
       // status of slack intagration
@@ -260,7 +260,7 @@ module.exports = (crowi) => {
   *                            description: user notification settings
   *                            description: user notification settings
   */
   */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.post('/user-notification', accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]), loginRequiredStrictly, adminRequired, addActivity, validator.userNotification, apiV3FormValidator, async(req, res) => {
+  router.post('/user-notification', accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]), Strictly, adminRequired, addActivity, validator.userNotification, apiV3FormValidator, async(req, res) => {
     const { pathPattern, channel } = req.body;
     const { pathPattern, channel } = req.body;
 
 
     try {
     try {
@@ -309,7 +309,7 @@ module.exports = (crowi) => {
    */
    */
   router.delete('/user-notification/:id',
   router.delete('/user-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     async(req, res) => {
     async(req, res) => {
@@ -359,7 +359,7 @@ module.exports = (crowi) => {
    */
    */
   router.get('/global-notification/:id',
   router.get('/global-notification/:id',
     accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     validator.globalNotification,
     validator.globalNotification,
     async(req, res) => {
     async(req, res) => {
@@ -409,7 +409,7 @@ module.exports = (crowi) => {
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.post('/global-notification',
   router.post('/global-notification',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.globalNotification,
     validator.globalNotification,
@@ -483,7 +483,7 @@ module.exports = (crowi) => {
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.put('/global-notification/:id',
   router.put('/global-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.globalNotification,
     validator.globalNotification,
@@ -568,7 +568,7 @@ module.exports = (crowi) => {
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.put('/notify-for-page-grant',
   router.put('/notify-for-page-grant',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.notifyForPageGrant,
     validator.notifyForPageGrant,
@@ -640,7 +640,7 @@ module.exports = (crowi) => {
    */
    */
   router.put('/global-notification/:id/enabled',
   router.put('/global-notification/:id/enabled',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     async(req, res) => {
     async(req, res) => {
@@ -700,7 +700,7 @@ module.exports = (crowi) => {
   */
   */
   router.delete('/global-notification/:id',
   router.delete('/global-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
-    loginRequiredStrictly,
+    Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     async(req, res) => {
     async(req, res) => {

+ 5 - 4
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -9,6 +9,7 @@ import { query, oneOf } from 'express-validator';
 import type { HydratedDocument } from 'mongoose';
 import type { HydratedDocument } from 'mongoose';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { SCOPE } from '~/interfaces/scope';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 import type { IPageGrantService } from '~/server/service/page-grant';
 import type { IPageGrantService } from '~/server/service/page-grant';
@@ -66,7 +67,7 @@ const routerFactory = (crowi: Crowi): Router => {
   const router = express.Router();
   const router = express.Router();
 
 
 
 
-  router.get('/root', accessTokenParser(), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  router.get('/root', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const Page = mongoose.model<IPage, PageModel>('Page');
     const Page = mongoose.model<IPage, PageModel>('Page');
 
 
     let rootPage;
     let rootPage;
@@ -81,7 +82,7 @@ const routerFactory = (crowi: Crowi): Router => {
   });
   });
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.get('/ancestors-children', accessTokenParser(), loginRequired, ...validator.pagePathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response): Promise<any> => {
+  router.get('/ancestors-children', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, ...validator.pagePathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response): Promise<any> => {
     const { path } = req.query;
     const { path } = req.query;
 
 
     const pageService = crowi.pageService;
     const pageService = crowi.pageService;
@@ -100,7 +101,7 @@ const routerFactory = (crowi: Crowi): Router => {
    * In most cases, using id should be prioritized
    * In most cases, using id should be prioritized
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.get('/children', accessTokenParser(), loginRequired, validator.pageIdOrPathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  router.get('/children', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, validator.pageIdOrPathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const { id, path } = req.query;
     const { id, path } = req.query;
 
 
     const pageService = crowi.pageService;
     const pageService = crowi.pageService;
@@ -121,7 +122,7 @@ const routerFactory = (crowi: Crowi): Router => {
   });
   });
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.get('/info', accessTokenParser(), loginRequired, validator.pageIdsOrPathRequired, validator.infoParams, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  router.get('/info', accessTokenParser([SCOPE.READ.BASE.PAGE]), loginRequired, validator.pageIdsOrPathRequired, validator.infoParams, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const {
     const {
       pageIds, path, attachBookmarkCount: attachBookmarkCountParam, attachShortBody: attachShortBodyParam,
       pageIds, path, attachBookmarkCount: attachBookmarkCountParam, attachShortBody: attachShortBodyParam,
     } = req.query;
     } = req.query;

+ 2 - 1
apps/app/src/server/routes/apiv3/page/get-page-paths-with-descendant-count.ts

@@ -4,6 +4,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 type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
@@ -56,7 +57,7 @@ export const getPagePathsWithDescendantCountFactory: GetPagePathsWithDescendantC
   ];
   ];
 
 
   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 {
       const {

+ 115 - 107
apps/app/src/server/routes/apiv3/personal-setting/index.js

@@ -5,6 +5,7 @@ import { body } from 'express-validator';
 import { i18n } from '^/config/next-i18next.config';
 import { i18n } from '^/config/next-i18next.config';
 
 
 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';
 
 
@@ -151,7 +152,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: personal params
    *                      description: personal params
    */
    */
-  router.get('/', accessTokenParser(), loginRequiredStrictly, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.USER.INFO]), loginRequiredStrictly, async(req, res) => {
     const { username } = req.user;
     const { username } = req.user;
     try {
     try {
       const user = await User.findUserByUsername(username);
       const user = await User.findUserByUsername(username);
@@ -189,7 +190,7 @@ module.exports = (crowi) => {
    *                    isPasswordSet:
    *                    isPasswordSet:
    *                      type: boolean
    *                      type: boolean
    */
    */
-  router.get('/is-password-set', accessTokenParser(), loginRequiredStrictly, async(req, res) => {
+  router.get('/is-password-set', accessTokenParser([SCOPE.READ.USER.PASSWORD]), loginRequiredStrictly, async(req, res) => {
     const { username } = req.user;
     const { username } = req.user;
 
 
     try {
     try {
@@ -231,7 +232,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: personal params
    *                      description: personal params
    */
    */
-  router.put('/', accessTokenParser(), loginRequiredStrictly, addActivity, validator.personal, apiV3FormValidator, async(req, res) => {
+  router.put('/', accessTokenParser([SCOPE.WRITE.USER.INFO]), loginRequiredStrictly, addActivity, validator.personal, apiV3FormValidator, async(req, res) => {
 
 
     try {
     try {
       const user = await User.findOne({ _id: req.user.id });
       const user = await User.findOne({ _id: req.user.id });
@@ -282,22 +283,24 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: user data
    *                      description: user data
    */
    */
-  router.put('/image-type', accessTokenParser(), loginRequiredStrictly, addActivity, validator.imageType, apiV3FormValidator, async(req, res) => {
-    const { isGravatarEnabled } = req.body;
+  router.put('/image-type', accessTokenParser([SCOPE.WRITE.USER.INFO]), loginRequiredStrictly, addActivity,
+    validator.imageType, apiV3FormValidator,
+    async(req, res) => {
+      const { isGravatarEnabled } = req.body;
 
 
-    try {
-      const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
+      try {
+        const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
 
 
-      const parameters = { action: SupportedAction.ACTION_USER_IMAGE_TYPE_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = { action: SupportedAction.ACTION_USER_IMAGE_TYPE_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-      return res.apiv3({ userData });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('update-personal-settings-failed');
-    }
-  });
+        return res.apiv3({ userData });
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err('update-personal-settings-failed');
+      }
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -319,7 +322,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: array of external accounts
    *                      description: array of external accounts
    */
    */
-  router.get('/external-accounts', accessTokenParser(), loginRequiredStrictly, async(req, res) => {
+  router.get('/external-accounts', accessTokenParser([SCOPE.READ.USER.EXTERNAL_ACCOUNT]), loginRequiredStrictly, async(req, res) => {
     const userData = req.user;
     const userData = req.user;
 
 
     try {
     try {
@@ -359,27 +362,28 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: user data updated
    *                      description: user data updated
    */
    */
-  router.put('/password', accessTokenParser(), loginRequiredStrictly, addActivity, validator.password, apiV3FormValidator, async(req, res) => {
-    const { body, user } = req;
-    const { oldPassword, newPassword } = body;
+  router.put('/password', accessTokenParser([SCOPE.WRITE.USER.PASSWORD]), loginRequiredStrictly, addActivity, validator.password, apiV3FormValidator,
+    async(req, res) => {
+      const { body, user } = req;
+      const { oldPassword, newPassword } = body;
 
 
-    if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) {
-      return res.apiv3Err('wrong-current-password', 400);
-    }
-    try {
-      const userData = await user.updatePassword(newPassword);
+      if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) {
+        return res.apiv3Err('wrong-current-password', 400);
+      }
+      try {
+        const userData = await user.updatePassword(newPassword);
 
 
-      const parameters = { action: SupportedAction.ACTION_USER_PASSWORD_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = { action: SupportedAction.ACTION_USER_PASSWORD_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-      return res.apiv3({ userData });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('update-password-failed');
-    }
+        return res.apiv3({ userData });
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err('update-password-failed');
+      }
 
 
-  });
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -401,7 +405,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: user data
    *                      description: user data
    */
    */
-  router.put('/api-token', loginRequiredStrictly, addActivity, async(req, res) => {
+  router.put('/api-token', accessTokenParser([SCOPE.WRITE.USER.API.API_TOKEN]), loginRequiredStrictly, addActivity, async(req, res) => {
     const { user } = req;
     const { user } = req;
 
 
     try {
     try {
@@ -438,7 +442,7 @@ module.exports = (crowi) => {
    *                   type: objet
    *                   type: objet
    *                   description: array of access tokens
    *                   description: array of access tokens
    */
    */
-  router.get('/access-token', getAccessTokenHandlerFactory(crowi));
+  router.get('/access-token', accessTokenParser([SCOPE.READ.USER.API.ACCESS_TOKEN]), getAccessTokenHandlerFactory(crowi));
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -471,7 +475,7 @@ module.exports = (crowi) => {
    *                     type: string[]
    *                     type: string[]
    *                     description: scope of access token
    *                     description: scope of access token
    */
    */
-  router.post('/access-token', generateAccessTokenHandlerFactory(crowi));
+  router.post('/access-token', accessTokenParser([SCOPE.WRITE.USER.API.ACCESS_TOKEN]), generateAccessTokenHandlerFactory(crowi));
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -486,7 +490,7 @@ module.exports = (crowi) => {
    *         description: succeded to delete access token
    *         description: succeded to delete access token
    *
    *
    */
    */
-  router.delete('/access-token', deleteAccessTokenHandlersFactory(crowi));
+  router.delete('/access-token', accessTokenParser([SCOPE.WRITE.USER.API.ACCESS_TOKEN]), deleteAccessTokenHandlersFactory(crowi));
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -500,7 +504,7 @@ module.exports = (crowi) => {
    *         200:
    *         200:
    *           description: succeded to delete all access tokens
    *           description: succeded to delete all access tokens
    */
    */
-  router.delete('/access-token/all', deleteAllAccessTokensHandlersFactory(crowi));
+  router.delete('/access-token/all', accessTokenParser([SCOPE.WRITE.USER.API.ACCESS_TOKEN]), deleteAllAccessTokensHandlersFactory(crowi));
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -528,31 +532,33 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: Ldap account associate to me
    *                      description: Ldap account associate to me
    */
    */
-  router.put('/associate-ldap', accessTokenParser(), loginRequiredStrictly, addActivity, validator.associateLdap, apiV3FormValidator, async(req, res) => {
-    const { passportService } = crowi;
-    const { user, body } = req;
-    const { username } = body;
-
-    if (!passportService.isLdapStrategySetup) {
-      logger.error('LdapStrategy has not been set up');
-      return res.apiv3Err('associate-ldap-account-failed', 405);
-    }
+  router.put('/associate-ldap', accessTokenParser([SCOPE.WRITE.USER.EXTERNAL_ACCOUNT]), loginRequiredStrictly, addActivity,
+    validator.associateLdap, apiV3FormValidator,
+    async(req, res) => {
+      const { passportService } = crowi;
+      const { user, body } = req;
+      const { username } = body;
+
+      if (!passportService.isLdapStrategySetup) {
+        logger.error('LdapStrategy has not been set up');
+        return res.apiv3Err('associate-ldap-account-failed', 405);
+      }
 
 
-    try {
-      await passport.authenticate('ldapauth');
-      const associateUser = await ExternalAccount.associate('ldap', username, user);
+      try {
+        await passport.authenticate('ldapauth');
+        const associateUser = await ExternalAccount.associate('ldap', username, user);
 
 
-      const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_ASSOCIATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_ASSOCIATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-      return res.apiv3({ associateUser });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('associate-ldap-account-failed');
-    }
+        return res.apiv3({ associateUser });
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err('associate-ldap-account-failed');
+      }
 
 
-  });
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -581,29 +587,30 @@ module.exports = (crowi) => {
    *                      description: Ldap account disassociate to me
    *                      description: Ldap account disassociate to me
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/disassociate-ldap', accessTokenParser(), loginRequiredStrictly, addActivity, validator.disassociateLdap, apiV3FormValidator, async(req, res) => {
-    const { user, body } = req;
-    const { providerType, accountId } = body;
-
-    try {
-      const count = await ExternalAccount.count({ user });
-      // make sure password set or this user has two or more ExternalAccounts
-      if (user.password == null && count <= 1) {
+  router.put('/disassociate-ldap', accessTokenParser([SCOPE.WRITE.USER.EXTERNAL_ACCOUNT]), loginRequiredStrictly, addActivity, validator.disassociateLdap, apiV3FormValidator,
+    async(req, res) => {
+      const { user, body } = req;
+      const { providerType, accountId } = body;
+
+      try {
+        const count = await ExternalAccount.count({ user });
+        // make sure password set or this user has two or more ExternalAccounts
+        if (user.password == null && count <= 1) {
+          return res.apiv3Err('disassociate-ldap-account-failed');
+        }
+        const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
+
+        const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_DISCONNECT };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3({ disassociateUser });
+      }
+      catch (err) {
+        logger.error(err);
         return res.apiv3Err('disassociate-ldap-account-failed');
         return res.apiv3Err('disassociate-ldap-account-failed');
       }
       }
-      const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
-
-      const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_DISCONNECT };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-
-      return res.apiv3({ disassociateUser });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('disassociate-ldap-account-failed');
-    }
 
 
-  });
+    });
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -625,34 +632,35 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: editor settings
    *                      description: editor settings
    */
    */
-  router.put('/editor-settings', accessTokenParser(), loginRequiredStrictly, addActivity, validator.editorSettings, apiV3FormValidator, async(req, res) => {
-    const query = { userId: req.user.id };
-    const { body } = req;
+  router.put('/editor-settings', accessTokenParser([SCOPE.WRITE.USER.OTHER]), loginRequiredStrictly, addActivity, validator.editorSettings, apiV3FormValidator,
+    async(req, res) => {
+      const query = { userId: req.user.id };
+      const { body } = req;
 
 
-    const {
-      theme, keymapMode, styleActiveLine, autoFormatMarkdownTable,
-    } = body;
+      const {
+        theme, keymapMode, styleActiveLine, autoFormatMarkdownTable,
+      } = body;
 
 
-    const document = {
-      theme, keymapMode, styleActiveLine, autoFormatMarkdownTable,
-    };
+      const document = {
+        theme, keymapMode, styleActiveLine, autoFormatMarkdownTable,
+      };
 
 
-    // Insert if document does not exist, and return new values
-    // See: https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
-    const options = { upsert: true, new: true };
-    try {
-      const response = await EditorSettings.findOneAndUpdate(query, { $set: document }, options);
+      // Insert if document does not exist, and return new values
+      // See: https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
+      const options = { upsert: true, new: true };
+      try {
+        const response = await EditorSettings.findOneAndUpdate(query, { $set: document }, options);
 
 
-      const parameters = { action: SupportedAction.ACTION_USER_EDITOR_SETTINGS_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = { action: SupportedAction.ACTION_USER_EDITOR_SETTINGS_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-      return res.apiv3(response);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('updating-editor-settings-failed');
-    }
-  });
+        return res.apiv3(response);
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err('updating-editor-settings-failed');
+      }
+    });
 
 
 
 
   /**
   /**
@@ -675,7 +683,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: editor settings
    *                      description: editor settings
    */
    */
-  router.get('/editor-settings', accessTokenParser(), loginRequiredStrictly, async(req, res) => {
+  router.get('/editor-settings', accessTokenParser([SCOPE.READ.USER.OTHER]), loginRequiredStrictly, async(req, res) => {
     try {
     try {
       const query = { userId: req.user.id };
       const query = { userId: req.user.id };
       const editorSettings = await EditorSettings.findOne(query) ?? new EditorSettings();
       const editorSettings = await EditorSettings.findOne(query) ?? new EditorSettings();
@@ -708,7 +716,7 @@ module.exports = (crowi) => {
    *                      description: in-app-notification-settings
    *                      description: in-app-notification-settings
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/in-app-notification-settings', accessTokenParser(), loginRequiredStrictly, addActivity, validator.inAppNotificationSettings, apiV3FormValidator, async(req, res) => {
+  router.put('/in-app-notification-settings', accessTokenParser([SCOPE.WRITE.USER.IN_APP_NOTIFICATION]), loginRequiredStrictly, addActivity, validator.inAppNotificationSettings, apiV3FormValidator, async(req, res) => {
     const query = { userId: req.user.id };
     const query = { userId: req.user.id };
     const subscribeRules = req.body.subscribeRules;
     const subscribeRules = req.body.subscribeRules;
 
 
@@ -751,7 +759,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: InAppNotificationSettings
    *                      description: InAppNotificationSettings
    */
    */
-  router.get('/in-app-notification-settings', accessTokenParser(), loginRequiredStrictly, async(req, res) => {
+  router.get('/in-app-notification-settings', accessTokenParser([SCOPE.READ.USER.IN_APP_NOTIFICATION]), loginRequiredStrictly, async(req, res) => {
     const query = { userId: req.user.id };
     const query = { userId: req.user.id };
     try {
     try {
       const response = await InAppNotificationSettings.findOne(query);
       const response = await InAppNotificationSettings.findOne(query);
@@ -764,7 +772,7 @@ module.exports = (crowi) => {
   });
   });
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/questionnaire-settings', accessTokenParser(), loginRequiredStrictly, validator.questionnaireSettings, apiV3FormValidator, async(req, res) => {
+  router.put('/questionnaire-settings', accessTokenParser([SCOPE.WRITE.BASE.QUESTIONNAIRE]), loginRequiredStrictly, validator.questionnaireSettings, apiV3FormValidator, async(req, res) => {
     const { isQuestionnaireEnabled } = req.body;
     const { isQuestionnaireEnabled } = req.body;
     const { user } = req;
     const { user } = req;
     try {
     try {

+ 3 - 1
apps/app/src/server/routes/attachment/get-brand-logo.ts

@@ -11,6 +11,8 @@ import { AttachmentType } from '../../interfaces/attachment';
 import { generateCertifyBrandLogoMiddleware } from '../../middlewares/certify-brand-logo';
 import { generateCertifyBrandLogoMiddleware } from '../../middlewares/certify-brand-logo';
 import { Attachment } from '../../models/attachment';
 import { Attachment } from '../../models/attachment';
 import ApiResponse from '../../util/apiResponse';
 import ApiResponse from '../../util/apiResponse';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
+import {SCOPE} from '~/interfaces/scope';
 
 
 import { getActionFactory } from './get';
 import { getActionFactory } from './get';
 
 
@@ -25,7 +27,7 @@ export const getBrandLogoRouterFactory = (crowi: Crowi): Router => {
 
 
   const router = express.Router();
   const router = express.Router();
 
 
-  router.get('/brand-logo', certifyBrandLogo, loginRequired, async(req: CrowiRequest, res: Response) => {
+  router.get('/brand-logo', certifyBrandLogo, accessTokenParser([SCOPE.READ.BASE.ATTACHMENT]), loginRequired, async(req: CrowiRequest, res: Response) => {
     const brandLogoAttachment = await Attachment.findOne({ attachmentType: AttachmentType.BRAND_LOGO });
     const brandLogoAttachment = await Attachment.findOne({ attachmentType: AttachmentType.BRAND_LOGO });
 
 
     if (brandLogoAttachment == null) {
     if (brandLogoAttachment == null) {

+ 0 - 2
apps/app/src/server/routes/attachment/get.ts

@@ -8,9 +8,7 @@ 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,