Explorar o código

Merge branch 'feat/enhanced-access-token' into fix/167097-codeql-problems

Shun Miyazawa hai 10 meses
pai
achega
656beaacb3
Modificáronse 100 ficheiros con 311 adicións e 233 borrados
  1. 31 1
      CHANGELOG.md
  2. 1 0
      apps/app/config/logger/config.dev.js
  3. 1 1
      apps/app/package.json
  4. 4 2
      apps/app/public/static/locales/en_US/commons.json
  5. 4 2
      apps/app/public/static/locales/fr_FR/commons.json
  6. 4 2
      apps/app/public/static/locales/ja_JP/commons.json
  7. 4 2
      apps/app/public/static/locales/zh_CN/commons.json
  8. 2 2
      apps/app/src/client/components/Admin/App/AppSetting.jsx
  9. 4 4
      apps/app/src/client/components/Admin/App/AwsSetting.tsx
  10. 8 8
      apps/app/src/client/components/Admin/App/AzureSetting.tsx
  11. 3 3
      apps/app/src/client/components/Admin/App/GcsSetting.tsx
  12. 1 1
      apps/app/src/client/components/Admin/App/MailSetting.tsx
  13. 3 3
      apps/app/src/client/components/Admin/App/MaskedInput.tsx
  14. 2 2
      apps/app/src/client/components/Admin/App/SesSetting.tsx
  15. 1 1
      apps/app/src/client/components/Admin/App/SiteUrlSetting.tsx
  16. 4 4
      apps/app/src/client/components/Admin/App/SmtpSetting.tsx
  17. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx
  18. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  19. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx
  20. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeTitle.tsx
  21. 2 2
      apps/app/src/client/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx
  22. 2 2
      apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx
  23. 1 1
      apps/app/src/client/components/Admin/Security/GitHubSecuritySettingContents.jsx
  24. 3 3
      apps/app/src/client/components/Admin/Security/GoogleSecuritySettingContents.jsx
  25. 10 10
      apps/app/src/client/components/Admin/Security/LdapSecuritySettingContents.jsx
  26. 1 1
      apps/app/src/client/components/Admin/Security/LocalSecuritySettingContents.jsx
  27. 16 16
      apps/app/src/client/components/Admin/Security/OidcSecuritySettingContents.jsx
  28. 9 9
      apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx
  29. 1 1
      apps/app/src/client/components/Admin/Security/SecuritySetting.jsx
  30. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  31. 2 2
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  32. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx
  33. 1 1
      apps/app/src/client/components/Me/AccessTokenForm.tsx
  34. 1 1
      apps/app/src/client/components/Me/AccessTokenScopeList.tsx
  35. 2 3
      apps/app/src/client/components/Me/AccessTokenScopeSelect.tsx
  36. 12 4
      apps/app/src/client/components/Me/ApiSettings.tsx
  37. 0 1
      apps/app/src/client/components/Sidebar/InAppNotification/PrimaryItemForNotification.tsx
  38. 15 4
      apps/app/src/client/components/Sidebar/SidebarContents.tsx
  39. 3 2
      apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx
  40. 4 2
      apps/app/src/client/services/page-operation.ts
  41. 21 17
      apps/app/src/client/util/scope-util.test.ts
  42. 2 2
      apps/app/src/client/util/scope-util.ts
  43. 1 1
      apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group-relation.ts
  44. 1 1
      apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts
  45. 1 1
      apps/app/src/features/growi-plugin/server/routes/apiv3/admin/index.ts
  46. 11 6
      apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx
  47. 1 1
      apps/app/src/features/openai/server/routes/ai-assistant.ts
  48. 1 1
      apps/app/src/features/openai/server/routes/ai-assistants.ts
  49. 1 1
      apps/app/src/features/openai/server/routes/delete-ai-assistant.ts
  50. 1 1
      apps/app/src/features/openai/server/routes/delete-thread.ts
  51. 1 1
      apps/app/src/features/openai/server/routes/edit/index.ts
  52. 1 1
      apps/app/src/features/openai/server/routes/get-threads.ts
  53. 1 1
      apps/app/src/features/openai/server/routes/message/get-messages.ts
  54. 1 1
      apps/app/src/features/openai/server/routes/message/post-message.ts
  55. 1 1
      apps/app/src/features/openai/server/routes/set-default-ai-assistant.ts
  56. 1 1
      apps/app/src/features/openai/server/routes/thread.ts
  57. 1 1
      apps/app/src/features/openai/server/routes/update-ai-assistant.ts
  58. 26 24
      apps/app/src/features/page-bulk-export/server/routes/apiv3/page-bulk-export.ts
  59. 1 1
      apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts
  60. 1 1
      apps/app/src/features/templates/server/routes/apiv3/index.ts
  61. 1 1
      apps/app/src/interfaces/access-token.ts
  62. 1 1
      apps/app/src/server/crowi/index.js
  63. 1 1
      apps/app/src/server/middlewares/access-token-parser/access-token.integ.ts
  64. 6 2
      apps/app/src/server/middlewares/access-token-parser/access-token.ts
  65. 6 4
      apps/app/src/server/middlewares/access-token-parser/index.ts
  66. 1 2
      apps/app/src/server/models/access-token.ts
  67. 1 1
      apps/app/src/server/routes/apiv3/activity.ts
  68. 1 1
      apps/app/src/server/routes/apiv3/admin-home.ts
  69. 1 2
      apps/app/src/server/routes/apiv3/app-settings.js
  70. 1 1
      apps/app/src/server/routes/apiv3/attachment.js
  71. 1 1
      apps/app/src/server/routes/apiv3/bookmark-folder.ts
  72. 1 1
      apps/app/src/server/routes/apiv3/bookmarks.js
  73. 1 1
      apps/app/src/server/routes/apiv3/customize-setting.js
  74. 1 1
      apps/app/src/server/routes/apiv3/export.js
  75. 1 1
      apps/app/src/server/routes/apiv3/g2g-transfer.ts
  76. 1 1
      apps/app/src/server/routes/apiv3/import.js
  77. 1 1
      apps/app/src/server/routes/apiv3/in-app-notification.ts
  78. 1 1
      apps/app/src/server/routes/apiv3/notification-setting.js
  79. 1 1
      apps/app/src/server/routes/apiv3/page-listing.ts
  80. 1 1
      apps/app/src/server/routes/apiv3/page/check-page-existence.ts
  81. 1 1
      apps/app/src/server/routes/apiv3/page/create-page.ts
  82. 1 1
      apps/app/src/server/routes/apiv3/page/get-page-paths-with-descendant-count.ts
  83. 1 1
      apps/app/src/server/routes/apiv3/page/get-yjs-data.ts
  84. 1 1
      apps/app/src/server/routes/apiv3/page/index.ts
  85. 1 1
      apps/app/src/server/routes/apiv3/page/publish-page.ts
  86. 1 1
      apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts
  87. 1 1
      apps/app/src/server/routes/apiv3/page/unpublish-page.ts
  88. 1 1
      apps/app/src/server/routes/apiv3/page/update-page.ts
  89. 1 1
      apps/app/src/server/routes/apiv3/pages/index.js
  90. 3 1
      apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts
  91. 3 1
      apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts
  92. 3 2
      apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts
  93. 3 1
      apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts
  94. 1 1
      apps/app/src/server/routes/apiv3/personal-setting/index.js
  95. 1 1
      apps/app/src/server/routes/apiv3/revisions.js
  96. 1 1
      apps/app/src/server/routes/apiv3/search.js
  97. 10 12
      apps/app/src/server/routes/apiv3/security-settings/index.js
  98. 1 1
      apps/app/src/server/routes/apiv3/share-links.js
  99. 1 1
      apps/app/src/server/routes/apiv3/slack-integration-legacy-settings.js
  100. 1 2
      apps/app/src/server/routes/apiv3/slack-integration-settings.js

+ 31 - 1
CHANGELOG.md

@@ -1,9 +1,39 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.2.5...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.2.6...HEAD)
 
 
 *Please do not manually update this file. We've automated the process.*
 *Please do not manually update this file. We've automated the process.*
 
 
+## [v7.2.6](https://github.com/weseek/growi/compare/v7.2.5...v7.2.6) - 2025-06-10
+
+### 💎 Features
+
+* feat(ai): Display spinner while creating diff (#9991) @miya
+
+### 🚀 Improvement
+
+* imprv: Message card markdown header size (#10038) @miya
+* imprv: Type safe configuration for file uploading (#10032) @yuki-takei
+* imprv: EditorAssistant instruction (#10030) @miya
+* imprv: Add NonEmptyString type (#10031) @yuki-takei
+* imprv: Security settings search results redesign (#9992) @arvid-e
+* imprv: OpenAPI spec properties ref (#10023) @yuki-takei
+* imprv(ai): Make input form position sticky (#10002) @miya
+* imprv: Prevent path traversal attack in pdf converter (#9993) @arafubeatbox
+* imprv: Discard when form is submitted without Accept/Discard after showing diff (#9980) @miya
+
+### 🐛 Bug Fixes
+
+* imprv:  The delete button on the user home page is now hidden for unauthorized users. (#9915) @taikou-m
+* fix: OpenAI threads can be retrieved regardless of assistant's public permissions (#9994) @miya
+* fix: Editor assistant button is being displayed when ai functionality is not enabled (#9985) @miya
+* fix: Improve attribute handling in Lsx (#9989) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: OpenAPI operationId generation (#10009) @yuki-takei
+* support: Configure biome for remark-growi-directive (#9999) @arafubeatbox
+
 ## [v7.2.5](https://github.com/weseek/growi/compare/v7.2.4...v7.2.5) - 2025-05-28
 ## [v7.2.5](https://github.com/weseek/growi/compare/v7.2.4...v7.2.5) - 2025-05-28
 
 
 ### 💎 Features
 ### 💎 Features

+ 1 - 0
apps/app/config/logger/config.dev.js

@@ -44,4 +44,5 @@ module.exports = {
   // 'growi:cli:ItemsTree': 'debug',
   // 'growi:cli:ItemsTree': 'debug',
   'growi:searchResultList': 'debug',
   'growi:searchResultList': 'debug',
   'growi:service:openai': 'debug',
   'growi:service:openai': 'debug',
+  'growi:middleware:access-token-parser:access-token': 'debug',
 };
 };

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "7.2.6-RC.0",
+  "version": "7.2.7-RC.0",
   "license": "MIT",
   "license": "MIT",
   "private": "true",
   "private": "true",
   "scripts": {
   "scripts": {

+ 4 - 2
apps/app/public/static/locales/en_US/commons.json

@@ -203,7 +203,8 @@
         "share_link": "Grants permission to view share link features.",
         "share_link": "Grants permission to view share link features.",
         "bookmark": "Grants permission to view bookmark features.",
         "bookmark": "Grants permission to view bookmark features.",
         "questionnaire": "Grants permission to view questionnaire features.",
         "questionnaire": "Grants permission to view questionnaire features.",
-        "attachment": "Grants permission to view attachment features."
+        "attachment": "Grants permission to view attachment features.",
+        "page_bulk_export": "Grants permission to view page bulk export features."
       }
       }
     },
     },
     "write": {
     "write": {
@@ -248,7 +249,8 @@
         "share_link": "Grants permission to edit share link features.",
         "share_link": "Grants permission to edit share link features.",
         "bookmark": "Grants permission to edit bookmark features.",
         "bookmark": "Grants permission to edit bookmark features.",
         "questionnaire": "Grants permission to edit questionnaire features.",
         "questionnaire": "Grants permission to edit questionnaire features.",
-        "attachment": "Grants permission to edit attachment features."
+        "attachment": "Grants permission to edit attachment features.",
+        "page_bulk_export": "Grants permission to edit page bulk export features."
       }
       }
     }
     }
   }
   }

+ 4 - 2
apps/app/public/static/locales/fr_FR/commons.json

@@ -189,7 +189,8 @@
         "share_link": "Accorde la permission de voir les fonctionnalités de lien de partage.",
         "share_link": "Accorde la permission de voir les fonctionnalités de lien de partage.",
         "bookmark": "Accorde la permission de voir les fonctionnalités de signet.",
         "bookmark": "Accorde la permission de voir les fonctionnalités de signet.",
         "questionnaire": "Accorde la permission de voir les fonctionnalités de questionnaire.",
         "questionnaire": "Accorde la permission de voir les fonctionnalités de questionnaire.",
-        "attachment": "Accorde la permission de voir les fonctionnalités de pièce jointe."
+        "attachment": "Accorde la permission de voir les fonctionnalités de pièce jointe.",
+        "page_bulk_export": "Accorde la permission de voir les fonctionnalités d'exportation en masse de pages."
       }
       }
     },
     },
     "write": {
     "write": {
@@ -234,7 +235,8 @@
         "share_link": "Accorde la permission de modifier les fonctionnalités de lien de partage.",
         "share_link": "Accorde la permission de modifier les fonctionnalités de lien de partage.",
         "bookmark": "Accorde la permission de modifier les fonctionnalités de signet.",
         "bookmark": "Accorde la permission de modifier les fonctionnalités de signet.",
         "questionnaire": "Accorde la permission de modifier les fonctionnalités de questionnaire.",
         "questionnaire": "Accorde la permission de modifier les fonctionnalités de questionnaire.",
-        "attachment": "Accorde la permission de modifier les fonctionnalités de pièce jointe."
+        "attachment": "Accorde la permission de modifier les fonctionnalités de pièce jointe.",
+        "page_bulk_export": "Accorde la permission de modifier les fonctionnalités d'exportation en masse de pages."
       }
       }
     }
     }
   }
   }

+ 4 - 2
apps/app/public/static/locales/ja_JP/commons.json

@@ -206,7 +206,8 @@
         "share_link": "共有リンク機能の閲覧権限を付与できます。",
         "share_link": "共有リンク機能の閲覧権限を付与できます。",
         "bookmark": "ブックマーク機能の閲覧権限を付与できます。",
         "bookmark": "ブックマーク機能の閲覧権限を付与できます。",
         "questionnaire": "アンケート機能の閲覧権限を付与できます。",
         "questionnaire": "アンケート機能の閲覧権限を付与できます。",
-        "attachment": "添付ファイル機能の閲覧権限を付与できます。"
+        "attachment": "添付ファイル機能の閲覧権限を付与できます。",
+        "page_bulk_export": "ページの一括エクスポート機能の閲覧権限を付与できます。"
       }
       }
     },
     },
     "write": {
     "write": {
@@ -251,7 +252,8 @@
         "share_link": "共有リンク機能の編集権限を付与できます。",
         "share_link": "共有リンク機能の編集権限を付与できます。",
         "bookmark": "ブックマーク機能の編集権限を付与できます。",
         "bookmark": "ブックマーク機能の編集権限を付与できます。",
         "questionnaire": "アンケート機能の編集権限を付与できます。",
         "questionnaire": "アンケート機能の編集権限を付与できます。",
-        "attachment": "添付ファイル機能の編集権限を付与できます。"
+        "attachment": "添付ファイル機能の編集権限を付与できます。",
+        "page_bulk_export": "ページの一括エクスポート機能の編集権限を付与できます。"
       }
       }
     }
     }
   }
   }

+ 4 - 2
apps/app/public/static/locales/zh_CN/commons.json

@@ -206,7 +206,8 @@
         "share_link": "授予查看共享链接功能的权限。",
         "share_link": "授予查看共享链接功能的权限。",
         "bookmark": "授予查看书签功能的权限。",
         "bookmark": "授予查看书签功能的权限。",
         "questionnaire": "授予查看问卷功能的权限。",
         "questionnaire": "授予查看问卷功能的权限。",
-        "attachment": "授予查看附件功能的权限。"
+        "attachment": "授予查看附件功能的权限。",
+        "page_bulk_export": "授予查看页面批量导出功能的权限。"
       }
       }
     },
     },
     "write": {
     "write": {
@@ -251,7 +252,8 @@
         "share_link": "授予编辑共享链接功能的权限。",
         "share_link": "授予编辑共享链接功能的权限。",
         "bookmark": "授予编辑书签功能的权限。",
         "bookmark": "授予编辑书签功能的权限。",
         "questionnaire": "授予编辑问卷功能的权限。",
         "questionnaire": "授予编辑问卷功能的权限。",
-        "attachment": "授予编辑附件功能的权限。"
+        "attachment": "授予编辑附件功能的权限。",
+        "page_bulk_export": "授予编辑页面批量导出功能的权限。"
       }
       }
     }
     }
   }
   }

+ 2 - 2
apps/app/src/client/components/Admin/App/AppSetting.jsx

@@ -40,7 +40,7 @@ const AppSetting = (props) => {
           <input
           <input
             className="form-control"
             className="form-control"
             type="text"
             type="text"
-            defaletValue={adminAppContainer.state.title || ''}
+            value={adminAppContainer.state.title || ''}
             onChange={(e) => {
             onChange={(e) => {
               adminAppContainer.changeTitle(e.target.value);
               adminAppContainer.changeTitle(e.target.value);
             }}
             }}
@@ -60,7 +60,7 @@ const AppSetting = (props) => {
           <input
           <input
             className="form-control"
             className="form-control"
             type="text"
             type="text"
-            defaultValue={adminAppContainer.state.confidential || ''}
+            value={adminAppContainer.state.confidential || ''}
             onChange={(e) => {
             onChange={(e) => {
               adminAppContainer.changeConfidential(e.target.value);
               adminAppContainer.changeConfidential(e.target.value);
             }}
             }}

+ 4 - 4
apps/app/src/client/components/Admin/App/AwsSetting.tsx

@@ -76,7 +76,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
           <input
           <input
             className="form-control"
             className="form-control"
             placeholder={`${t('eg')} ap-northeast-1`}
             placeholder={`${t('eg')} ap-northeast-1`}
-            defaultValue={props.s3Region || ''}
+            value={props.s3Region || ''}
             onChange={(e) => {
             onChange={(e) => {
               props?.onChangeS3Region(e.target.value);
               props?.onChangeS3Region(e.target.value);
             }}
             }}
@@ -93,7 +93,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
             className="form-control"
             className="form-control"
             type="text"
             type="text"
             placeholder={`${t('eg')} http://localhost:9000`}
             placeholder={`${t('eg')} http://localhost:9000`}
-            defaultValue={props.s3CustomEndpoint || ''}
+            value={props.s3CustomEndpoint || ''}
             onChange={(e) => {
             onChange={(e) => {
               props?.onChangeS3CustomEndpoint(e.target.value);
               props?.onChangeS3CustomEndpoint(e.target.value);
             }}
             }}
@@ -111,7 +111,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
             className="form-control"
             className="form-control"
             type="text"
             type="text"
             placeholder={`${t('eg')} crowi`}
             placeholder={`${t('eg')} crowi`}
-            defaultValue={props.s3Bucket || ''}
+            value={props.s3Bucket || ''}
             onChange={(e) => {
             onChange={(e) => {
               props.onChangeS3Bucket(e.target.value);
               props.onChangeS3Bucket(e.target.value);
             }}
             }}
@@ -127,7 +127,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
           <input
           <input
             className="form-control"
             className="form-control"
             type="text"
             type="text"
-            defaultValue={props.s3AccessKeyId || ''}
+            value={props.s3AccessKeyId || ''}
             onChange={(e) => {
             onChange={(e) => {
               props?.onChangeS3AccessKeyId(e.target.value);
               props?.onChangeS3AccessKeyId(e.target.value);
             }}
             }}

+ 8 - 8
apps/app/src/client/components/Admin/App/AzureSetting.tsx

@@ -118,12 +118,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
               <MaskedInput
                 name="azureTenantId"
                 name="azureTenantId"
                 readOnly={azureUseOnlyEnvVars}
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureTenantId}
+                value={azureTenantId}
                 onChange={e => props?.onChangeAzureTenantId(e.target.value)}
                 onChange={e => props?.onChangeAzureTenantId(e.target.value)}
               />
               />
             </td>
             </td>
             <td>
             <td>
-              <MaskedInput name="envAzureTenantId" defaultValue={envAzureTenantId || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureTenantId" value={envAzureTenantId || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_TENANT_ID' }) }} />
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_TENANT_ID' }) }} />
@@ -136,12 +136,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
               <MaskedInput
                 name="azureClientId"
                 name="azureClientId"
                 readOnly={azureUseOnlyEnvVars}
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureClientId}
+                value={azureClientId}
                 onChange={e => props?.onChangeAzureClientId(e.target.value)}
                 onChange={e => props?.onChangeAzureClientId(e.target.value)}
               />
               />
             </td>
             </td>
             <td>
             <td>
-              <MaskedInput name="envAzureClientId" defaultValue={envAzureClientId || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureClientId" value={envAzureClientId || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_ID' }) }} />
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_ID' }) }} />
@@ -154,12 +154,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
               <MaskedInput
                 name="azureClientSecret"
                 name="azureClientSecret"
                 readOnly={azureUseOnlyEnvVars}
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureClientSecret}
+                value={azureClientSecret}
                 onChange={e => props?.onChangeAzureClientSecret(e.target.value)}
                 onChange={e => props?.onChangeAzureClientSecret(e.target.value)}
               />
               />
             </td>
             </td>
             <td>
             <td>
-              <MaskedInput name="envAzureClientSecret" defaultValue={envAzureClientSecret || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureClientSecret" value={envAzureClientSecret || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_SECRET' }) }} />
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_SECRET' }) }} />
@@ -174,7 +174,7 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
                 type="text"
                 type="text"
                 name="azureStorageAccountName"
                 name="azureStorageAccountName"
                 readOnly={azureUseOnlyEnvVars}
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureStorageAccountName}
+                value={azureStorageAccountName}
                 onChange={e => props?.onChangeAzureStorageAccountName(e.target.value)}
                 onChange={e => props?.onChangeAzureStorageAccountName(e.target.value)}
               />
               />
             </td>
             </td>
@@ -194,7 +194,7 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
                 type="text"
                 type="text"
                 name="azureStorageContainerName"
                 name="azureStorageContainerName"
                 readOnly={azureUseOnlyEnvVars}
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureStorageContainerName}
+                value={azureStorageContainerName}
                 onChange={e => props?.onChangeAzureStorageContainerName(e.target.value)}
                 onChange={e => props?.onChangeAzureStorageContainerName(e.target.value)}
               />
               />
             </td>
             </td>

+ 3 - 3
apps/app/src/client/components/Admin/App/GcsSetting.tsx

@@ -108,7 +108,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 type="text"
                 name="gcsApiKeyJsonPath"
                 name="gcsApiKeyJsonPath"
                 readOnly={gcsUseOnlyEnvVars}
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsApiKeyJsonPath}
+                value={gcsApiKeyJsonPath}
                 onChange={e => props?.onChangeGcsApiKeyJsonPath(e.target.value)}
                 onChange={e => props?.onChangeGcsApiKeyJsonPath(e.target.value)}
               />
               />
             </td>
             </td>
@@ -128,7 +128,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 type="text"
                 name="gcsBucket"
                 name="gcsBucket"
                 readOnly={gcsUseOnlyEnvVars}
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsBucket}
+                value={gcsBucket}
                 onChange={e => props?.onChangeGcsBucket(e.target.value)}
                 onChange={e => props?.onChangeGcsBucket(e.target.value)}
               />
               />
             </td>
             </td>
@@ -148,7 +148,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 type="text"
                 name="gcsUploadNamespace"
                 name="gcsUploadNamespace"
                 readOnly={gcsUseOnlyEnvVars}
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsUploadNamespace}
+                value={gcsUploadNamespace}
                 onChange={e => props?.onChangeGcsUploadNamespace(e.target.value)}
                 onChange={e => props?.onChangeGcsUploadNamespace(e.target.value)}
               />
               />
             </td>
             </td>

+ 1 - 1
apps/app/src/client/components/Admin/App/MailSetting.tsx

@@ -56,7 +56,7 @@ const MailSetting = (props: Props) => {
             className="form-control"
             className="form-control"
             type="text"
             type="text"
             placeholder={`${t('eg')} mail@growi.org`}
             placeholder={`${t('eg')} mail@growi.org`}
-            defaultValue={adminAppContainer.state.fromAddress || ''}
+            value={adminAppContainer.state.fromAddress || ''}
             onChange={(e) => { adminAppContainer.changeFromAddress(e.target.value) }}
             onChange={(e) => { adminAppContainer.changeFromAddress(e.target.value) }}
           />
           />
         </div>
         </div>

+ 3 - 3
apps/app/src/client/components/Admin/App/MaskedInput.tsx

@@ -5,7 +5,7 @@ import styles from './MaskedInput.module.scss';
 type Props = {
 type Props = {
   name: string
   name: string
   readOnly: boolean
   readOnly: boolean
-  defaultValue: string
+  value: string
   onChange?: (e: any) => void
   onChange?: (e: any) => void
   tabIndex?: number | undefined
   tabIndex?: number | undefined
 };
 };
@@ -17,7 +17,7 @@ export default function MaskedInput(props: Props): JSX.Element {
   };
   };
 
 
   const {
   const {
-    name, readOnly, defaultValue, onChange, tabIndex,
+    name, readOnly, value, onChange, tabIndex,
   } = props;
   } = props;
 
 
   return (
   return (
@@ -27,7 +27,7 @@ export default function MaskedInput(props: Props): JSX.Element {
         type={passwordShown ? 'text' : 'password'}
         type={passwordShown ? 'text' : 'password'}
         name={name}
         name={name}
         readOnly={readOnly}
         readOnly={readOnly}
-        defaultValue={defaultValue}
+        value={value}
         onChange={onChange}
         onChange={onChange}
         tabIndex={tabIndex}
         tabIndex={tabIndex}
       />
       />

+ 2 - 2
apps/app/src/client/components/Admin/App/SesSetting.tsx

@@ -24,7 +24,7 @@ const SmtpSetting = (props: Props) => {
             <input
             <input
               className="form-control"
               className="form-control"
               type="text"
               type="text"
-              defaultValue={adminAppContainer.state.sesAccessKeyId || ''}
+              value={adminAppContainer.state.sesAccessKeyId || ''}
               onChange={(e) => {
               onChange={(e) => {
                 adminAppContainer.changeSesAccessKeyId(e.target.value);
                 adminAppContainer.changeSesAccessKeyId(e.target.value);
               }}
               }}
@@ -40,7 +40,7 @@ const SmtpSetting = (props: Props) => {
             <input
             <input
               className="form-control"
               className="form-control"
               type="text"
               type="text"
-              defaultValue={adminAppContainer.state.sesSecretAccessKey || ''}
+              value={adminAppContainer.state.sesSecretAccessKey || ''}
               onChange={(e) => {
               onChange={(e) => {
                 adminAppContainer.changeSesSecretAccessKey(e.target.value);
                 adminAppContainer.changeSesSecretAccessKey(e.target.value);
               }}
               }}

+ 1 - 1
apps/app/src/client/components/Admin/App/SiteUrlSetting.tsx

@@ -70,7 +70,7 @@ const SiteUrlSetting = (props: Props) => {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="settingForm[app:siteUrl]"
                   name="settingForm[app:siteUrl]"
-                  defaultValue={adminAppContainer.state.siteUrl || ''}
+                  value={adminAppContainer.state.siteUrl || ''}
                   disabled={adminAppContainer.state.siteUrlUseOnlyEnvVars ?? true}
                   disabled={adminAppContainer.state.siteUrlUseOnlyEnvVars ?? true}
                   onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
                   onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
                   placeholder="e.g. https://my.growi.org"
                   placeholder="e.g. https://my.growi.org"

+ 4 - 4
apps/app/src/client/components/Admin/App/SmtpSetting.tsx

@@ -27,7 +27,7 @@ const SmtpSetting = (props: Props) => {
             <input
             <input
               className="form-control"
               className="form-control"
               type="text"
               type="text"
-              defaultValue={adminAppContainer.state.smtpHost || ''}
+              value={adminAppContainer.state.smtpHost || ''}
               onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
               onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
             />
             />
           </div>
           </div>
@@ -40,7 +40,7 @@ const SmtpSetting = (props: Props) => {
           <div className="col-md-6">
           <div className="col-md-6">
             <input
             <input
               className="form-control"
               className="form-control"
-              defaultValue={adminAppContainer.state.smtpPort || ''}
+              value={adminAppContainer.state.smtpPort || ''}
               onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
               onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
             />
             />
           </div>
           </div>
@@ -54,7 +54,7 @@ const SmtpSetting = (props: Props) => {
             <input
             <input
               className="form-control"
               className="form-control"
               type="text"
               type="text"
-              defaultValue={adminAppContainer.state.smtpUser || ''}
+              value={adminAppContainer.state.smtpUser || ''}
               onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
               onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
             />
             />
           </div>
           </div>
@@ -68,7 +68,7 @@ const SmtpSetting = (props: Props) => {
             <input
             <input
               className="form-control"
               className="form-control"
               type="password"
               type="password"
-              defaultValue={adminAppContainer.state.smtpPassword || ''}
+              value={adminAppContainer.state.smtpPassword || ''}
               onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
               onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
             />
             />
           </div>
           </div>

+ 1 - 1
apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx

@@ -46,7 +46,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
               className="form-control"
               className="form-control"
               name="customizeCss"
               name="customizeCss"
               rows={8}
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeCss || ''}
+              value={adminCustomizeContainer.state.currentCustomizeCss || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
             />
             />
           </div>
           </div>

+ 1 - 1
apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx

@@ -50,7 +50,7 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
               className="form-control mb-2"
               className="form-control mb-2"
               name="customizeNoscript"
               name="customizeNoscript"
               rows={8}
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeNoscript || ''}
+              value={adminCustomizeContainer.state.currentCustomizeNoscript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeNoscript(e.target.value) }}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeNoscript(e.target.value) }}
             />
             />
           </div>
           </div>

+ 1 - 1
apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -47,7 +47,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
               className="form-control mb-2"
               className="form-control mb-2"
               name="customizeScript"
               name="customizeScript"
               rows={8}
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeScript || ''}
+              value={adminCustomizeContainer.state.currentCustomizeScript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
             />
             />
           </div>
           </div>

+ 1 - 1
apps/app/src/client/components/Admin/Customize/CustomizeTitle.tsx

@@ -67,7 +67,7 @@ export const CustomizeTitle: FC = () => {
         <div className="col-12">
         <div className="col-12">
           <input
           <input
             className="form-control"
             className="form-control"
-            defaultValue={currentCustomizeTitle}
+            value={currentCustomizeTitle}
             onChange={(e) => { setCrrentCustomizeTitle(e.target.value) }}
             onChange={(e) => { setCrrentCustomizeTitle(e.target.value) }}
           />
           />
         </div>
         </div>

+ 2 - 2
apps/app/src/client/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx

@@ -70,7 +70,7 @@ class SlackConfiguration extends React.Component {
                 <input
                 <input
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
-                  defaultValue={adminSlackIntegrationLegacyContainer.state.webhookUrl || ''}
+                  value={adminSlackIntegrationLegacyContainer.state.webhookUrl || ''}
                   onChange={e => adminSlackIntegrationLegacyContainer.changeWebhookUrl(e.target.value)}
                   onChange={e => adminSlackIntegrationLegacyContainer.changeWebhookUrl(e.target.value)}
                 />
                 />
               </div>
               </div>
@@ -122,7 +122,7 @@ class SlackConfiguration extends React.Component {
                   <input
                   <input
                     className="form-control"
                     className="form-control"
                     type="text"
                     type="text"
-                    defaultValue={adminSlackIntegrationLegacyContainer.state.slackToken || ''}
+                    value={adminSlackIntegrationLegacyContainer.state.slackToken || ''}
                     onChange={e => adminSlackIntegrationLegacyContainer.changeSlackToken(e.target.value)}
                     onChange={e => adminSlackIntegrationLegacyContainer.changeSlackToken(e.target.value)}
                   />
                   />
                 </div>
                 </div>

+ 2 - 2
apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx

@@ -52,7 +52,7 @@ export const WhitelistInput = (props: Props): JSX.Element => {
           name="recommendedTags"
           name="recommendedTags"
           rows={6}
           rows={6}
           cols={40}
           cols={40}
-          defaultValue={adminMarkDownContainer.state.tagWhitelist}
+          value={adminMarkDownContainer.state.tagWhitelist}
           onChange={(e) => { adminMarkDownContainer.setState({ tagWhitelist: e.target.value }) }}
           onChange={(e) => { adminMarkDownContainer.setState({ tagWhitelist: e.target.value }) }}
         />
         />
       </div>
       </div>
@@ -69,7 +69,7 @@ export const WhitelistInput = (props: Props): JSX.Element => {
           name="recommendedAttrs"
           name="recommendedAttrs"
           rows={6}
           rows={6}
           cols={40}
           cols={40}
-          defaultValue={adminMarkDownContainer.state.attrWhitelist}
+          value={adminMarkDownContainer.state.attrWhitelist}
           onChange={(e) => { adminMarkDownContainer.setState({ attrWhitelist: e.target.value }) }}
           onChange={(e) => { adminMarkDownContainer.setState({ attrWhitelist: e.target.value }) }}
         />
         />
       </div>
       </div>

+ 1 - 1
apps/app/src/client/components/Admin/Security/GitHubSecuritySettingContents.jsx

@@ -125,7 +125,7 @@ class GitHubSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="githubClientSecret"
                   name="githubClientSecret"
-                  defaultValue={adminGitHubSecurityContainer.state.githubClientSecret || ''}
+                  value={adminGitHubSecurityContainer.state.githubClientSecret || ''}
                   onChange={e => adminGitHubSecurityContainer.changeGitHubClientSecret(e.target.value)}
                   onChange={e => adminGitHubSecurityContainer.changeGitHubClientSecret(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">

+ 3 - 3
apps/app/src/client/components/Admin/Security/GoogleSecuritySettingContents.jsx

@@ -108,7 +108,7 @@ class GoogleSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="googleClientId"
                   name="googleClientId"
-                  defaultValue={adminGoogleSecurityContainer.state.googleClientId || ''}
+                  value={adminGoogleSecurityContainer.state.googleClientId || ''}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientId(e.target.value)}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientId(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -122,9 +122,9 @@ class GoogleSecurityManagementContents extends React.Component {
               <div className="col-6">
               <div className="col-6">
                 <input
                 <input
                   className="form-control"
                   className="form-control"
-                  type="text"
+                  type="password"
                   name="googleClientSecret"
                   name="googleClientSecret"
-                  defaultValue={adminGoogleSecurityContainer.state.googleClientSecret || ''}
+                  value={adminGoogleSecurityContainer.state.googleClientSecret || ''}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientSecret(e.target.value)}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientSecret(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">

+ 10 - 10
apps/app/src/client/components/Admin/Security/LdapSecuritySettingContents.jsx

@@ -92,7 +92,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="serverUrl"
                   name="serverUrl"
-                  defaultValue={adminLdapSecurityContainer.state.serverUrl || ''}
+                  value={adminLdapSecurityContainer.state.serverUrl || ''}
                   onChange={e => adminLdapSecurityContainer.changeServerUrl(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeServerUrl(e.target.value)}
                 />
                 />
                 <small>
                 <small>
@@ -145,7 +145,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="bindDN"
                   name="bindDN"
-                  defaultValue={adminLdapSecurityContainer.state.ldapBindDN || ''}
+                  value={adminLdapSecurityContainer.state.ldapBindDN || ''}
                   onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
                 />
                 />
                 {(adminLdapSecurityContainer.state.isUserBind === true) ? (
                 {(adminLdapSecurityContainer.state.isUserBind === true) ? (
@@ -194,7 +194,7 @@ class LdapSecuritySettingContents extends React.Component {
                         className="form-control passport-ldap-managerbind"
                         className="form-control passport-ldap-managerbind"
                         type="password"
                         type="password"
                         name="bindDNPassword"
                         name="bindDNPassword"
-                        defaultValue={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
+                        value={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
                         onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
                         onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
                       />
                       />
                     </>
                     </>
@@ -211,7 +211,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="searchFilter"
                   name="searchFilter"
-                  defaultValue={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
+                  value={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
                   onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -248,7 +248,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   type="text"
                   placeholder="Default: uid"
                   placeholder="Default: uid"
                   name="attrMapUsername"
                   name="attrMapUsername"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -292,7 +292,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   type="text"
                   placeholder="Default: mail"
                   placeholder="Default: mail"
                   name="attrMapMail"
                   name="attrMapMail"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapMail || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapMail || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapMail(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapMail(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -312,7 +312,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="attrMapName"
                   name="attrMapName"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -337,7 +337,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="groupSearchBase"
                   name="groupSearchBase"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -359,7 +359,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="groupSearchFilter"
                   name="groupSearchFilter"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -391,7 +391,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   type="text"
                   placeholder="Default: uid"
                   placeholder="Default: uid"
                   name="groupDnProperty"
                   name="groupDnProperty"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
                   onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">

+ 1 - 1
apps/app/src/client/components/Admin/Security/LocalSecuritySettingContents.jsx

@@ -147,7 +147,7 @@ class LocalSecuritySettingContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="textarea"
                   type="textarea"
                   name="registrationWhitelist"
                   name="registrationWhitelist"
-                  defaultValue={adminLocalSecurityContainer.state.registrationWhitelist.join('\n')}
+                  value={adminLocalSecurityContainer.state.registrationWhitelist.join('\n')}
                   onChange={e => adminLocalSecurityContainer.changeRegistrationWhitelist(e.target.value)}
                   onChange={e => adminLocalSecurityContainer.changeRegistrationWhitelist(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted small">
                 <p className="form-text text-muted small">

+ 16 - 16
apps/app/src/client/components/Admin/Security/OidcSecuritySettingContents.jsx

@@ -101,7 +101,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcProviderName"
                   name="oidcProviderName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcProviderName || ''}
+                  value={adminOidcSecurityContainer.state.oidcProviderName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcProviderName(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcProviderName(e.target.value)}
                 />
                 />
               </div>
               </div>
@@ -114,7 +114,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcIssuerHost"
                   name="oidcIssuerHost"
-                  defaultValue={adminOidcSecurityContainer.state.oidcIssuerHost || ''}
+                  value={adminOidcSecurityContainer.state.oidcIssuerHost || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcIssuerHost(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcIssuerHost(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -130,7 +130,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcClientId"
                   name="oidcClientId"
-                  defaultValue={adminOidcSecurityContainer.state.oidcClientId || ''}
+                  value={adminOidcSecurityContainer.state.oidcClientId || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientId(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientId(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -146,7 +146,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcClientSecret"
                   name="oidcClientSecret"
-                  defaultValue={adminOidcSecurityContainer.state.oidcClientSecret || ''}
+                  value={adminOidcSecurityContainer.state.oidcClientSecret || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientSecret(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientSecret(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -164,7 +164,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcAuthorizationEndpoint"
                   name="oidcAuthorizationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAuthorizationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcAuthorizationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAuthorizationEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcAuthorizationEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -180,7 +180,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcTokenEndpoint"
                   name="oidcTokenEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcTokenEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcTokenEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcTokenEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcTokenEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -198,7 +198,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcRevocationEndpoint"
                   name="oidcRevocationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcRevocationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcRevocationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcRevocationEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcRevocationEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -216,7 +216,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcIntrospectionEndpoint"
                   name="oidcIntrospectionEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcIntrospectionEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcIntrospectionEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcIntrospectionEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcIntrospectionEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -234,7 +234,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcUserInfoEndpoint"
                   name="oidcUserInfoEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcUserInfoEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcUserInfoEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcUserInfoEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcUserInfoEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -252,7 +252,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcEndSessionEndpoint"
                   name="oidcEndSessionEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcEndSessionEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcEndSessionEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcEndSessionEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcEndSessionEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -270,7 +270,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcRegistrationEndpoint"
                   name="oidcRegistrationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcRegistrationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcRegistrationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcRegistrationEndpoint(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcRegistrationEndpoint(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -286,7 +286,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcJWKSUri"
                   name="oidcJWKSUri"
-                  defaultValue={adminOidcSecurityContainer.state.oidcJWKSUri || ''}
+                  value={adminOidcSecurityContainer.state.oidcJWKSUri || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcJWKSUri(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcJWKSUri(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -306,7 +306,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcAttrMapId"
                   name="oidcAttrMapId"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapId || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapId || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapId(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapId(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -322,7 +322,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcAttrMapUserName"
                   name="oidcAttrMapUserName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapUserName || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapUserName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapUserName(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapUserName(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -338,7 +338,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcAttrMapName"
                   name="oidcAttrMapName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapName || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapName(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapName(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
@@ -354,7 +354,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   className="form-control"
                   type="text"
                   type="text"
                   name="oidcAttrMapEmail"
                   name="oidcAttrMapEmail"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapEmail || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapEmail || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapEmail(e.target.value)}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapEmail(e.target.value)}
                 />
                 />
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">

+ 9 - 9
apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -150,7 +150,7 @@ class SamlSecurityManagementContents extends React.Component {
                       type="text"
                       type="text"
                       name="samlEntryPoint"
                       name="samlEntryPoint"
                       readOnly={useOnlyEnvVars}
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlEntryPoint}
+                      value={adminSamlSecurityContainer.state.samlEntryPoint}
                       onChange={e => adminSamlSecurityContainer.changeSamlEntryPoint(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlEntryPoint(e.target.value)}
                     />
                     />
                   </td>
                   </td>
@@ -174,7 +174,7 @@ class SamlSecurityManagementContents extends React.Component {
                       type="text"
                       type="text"
                       name="samlEnvVarissuer"
                       name="samlEnvVarissuer"
                       readOnly={useOnlyEnvVars}
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlIssuer}
+                      value={adminSamlSecurityContainer.state.samlIssuer}
                       onChange={e => adminSamlSecurityContainer.changeSamlIssuer(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlIssuer(e.target.value)}
                     />
                     />
                   </td>
                   </td>
@@ -199,7 +199,7 @@ class SamlSecurityManagementContents extends React.Component {
                       rows="5"
                       rows="5"
                       name="samlCert"
                       name="samlCert"
                       readOnly={useOnlyEnvVars}
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlCert}
+                      value={adminSamlSecurityContainer.state.samlCert}
                       onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                     />
                     />
                     <p>
                     <p>
@@ -258,7 +258,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                     <input
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapId}
+                      value={adminSamlSecurityContainer.state.samlAttrMapId}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapId(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapId(e.target.value)}
                     />
                     />
                     <p className="form-text text-muted">
                     <p className="form-text text-muted">
@@ -285,7 +285,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                     <input
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapUsername}
+                      value={adminSamlSecurityContainer.state.samlAttrMapUsername}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapUserName(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapUserName(e.target.value)}
                     />
                     />
                     <p className="form-text text-muted">
                     <p className="form-text text-muted">
@@ -310,7 +310,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                     <input
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapMail}
+                      value={adminSamlSecurityContainer.state.samlAttrMapMail}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapMail(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapMail(e.target.value)}
                     />
                     />
                     <p className="form-text text-muted">
                     <p className="form-text text-muted">
@@ -335,7 +335,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                     <input
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapFirstName}
+                      value={adminSamlSecurityContainer.state.samlAttrMapFirstName}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapFirstName(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapFirstName(e.target.value)}
                     />
                     />
                     <p className="form-text text-muted">
                     <p className="form-text text-muted">
@@ -365,7 +365,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                     <input
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapLastName}
+                      value={adminSamlSecurityContainer.state.samlAttrMapLastName}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapLastName(e.target.value)}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapLastName(e.target.value)}
                     />
                     />
                     <p className="form-text text-muted">
                     <p className="form-text text-muted">
@@ -462,7 +462,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <textarea
                     <textarea
                       className="form-control"
                       className="form-control"
                       type="text"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlABLCRule || ''}
+                      value={adminSamlSecurityContainer.state.samlABLCRule || ''}
                       onChange={(e) => { adminSamlSecurityContainer.changeSamlABLCRule(e.target.value) }}
                       onChange={(e) => { adminSamlSecurityContainer.changeSamlABLCRule(e.target.value) }}
                     />
                     />
                     <div className="mt-2">
                     <div className="mt-2">

+ 1 - 1
apps/app/src/client/components/Admin/Security/SecuritySetting.jsx

@@ -591,7 +591,7 @@ class SecuritySetting extends React.Component {
             <input
             <input
               className="form-control col-md-4"
               className="form-control col-md-4"
               type="text"
               type="text"
-              defaultValue={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
+              value={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
               onChange={(e) => {
               onChange={(e) => {
                 adminGeneralSecurityContainer.setSessionMaxAge(e.target.value);
                 adminGeneralSecurityContainer.setSessionMaxAge(e.target.value);
               }}
               }}

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -111,7 +111,7 @@ const CustomBotWithProxySettings = (props) => {
                 className="form-control"
                 className="form-control"
                 type="text"
                 type="text"
                 name="settingForm[proxyUrl]"
                 name="settingForm[proxyUrl]"
-                defaultValue={newProxyServerUri}
+                value={newProxyServerUri}
                 onChange={(e) => { setNewProxyServerUri(e.target.value) }}
                 onChange={(e) => { setNewProxyServerUri(e.target.value) }}
               />
               />
             </div>
             </div>

+ 2 - 2
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx

@@ -65,7 +65,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <input
           <input
             className="form-control"
             className="form-control"
             type="text"
             type="text"
-            defaultValue={slackSigningSecretEnv}
+            value={slackSigningSecretEnv}
             readOnly
             readOnly
           />
           />
           <p className="form-text text-muted">
           <p className="form-text text-muted">
@@ -94,7 +94,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <input
           <input
             className="form-control"
             className="form-control"
             type="text"
             type="text"
-            defaultValue={slackBotTokenEnv}
+            value={slackBotTokenEnv}
             readOnly
             readOnly
           />
           />
           <p className="form-text text-muted">
           <p className="form-text text-muted">

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx

@@ -147,7 +147,7 @@ const PermissionSettingForEachPermissionTypeComponent = ({
             className="form-control"
             className="form-control"
             type="textarea"
             type="textarea"
             name={keyName}
             name={keyName}
-            defaultValue={textareaDefaultValue}
+            value={textareaDefaultValue}
             onChange={onUpdateChannels}
             onChange={onUpdateChannels}
           />
           />
           <p className="form-text text-muted small">
           <p className="form-text text-muted small">

+ 1 - 1
apps/app/src/client/components/Me/AccessTokenForm.tsx

@@ -1,10 +1,10 @@
 import React from 'react';
 import React from 'react';
 
 
+import type { Scope } from '@growi/core/dist/interfaces';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { useForm } from 'react-hook-form';
 import { useForm } from 'react-hook-form';
 
 
 import type { IAccessTokenInfo } from '~/interfaces/access-token';
 import type { IAccessTokenInfo } from '~/interfaces/access-token';
-import type { Scope } from '~/interfaces/scope';
 
 
 import { AccessTokenScopeSelect } from './AccessTokenScopeSelect';
 import { AccessTokenScopeSelect } from './AccessTokenScopeSelect';
 
 

+ 1 - 1
apps/app/src/client/components/Me/AccessTokenScopeList.tsx

@@ -1,11 +1,11 @@
 import React from 'react';
 import React from 'react';
 
 
+import type { Scope } from '@growi/core/dist/interfaces';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import type { UseFormRegisterReturn } from 'react-hook-form';
 import type { UseFormRegisterReturn } from 'react-hook-form';
 
 
 import { useIsDeviceLargerThanMd } from '~/stores/ui';
 import { useIsDeviceLargerThanMd } from '~/stores/ui';
 
 
-import type { Scope } from '../../../interfaces/scope';
 
 
 import styles from './AccessTokenScopeList.module.scss';
 import styles from './AccessTokenScopeList.module.scss';
 
 

+ 2 - 3
apps/app/src/client/components/Me/AccessTokenScopeSelect.tsx

@@ -1,13 +1,12 @@
 import React, { useEffect, useState, useMemo } from 'react';
 import React, { useEffect, useState, useMemo } from 'react';
 
 
+import type { Scope } from '@growi/core/dist/interfaces';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import type { UseFormRegisterReturn } from 'react-hook-form';
 import type { UseFormRegisterReturn } from 'react-hook-form';
 
 
 import { extractScopes, getDisabledScopes, parseScopes } from '~/client/util/scope-util';
 import { extractScopes, getDisabledScopes, parseScopes } from '~/client/util/scope-util';
 import { useIsAdmin } from '~/stores-universal/context';
 import { useIsAdmin } from '~/stores-universal/context';
 
 
-import type { Scope } from '../../../interfaces/scope';
-import { SCOPE } from '../../../interfaces/scope';
-
 import { AccessTokenScopeList } from './AccessTokenScopeList';
 import { AccessTokenScopeList } from './AccessTokenScopeList';
 
 
 /**
 /**

+ 12 - 4
apps/app/src/client/components/Me/ApiSettings.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+import { useCurrentUser } from '~/stores-universal/context';
+
 import { AccessTokenSettings } from './AccessTokenSettings';
 import { AccessTokenSettings } from './AccessTokenSettings';
 import { ApiTokenSettings } from './ApiTokenSettings';
 import { ApiTokenSettings } from './ApiTokenSettings';
 
 
@@ -9,6 +11,9 @@ import { ApiTokenSettings } from './ApiTokenSettings';
 const ApiSettings = React.memo((): JSX.Element => {
 const ApiSettings = React.memo((): JSX.Element => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { data: currentUser, isLoading: isLoadingCurrentUserData } = useCurrentUser();
+
+  const shouldHideAccessTokenSettings = isLoadingCurrentUserData || !currentUser?.readOnly;
 
 
   return (
   return (
     <>
     <>
@@ -16,10 +21,13 @@ const ApiSettings = React.memo((): JSX.Element => {
         <h2 className="border-bottom pb-2 my-4 fs-4">{ t('API Token Settings') }</h2>
         <h2 className="border-bottom pb-2 my-4 fs-4">{ t('API Token Settings') }</h2>
         <ApiTokenSettings />
         <ApiTokenSettings />
       </div>
       </div>
-      <div className="mt-4">
-        <h2 className="border-bottom pb-2 my-4 fs-4">{ t('Access Token Settings') }</h2>
-        <AccessTokenSettings />
-      </div>
+
+      {shouldHideAccessTokenSettings && (
+        <div className="mt-4">
+          <h2 className="border-bottom pb-2 my-4 fs-4">{ t('Access Token Settings') }</h2>
+          <AccessTokenSettings />
+        </div>
+      )}
     </>
     </>
   );
   );
 });
 });

+ 0 - 1
apps/app/src/client/components/Sidebar/InAppNotification/PrimaryItemForNotification.tsx

@@ -8,7 +8,6 @@ import { PrimaryItem, type PrimaryItemProps } from '../SidebarNav/PrimaryItem';
 
 
 type PrimaryItemForNotificationProps = Omit<PrimaryItemProps, 'onClick' | 'label' | 'iconName' | 'contents' | 'badgeContents' >
 type PrimaryItemForNotificationProps = Omit<PrimaryItemProps, 'onClick' | 'label' | 'iconName' | 'contents' | 'badgeContents' >
 
 
-// TODO(after v7 release): https://redmine.weseek.co.jp/issues/138463
 export const PrimaryItemForNotification = memo((props: PrimaryItemForNotificationProps) => {
 export const PrimaryItemForNotification = memo((props: PrimaryItemForNotificationProps) => {
   const { sidebarMode, onHover } = props;
   const { sidebarMode, onHover } = props;
 
 

+ 15 - 4
apps/app/src/client/components/Sidebar/SidebarContents.tsx

@@ -2,6 +2,7 @@ import React, { memo, useMemo } from 'react';
 
 
 import { AiAssistant } from '~/features/openai/client/components/AiAssistant/Sidebar/AiAssistant';
 import { AiAssistant } from '~/features/openai/client/components/AiAssistant/Sidebar/AiAssistant';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { SidebarContentsType } from '~/interfaces/ui';
+import { useIsAiEnabled, useIsGuestUser } from '~/stores-universal/context';
 import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 
 
 
 
@@ -17,8 +18,10 @@ import styles from './SidebarContents.module.scss';
 
 
 export const SidebarContents = memo(() => {
 export const SidebarContents = memo(() => {
   const { isCollapsedMode } = useSidebarMode();
   const { isCollapsedMode } = useSidebarMode();
-  const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isAiEnabled } = useIsAiEnabled();
 
 
+  const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
   const { data: currentSidebarContents } = useCurrentSidebarContents();
   const { data: currentSidebarContents } = useCurrentSidebarContents();
 
 
   const Contents = useMemo(() => {
   const Contents = useMemo(() => {
@@ -32,13 +35,21 @@ export const SidebarContents = memo(() => {
       case SidebarContentsType.BOOKMARKS:
       case SidebarContentsType.BOOKMARKS:
         return Bookmarks;
         return Bookmarks;
       case SidebarContentsType.NOTIFICATION:
       case SidebarContentsType.NOTIFICATION:
-        return InAppNotification;
+        if (isGuestUser == null) return () => <></>; // wait for isGuestUser to be determined
+        if (!isGuestUser) {
+          return InAppNotification;
+        }
+        return PageTree;
       case SidebarContentsType.AI_ASSISTANT:
       case SidebarContentsType.AI_ASSISTANT:
-        return AiAssistant;
+        if (isAiEnabled == null) return () => <></>; // wait for isAiEnabled to be determined
+        if (isAiEnabled) {
+          return AiAssistant;
+        }
+        return PageTree;
       default:
       default:
         return PageTree;
         return PageTree;
     }
     }
-  }, [currentSidebarContents]);
+  }, [currentSidebarContents, isAiEnabled, isGuestUser]);
 
 
   const isHidden = isCollapsedMode() && !isCollapsedContentsOpened;
   const isHidden = isCollapsedMode() && !isCollapsedContentsOpened;
   const classToHide = isHidden ? 'd-none' : '';
   const classToHide = isHidden ? 'd-none' : '';

+ 3 - 2
apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -3,7 +3,7 @@ import { memo } from 'react';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 
 
 import { SidebarContentsType } from '~/interfaces/ui';
 import { SidebarContentsType } from '~/interfaces/ui';
-import { useIsAiEnabled } from '~/stores-universal/context';
+import { useIsAiEnabled, useIsGuestUser } from '~/stores-universal/context';
 import { useSidebarMode } from '~/stores/ui';
 import { useSidebarMode } from '~/stores/ui';
 
 
 import { PrimaryItem } from './PrimaryItem';
 import { PrimaryItem } from './PrimaryItem';
@@ -24,6 +24,7 @@ export const PrimaryItems = memo((props: Props) => {
 
 
   const { data: sidebarMode } = useSidebarMode();
   const { data: sidebarMode } = useSidebarMode();
   const { data: isAiEnabled } = useIsAiEnabled();
   const { data: isAiEnabled } = useIsAiEnabled();
+  const { data: isGuestUser } = useIsGuestUser();
 
 
   if (sidebarMode == null) {
   if (sidebarMode == null) {
     return <></>;
     return <></>;
@@ -36,7 +37,7 @@ export const PrimaryItems = memo((props: Props) => {
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
-      <PrimaryItemForNotification sidebarMode={sidebarMode} onHover={onItemHover} />
+      {isGuestUser === false && <PrimaryItemForNotification sidebarMode={sidebarMode} onHover={onItemHover} />}
       {isAiEnabled && (
       {isAiEnabled && (
         <PrimaryItem
         <PrimaryItem
           sidebarMode={sidebarMode}
           sidebarMode={sidebarMode}

+ 4 - 2
apps/app/src/client/services/page-operation.ts

@@ -5,6 +5,7 @@ import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
 import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
 import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
+import { useIsGuestUser } from '~/stores-universal/context';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import {
 import {
   useCurrentPageId, useSWRMUTxCurrentPage, useSWRxApplicableGrant, useSWRxTagsInfo,
   useCurrentPageId, useSWRMUTxCurrentPage, useSWRxApplicableGrant, useSWRxTagsInfo,
@@ -103,8 +104,9 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
   const { sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
-  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(pageId);
-  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(pageId);
+  const { data: isGuestUser } = useIsGuestUser();
+  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(isGuestUser ? null : pageId);
+  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(isGuestUser ? null : pageId);
 
 
   // update swr 'currentPageId', 'currentPage', remote states
   // update swr 'currentPageId', 'currentPage', remote states
   return useCallback(async() => {
   return useCallback(async() => {

+ 21 - 17
apps/app/src/client/util/scope-util.test.ts

@@ -1,7 +1,6 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { describe, it, expect } from 'vitest';
 import { describe, it, expect } from 'vitest';
 
 
-import { ALL_SIGN } from '../../interfaces/scope';
-
 import { parseScopes, getDisabledScopes, extractScopes } from './scope-util';
 import { parseScopes, getDisabledScopes, extractScopes } from './scope-util';
 
 
 describe('scope-util', () => {
 describe('scope-util', () => {
@@ -60,34 +59,39 @@ describe('scope-util', () => {
   });
   });
 
 
   it('should disable specific scopes when a wildcard is selected', () => {
   it('should disable specific scopes when a wildcard is selected', () => {
-    const selectedScopes = [`read:${ALL_SIGN}`];
-    const availableScopes = ['read:user', 'read:admin', 'write:user', `read:${ALL_SIGN}`];
+    const selectedScopes = [SCOPE.READ.ALL];
+    const availableScopes = [
+      SCOPE.READ.FEATURES.PAGE,
+      SCOPE.READ.FEATURES.ATTACHMENT,
+      SCOPE.WRITE.FEATURES.PAGE,
+      SCOPE.READ.ALL,
+    ];
 
 
     const result = getDisabledScopes(selectedScopes, availableScopes);
     const result = getDisabledScopes(selectedScopes, availableScopes);
 
 
     // Should disable all read: scopes except the wildcard itself
     // Should disable all read: scopes except the wildcard itself
-    expect(result.has('read:user')).toBe(true);
-    expect(result.has('read:admin')).toBe(true);
-    expect(result.has(`read:${ALL_SIGN}`)).toBe(false);
-    expect(result.has('write:user')).toBe(false);
+    expect(result.has(SCOPE.READ.FEATURES.PAGE)).toBe(true);
+    expect(result.has(SCOPE.READ.FEATURES.ATTACHMENT)).toBe(true);
+    expect(result.has(SCOPE.WRITE.FEATURES.PAGE)).toBe(false);
+    expect(result.has(SCOPE.READ.ALL)).toBe(false);
   });
   });
 
 
   it('should handle multiple wildcard selections', () => {
   it('should handle multiple wildcard selections', () => {
-    const selectedScopes = [`read:${ALL_SIGN}`, `write:${ALL_SIGN}`];
+    const selectedScopes = [SCOPE.READ.ALL, SCOPE.WRITE.ALL];
     const availableScopes = [
     const availableScopes = [
-      'read:user', 'read:admin', `read:${ALL_SIGN}`,
-      'write:user', 'write:admin', `write:${ALL_SIGN}`,
+      SCOPE.READ.FEATURES.PAGE, SCOPE.READ.FEATURES.ATTACHMENT, SCOPE.READ.ALL,
+      SCOPE.WRITE.FEATURES.PAGE, SCOPE.WRITE.FEATURES.ATTACHMENT, SCOPE.WRITE.ALL,
     ];
     ];
 
 
     const result = getDisabledScopes(selectedScopes, availableScopes);
     const result = getDisabledScopes(selectedScopes, availableScopes);
 
 
     // Should disable all specific scopes under both wildcards
     // Should disable all specific scopes under both wildcards
-    expect(result.has('read:user')).toBe(true);
-    expect(result.has('read:admin')).toBe(true);
-    expect(result.has('write:user')).toBe(true);
-    expect(result.has('write:admin')).toBe(true);
-    expect(result.has(`read:${ALL_SIGN}`)).toBe(false);
-    expect(result.has(`write:${ALL_SIGN}`)).toBe(false);
+    expect(result.has(SCOPE.READ.FEATURES.PAGE)).toBe(true);
+    expect(result.has(SCOPE.READ.FEATURES.ATTACHMENT)).toBe(true);
+    expect(result.has(SCOPE.WRITE.FEATURES.PAGE)).toBe(true);
+    expect(result.has(SCOPE.WRITE.FEATURES.ATTACHMENT)).toBe(true);
+    expect(result.has(SCOPE.READ.ALL)).toBe(false);
+    expect(result.has(SCOPE.WRITE.ALL)).toBe(false);
   });
   });
 
 
   it('should extract all scope strings from a nested object', () => {
   it('should extract all scope strings from a nested object', () => {

+ 2 - 2
apps/app/src/client/util/scope-util.ts

@@ -1,5 +1,5 @@
-import type { Scope } from '~/interfaces/scope';
-import { ALL_SIGN } from '~/interfaces/scope';
+import { ALL_SIGN, type Scope } from '@growi/core/dist/interfaces';
+
 
 
 // Data structure for the final merged scopes
 // Data structure for the final merged scopes
 interface ScopeMap {
 interface ScopeMap {

+ 1 - 1
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group-relation.ts

@@ -3,7 +3,7 @@ import type { Router, Request } from 'express';
 
 
 import type { IExternalUserGroupRelationHasId } from '~/features/external-user-group/interfaces/external-user-group';
 import type { IExternalUserGroupRelationHasId } from '~/features/external-user-group/interfaces/external-user-group';
 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 { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { serializeUserGroupRelationSecurely } from '~/server/models/serializers/user-group-relation-serializer';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers/user-group-relation-serializer';

+ 1 - 1
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -9,7 +9,7 @@ import {
 import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
 import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
 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 { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import type { PageActionOnGroupDelete } from '~/interfaces/user-group';
 import type { PageActionOnGroupDelete } from '~/interfaces/user-group';
 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';

+ 1 - 1
apps/app/src/features/growi-plugin/server/routes/apiv3/admin/index.ts

@@ -3,7 +3,7 @@ import express from 'express';
 import { body, query } from 'express-validator';
 import { body, query } from 'express-validator';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';

+ 11 - 6
apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx

@@ -10,9 +10,8 @@ import { useAiAssistantSidebar, useSWRxAiAssistants } from '../../stores/ai-assi
 
 
 import styles from './OpenDefaultAiAssistantButton.module.scss';
 import styles from './OpenDefaultAiAssistantButton.module.scss';
 
 
-const OpenDefaultAiAssistantButton = (): JSX.Element => {
+const OpenDefaultAiAssistantButtonSubstance = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { data: isAiEnabled } = useIsAiEnabled();
   const { data: aiAssistantData } = useSWRxAiAssistants();
   const { data: aiAssistantData } = useSWRxAiAssistants();
   const { openChat } = useAiAssistantSidebar();
   const { openChat } = useAiAssistantSidebar();
 
 
@@ -33,10 +32,6 @@ const OpenDefaultAiAssistantButton = (): JSX.Element => {
     openChat(defaultAiAssistant);
     openChat(defaultAiAssistant);
   }, [defaultAiAssistant, openChat]);
   }, [defaultAiAssistant, openChat]);
 
 
-  if (!isAiEnabled) {
-    return <></>;
-  }
-
   return (
   return (
     <NotAvailableForGuest>
     <NotAvailableForGuest>
       <NotAvailable isDisabled={defaultAiAssistant == null} title={t('default_ai_assistant.not_set')}>
       <NotAvailable isDisabled={defaultAiAssistant == null} title={t('default_ai_assistant.not_set')}>
@@ -52,4 +47,14 @@ const OpenDefaultAiAssistantButton = (): JSX.Element => {
   );
   );
 };
 };
 
 
+const OpenDefaultAiAssistantButton = (): JSX.Element => {
+  const { data: isAiEnabled } = useIsAiEnabled();
+
+  if (!isAiEnabled) {
+    return <></>;
+  }
+
+  return <OpenDefaultAiAssistantButtonSubstance />;
+};
+
 export default OpenDefaultAiAssistantButton;
 export default OpenDefaultAiAssistantButton;

+ 1 - 1
apps/app/src/features/openai/server/routes/ai-assistant.ts

@@ -2,7 +2,7 @@ import { type IUserHasId } from '@growi/core';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/ai-assistants.ts

@@ -2,7 +2,7 @@ import { type IUserHasId } from '@growi/core';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';

+ 1 - 1
apps/app/src/features/openai/server/routes/delete-ai-assistant.ts

@@ -5,7 +5,7 @@ import { type ValidationChain, param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 import { isHttpError } from 'http-errors';
 
 
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/delete-thread.ts

@@ -5,7 +5,7 @@ import { type ValidationChain, param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 import { isHttpError } from 'http-errors';
 
 
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -8,7 +8,7 @@ import { zodResponseFormat } from 'openai/helpers/zod';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 import { z } from 'zod';
 import { z } from 'zod';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 // Necessary imports
 // Necessary imports
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/get-threads.ts

@@ -3,7 +3,7 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 import { type ValidationChain, param } from 'express-validator';
 import { type ValidationChain, param } from 'express-validator';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/message/get-messages.ts

@@ -3,7 +3,7 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 import { type ValidationChain, param } from 'express-validator';
 import { type ValidationChain, param } from 'express-validator';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/message/post-message.ts

@@ -1,4 +1,5 @@
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler, Response } from 'express';
 import type { Request, RequestHandler, Response } from 'express';
 import type { ValidationChain } from 'express-validator';
 import type { ValidationChain } from 'express-validator';
@@ -7,7 +8,6 @@ import type { AssistantStream } from 'openai/lib/AssistantStream';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 
 
 import { getOrCreateChatAssistant } from '~/features/openai/server/services/assistant';
 import { getOrCreateChatAssistant } from '~/features/openai/server/services/assistant';
-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';

+ 1 - 1
apps/app/src/features/openai/server/routes/set-default-ai-assistant.ts

@@ -3,7 +3,7 @@ import type { Request, RequestHandler } from 'express';
 import { type ValidationChain, param, body } from 'express-validator';
 import { type ValidationChain, param, body } from 'express-validator';
 import { isHttpError } from 'http-errors';
 import { isHttpError } from 'http-errors';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/thread.ts

@@ -4,7 +4,7 @@ import type { Request, RequestHandler } from 'express';
 import type { ValidationChain } from 'express-validator';
 import type { ValidationChain } from 'express-validator';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/features/openai/server/routes/update-ai-assistant.ts

@@ -4,7 +4,7 @@ import type { Request, RequestHandler } from 'express';
 import { type ValidationChain, param } from 'express-validator';
 import { type ValidationChain, param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 import { isHttpError } from 'http-errors';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 26 - 24
apps/app/src/features/page-bulk-export/server/routes/apiv3/page-bulk-export.ts

@@ -1,3 +1,4 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request } from 'express';
 import type { Request } from 'express';
 import { Router } from 'express';
 import { Router } from 'express';
@@ -18,6 +19,7 @@ interface AuthorizedRequest extends Request {
 }
 }
 
 
 module.exports = (crowi: Crowi): Router => {
 module.exports = (crowi: Crowi): Router => {
+  const accessTokenParser = crowi.accessTokenParser;
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
 
 
   const validators = {
   const validators = {
@@ -28,31 +30,31 @@ module.exports = (crowi: Crowi): Router => {
     ],
     ],
   };
   };
 
 
-  // TODO: https://redmine.weseek.co.jp/issues/166911
-  router.post('/', loginRequiredStrictly, validators.pageBulkExport, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.status(400).json({ errors: errors.array() });
-    }
-
-    const { path, format, restartJob } = req.body;
-
-    try {
-      await pageBulkExportService?.createOrResetBulkExportJob(path, format, req.user, restartJob);
-      return res.apiv3({}, 204);
-    }
-    catch (err) {
-      logger.error(err);
-      if (err instanceof DuplicateBulkExportJobError) {
-        return res.apiv3Err(new ErrorV3(
-          'Duplicate bulk export job is in progress',
-          'page_export.duplicate_bulk_export_job_error', undefined,
-          { duplicateJob: { createdAt: err.duplicateJob.createdAt } },
-        ), 409);
+  router.post('/', accessTokenParser([SCOPE.WRITE.FEATURES.PAGE_BULK_EXPORT]),
+    loginRequiredStrictly, validators.pageBulkExport, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const errors = validationResult(req);
+      if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
       }
       }
-      return res.apiv3Err(new ErrorV3('Failed to start bulk export', 'page_export.failed_to_export'));
-    }
-  });
+
+      const { path, format, restartJob } = req.body;
+
+      try {
+        await pageBulkExportService?.createOrResetBulkExportJob(path, format, req.user, restartJob);
+        return res.apiv3({}, 204);
+      }
+      catch (err) {
+        logger.error(err);
+        if (err instanceof DuplicateBulkExportJobError) {
+          return res.apiv3Err(new ErrorV3(
+            'Duplicate bulk export job is in progress',
+            'page_export.duplicate_bulk_export_job_error', undefined,
+            { duplicateJob: { createdAt: err.duplicateJob.createdAt } },
+          ), 409);
+        }
+        return res.apiv3Err(new ErrorV3('Failed to start bulk export', 'page_export.failed_to_export'));
+      }
+    });
 
 
   return router;
   return router;
 
 

+ 1 - 1
apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts

@@ -3,7 +3,7 @@ import type { Request } from 'express';
 import { Router } from 'express';
 import { Router } from 'express';
 import { body, validationResult } from 'express-validator';
 import { body, validationResult } from 'express-validator';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';

+ 1 - 1
apps/app/src/features/templates/server/routes/apiv3/index.ts

@@ -8,7 +8,7 @@ import { param, query } from 'express-validator';
 
 
 import { PLUGIN_STORING_PATH } from '~/features/growi-plugin/server/consts';
 import { PLUGIN_STORING_PATH } from '~/features/growi-plugin/server/consts';
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/interfaces/access-token.ts

@@ -1,4 +1,4 @@
-import type { Scope } from './scope';
+import type { Scope } from '@growi/core/dist/interfaces';
 
 
 export type IAccessTokenInfo = {
 export type IAccessTokenInfo = {
   expiredAt: Date,
   expiredAt: Date,

+ 1 - 1
apps/app/src/server/crowi/index.js

@@ -62,7 +62,7 @@ class Crowi {
 
 
   /**
   /**
    * For retrieving other packages
    * For retrieving other packages
-   * @type {(req: import('express').Request, res: import('express').Response, next: import('express').NextFunction) => Promise<void>}
+   * @type {import('~/server/middlewares/access-token-parser').AccessTokenParser}
    */
    */
   accessTokenParser;
   accessTokenParser;
 
 

+ 1 - 1
apps/app/src/server/middlewares/access-token-parser/access-token.integ.ts

@@ -3,7 +3,7 @@ import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import type { Response } from 'express';
 import type { Response } from 'express';
 import { mock } from 'vitest-mock-extended';
 import { mock } from 'vitest-mock-extended';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import type UserEvent from '~/server/events/user';
 import type UserEvent from '~/server/events/user';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';

+ 6 - 2
apps/app/src/server/middlewares/access-token-parser/access-token.ts

@@ -1,8 +1,7 @@
-import type { IUserHasId } from '@growi/core/dist/interfaces';
+import type { IUserHasId, Scope } from '@growi/core/dist/interfaces';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import type { Response } from 'express';
 import type { Response } from 'express';
 
 
-import type { Scope } from '~/interfaces/scope';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -36,6 +35,11 @@ export const parserForAccessToken = (scopes: Scope[]) => {
       return;
       return;
     }
     }
 
 
+    if (userByAccessToken.readOnly) {
+      logger.debug('The access token\'s associated user is read-only');
+      return;
+    }
+
     // transforming attributes
     // transforming attributes
     req.user = serializeUserSecurely(userByAccessToken);
     req.user = serializeUserSecurely(userByAccessToken);
     if (req.user == null) {
     if (req.user == null) {

+ 6 - 4
apps/app/src/server/middlewares/access-token-parser/index.ts

@@ -1,13 +1,15 @@
+import type { Scope } from '@growi/core/dist/interfaces';
 import type { NextFunction, Response } from 'express';
 import type { NextFunction, Response } from 'express';
 
 
-import type { Scope } from '~/interfaces/scope';
-
 import { parserForAccessToken } from './access-token';
 import { parserForAccessToken } from './access-token';
 import { parserForApiToken } from './api-token';
 import { parserForApiToken } from './api-token';
 import type { AccessTokenParserReq } from './interfaces';
 import type { AccessTokenParserReq } from './interfaces';
 
 
-export const accessTokenParser = (scopes?: Scope[], opts?: {acceptLegacy: boolean}) => {
-  return async(req: AccessTokenParserReq, res: Response, next: NextFunction): Promise<void> => {
+export type AccessTokenParser = (scopes?: Scope[], opts?: {acceptLegacy: boolean})
+  => (req: AccessTokenParserReq, res: Response, next: NextFunction) => Promise<void>
+
+export const accessTokenParser: AccessTokenParser = (scopes, opts) => {
+  return async(req, res, next): Promise<void> => {
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     if (scopes == null || scopes.length === 0) {
     if (scopes == null || scopes.length === 0) {
       return next();
       return next();

+ 1 - 2
apps/app/src/server/models/access-token.ts

@@ -1,6 +1,6 @@
 import crypto from 'crypto';
 import crypto from 'crypto';
 
 
-import type { Ref, IUserHasId } from '@growi/core/dist/interfaces';
+import type { Ref, IUserHasId, Scope } from '@growi/core/dist/interfaces';
 import type {
 import type {
   Document, Model, Types, HydratedDocument,
   Document, Model, Types, HydratedDocument,
 } from 'mongoose';
 } from 'mongoose';
@@ -8,7 +8,6 @@ import { Schema } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
 import mongoosePaginate from 'mongoose-paginate-v2';
 import uniqueValidator from 'mongoose-unique-validator';
 import uniqueValidator from 'mongoose-unique-validator';
 
 
-import type { Scope } from '~/interfaces/scope';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { getOrCreateModel } from '../util/mongoose-utils';
 import { getOrCreateModel } from '../util/mongoose-utils';

+ 1 - 1
apps/app/src/server/routes/apiv3/activity.ts

@@ -5,7 +5,7 @@ import express from 'express';
 import { query } from 'express-validator';
 import { query } from 'express-validator';
 
 
 import type { IActivity, ISearchFilter } from '~/interfaces/activity';
 import type { IActivity, ISearchFilter } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';

+ 1 - 1
apps/app/src/server/routes/apiv3/admin-home.ts

@@ -1,4 +1,4 @@
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { getGrowiVersion } from '~/utils/growi-version';
 import { getGrowiVersion } from '~/utils/growi-version';

+ 1 - 2
apps/app/src/server/routes/apiv3/app-settings.js

@@ -1,5 +1,5 @@
 import {
 import {
-  ConfigSource, toNonBlankString, toNonBlankStringOrUndefined,
+  ConfigSource, toNonBlankString, toNonBlankStringOrUndefined, SCOPE,
 } from '@growi/core/dist/interfaces';
 } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
@@ -7,7 +7,6 @@ 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 { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 import { getTranslation } from '~/server/service/i18next';
 import { getTranslation } from '~/server/service/i18next';

+ 1 - 1
apps/app/src/server/routes/apiv3/attachment.js

@@ -5,7 +5,7 @@ import multer from 'multer';
 import autoReap from 'multer-autoreap';
 import autoReap from 'multer-autoreap';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Attachment } from '~/server/models/attachment';
 import { Attachment } from '~/server/models/attachment';

+ 1 - 1
apps/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -3,7 +3,7 @@ import { body } from 'express-validator';
 import type { Types } from 'mongoose';
 import type { Types } from 'mongoose';
 
 
 import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
 import { InvalidParentBookmarkFolderError } from '~/server/models/errors';
 import { InvalidParentBookmarkFolderError } from '~/server/models/errors';

+ 1 - 1
apps/app/src/server/routes/apiv3/bookmarks.js

@@ -1,7 +1,7 @@
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
 import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';

+ 1 - 1
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -8,7 +8,7 @@ import multer from 'multer';
 
 
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Attachment } from '~/server/models/attachment';
 import { Attachment } from '~/server/models/attachment';

+ 1 - 1
apps/app/src/server/routes/apiv3/export.js

@@ -1,7 +1,7 @@
 import sanitize from 'sanitize-filename';
 import sanitize from 'sanitize-filename';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { exportService } from '~/server/service/export';
 import { exportService } from '~/server/service/export';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';

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

@@ -7,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/server/routes/apiv3/import.js

@@ -1,7 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { getImportService } from '~/server/service/import';
 import { getImportService } from '~/server/service/import';
 import { generateOverwriteParams } from '~/server/service/import/overwrite-params';
 import { generateOverwriteParams } from '~/server/service/import/overwrite-params';

+ 1 - 1
apps/app/src/server/routes/apiv3/in-app-notification.ts

@@ -3,7 +3,7 @@ import express from 'express';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
 
 

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

@@ -2,7 +2,7 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';

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

@@ -9,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -6,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -16,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -4,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -5,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -16,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -5,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -5,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -5,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

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

@@ -15,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/server/routes/apiv3/pages/index.js

@@ -9,7 +9,7 @@ import { body, query } from 'express-validator';
 
 
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting';
 import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting';
 import PageTagRelation from '~/server/models/page-tag-relation';
 import PageTagRelation from '~/server/models/page-tag-relation';

+ 3 - 1
apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts

@@ -3,11 +3,12 @@ import type { Request, RequestHandler } from 'express';
 import { query } from 'express-validator';
 import { query } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
+import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -40,6 +41,7 @@ export const deleteAccessTokenHandlersFactory: DeleteAccessTokenHandlersFactory
   return [
   return [
     accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
     accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
     loginRequiredStrictly,
     loginRequiredStrictly,
+    excludeReadOnlyUser,
     addActivity,
     addActivity,
     validator,
     validator,
     apiV3FormValidator,
     apiV3FormValidator,

+ 3 - 1
apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts

@@ -3,10 +3,11 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
+import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -29,6 +30,7 @@ export const deleteAllAccessTokensHandlersFactory: DeleteAllAccessTokensHandlers
   return [
   return [
     accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
     accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
     loginRequiredStrictly,
     loginRequiredStrictly,
+    excludeReadOnlyUser,
     addActivity,
     addActivity,
     async(req: DeleteAllAccessTokensRequest, res: ApiV3Response) => {
     async(req: DeleteAllAccessTokensRequest, res: ApiV3Response) => {
       const { user } = req;
       const { user } = req;

+ 3 - 2
apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts

@@ -1,14 +1,14 @@
 import type {
 import type {
-  IUserHasId,
+  IUserHasId, Scope,
 } from '@growi/core/dist/interfaces';
 } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import type { Scope } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
+import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';
 import { isValidScope } from '~/server/util/scope-utils';
 import { isValidScope } from '~/server/util/scope-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -82,6 +82,7 @@ export const generateAccessTokenHandlerFactory: GenerateAccessTokenHandlerFactor
 
 
   return [
   return [
     loginRequiredStrictly,
     loginRequiredStrictly,
+    excludeReadOnlyUser,
     addActivity,
     addActivity,
     validator,
     validator,
     apiV3FormValidator,
     apiV3FormValidator,

+ 3 - 1
apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts

@@ -2,10 +2,11 @@ import type { IUserHasId } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
+import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import { AccessToken } from '~/server/models/access-token';
 import { AccessToken } from '~/server/models/access-token';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -27,6 +28,7 @@ export const getAccessTokenHandlerFactory: GetAccessTokenHandlerFactory = (crowi
   return [
   return [
     accessTokenParser([SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN]),
     accessTokenParser([SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN]),
     loginRequiredStrictly,
     loginRequiredStrictly,
+    excludeReadOnlyUser,
     addActivity,
     addActivity,
     async(req: GetAccessTokenRequest, res: ApiV3Response) => {
     async(req: GetAccessTokenRequest, res: ApiV3Response) => {
       const { user } = req;
       const { user } = req;

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

@@ -5,7 +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 { SCOPE } from '@growi/core/dist/interfaces';
 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';
 
 

+ 1 - 1
apps/app/src/server/routes/apiv3/revisions.js

@@ -3,7 +3,7 @@ import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import express from 'express';
 import express from 'express';
 import { connection } from 'mongoose';
 import { connection } from 'mongoose';
 
 
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Revision } from '~/server/models/revision';
 import { Revision } from '~/server/models/revision';
 import { normalizeLatestRevisionIfBroken } from '~/server/service/revision/normalize-latest-revision-if-broken';
 import { normalizeLatestRevisionIfBroken } from '~/server/service/revision/normalize-latest-revision-if-broken';

+ 1 - 1
apps/app/src/server/routes/apiv3/search.js

@@ -1,7 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';
 
 

+ 10 - 12
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -1,11 +1,10 @@
-import { ConfigSource } from '@growi/core/dist/interfaces';
+import { ConfigSource, toNonBlankStringOrUndefined, SCOPE } 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 { 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';
@@ -410,11 +409,11 @@ module.exports = (crowi) => {
 
 
   const activityEvent = crowi.event('activity');
   const activityEvent = crowi.event('activity');
 
 
-  async function updateAndReloadStrategySettings(authId, params) {
+  async function updateAndReloadStrategySettings(authId, params, opts = { removeIfUndefined: false }) {
     const { passportService } = crowi;
     const { passportService } = crowi;
 
 
     // update config without publishing S2sMessage
     // update config without publishing S2sMessage
-    await configManager.updateConfigs(params, { skipPubsub: true });
+    await configManager.updateConfigs(params, { skipPubsub: true, removeIfUndefined: opts.removeIfUndefined });
 
 
     await passportService.setupStrategyById(authId);
     await passportService.setupStrategyById(authId);
     passportService.publishUpdatedMessage(authId);
     passportService.publishUpdatedMessage(authId);
@@ -1279,15 +1278,14 @@ module.exports = (crowi) => {
   router.put('/google-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
   router.put('/google-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity,
     validator.googleOAuth, apiV3FormValidator,
     validator.googleOAuth, apiV3FormValidator,
     async(req, res) => {
     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 {
       try {
-        await updateAndReloadStrategySettings('google', requestParams);
+        await updateAndReloadStrategySettings('google', {
+          'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+        });
+        await updateAndReloadStrategySettings('google', {
+          'security:passport-google:clientId': toNonBlankStringOrUndefined(req.body.googleClientId),
+          'security:passport-google:clientSecret': toNonBlankStringOrUndefined(req.body.googleClientSecret),
+        }, { removeIfUndefined: true });
 
 
         const securitySettingParams = {
         const securitySettingParams = {
           googleClientId: await configManager.getConfig('security:passport-google:clientId'),
           googleClientId: await configManager.getConfig('security:passport-google:clientId'),

+ 1 - 1
apps/app/src/server/routes/apiv3/share-links.js

@@ -4,7 +4,7 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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';

+ 1 - 1
apps/app/src/server/routes/apiv3/slack-integration-legacy-settings.js

@@ -3,7 +3,7 @@ import express from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '~/interfaces/scope';
+import { SCOPE } from '@growi/core/dist/interfaces';
 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 loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';

+ 1 - 2
apps/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -1,4 +1,4 @@
-import { ConfigSource } from '@growi/core/dist/interfaces';
+import { ConfigSource, SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import {
 import {
   SlackbotType, REQUEST_TIMEOUT_FOR_GTOP,
   SlackbotType, REQUEST_TIMEOUT_FOR_GTOP,
@@ -10,7 +10,6 @@ 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';
 
 

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio