arvid-e 9 месяцев назад
Родитель
Сommit
a73a467b89

+ 162 - 0
apps/app/src/client/services/attachment.ts

@@ -0,0 +1,162 @@
+import express from 'express';
+import type { Request, Response } from 'express';
+import { body, validationResult } from 'express-validator';
+
+import { configManager } from '~/server/service/config-manager';
+import ApiResponse from '~/server/util/apiResponse';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:routes:admin:attachments');
+
+export const PREDEFINED_INLINE_MIME_TYPES = [
+  'image/png',
+  'image/jpeg',
+  'image/gif',
+  'image/webp',
+  'image/bmp',
+  'image/x-icon',
+  'application/pdf',
+  'video/mp4',
+  'audio/mpeg',
+  'text/plain',
+] as const;
+
+const DANGEROUS_MIME_TYPES = new Set([
+  'text/html',
+  'application/javascript',
+  'application/x-javascript',
+  'image/svg+xml',
+  'application/x-shockwave-flash',
+]);
+
+// Assign the router factory function to a variable before exporting
+const adminAttachmentsRouterFactory = (crowi: any) => {
+  const router = express.Router();
+
+  /**
+   * @swagger
+   * /admin/attachments/inline-allowlist:
+   * get:
+   * tags: [Attachments, Admin]
+   * summary: Get inline display allowlist settings
+   * description: Retrieve the current inline display allowlist for attachment MIME types.
+   * responses:
+   * 200:
+   * description: Successfully retrieved settings.
+   * content:
+   * application/json:
+   * schema:
+   * properties:
+   * inlineAllowlistTypes:
+   * type: array
+   * items:
+   * type: string
+   * description: List of allowed MIME types for inline display.
+   * customInlineAllowlistTypes:
+   * type: string
+   * description: Comma-separated string of custom allowed MIME types.
+   * 500:
+   * description: Server error.
+   */
+  router.get('/inline-allowlist', (req: Request, res: Response) => {
+    try {
+      const inlineAllowlistTypes = configManager.getConfig('security:inlineAllowlistTypes') || [];
+      const customInlineAllowlistTypes = configManager.getConfig('security:customInlineAllowlistTypes') || '';
+      return res.json(ApiResponse.success({ inlineAllowlistTypes, customInlineAllowlistTypes }));
+    } catch (err) {
+      logger.error('Failed to retrieve inline allowlist settings', err);
+      return res.json(ApiResponse.error('Failed to retrieve settings.'));
+    }
+  });
+
+  /**
+   * @swagger
+   * /admin/attachments/inline-allowlist:
+   * put:
+   * tags: [Attachments, Admin]
+   * summary: Update inline display allowlist settings
+   * description: Update the inline display allowlist for attachment MIME types.
+   * requestBody:
+   * required: true
+   * content:
+   * application/json:
+   * schema:
+   * type: object
+   * properties:
+   * inlineAllowlistTypes:
+   * type: array
+   * items:
+   * type: string
+   * description: List of selected predefined MIME types.
+   * customInlineAllowlistTypes:
+   * type: string
+   * description: Comma-separated string of custom MIME types.
+   * responses:
+   * 200:
+   * description: Successfully updated settings.
+   * content:
+   * application/json:
+   * schema:
+   * properties:
+   * updatedConfig:
+   * type: object
+   * properties:
+   * inlineAllowlistTypes:
+   * type: array
+   * items:
+   * type: string
+   * customInlineAllowlistTypes:
+   * type: string
+   * 400:
+   * description: Invalid request body.
+   * 500:
+   * description: Server error.
+   */
+  router.put('/inline-allowlist', [
+    body('inlineAllowlistTypes').isArray().withMessage('inlineAllowlistTypes must be an array'),
+    body('inlineAllowlistTypes.*').isString().withMessage('inlineAllowlistTypes elements must be strings'),
+    body('customInlineAllowlistTypes').isString().withMessage('customInlineAllowlistTypes must be a string'),
+  ], async(req: Request, res: Response) => {
+    const errors = validationResult(req);
+    if (!errors.isEmpty()) {
+      return res.status(400).json(ApiResponse.error(errors.array().map(e => e.msg).join(', ')));
+    }
+
+    const { inlineAllowlistTypes, customInlineAllowlistTypes } = req.body;
+
+    const validPredefinedTypes = (Array.isArray(inlineAllowlistTypes) ? inlineAllowlistTypes : [])
+      .filter((type: string) => typeof type === 'string' && PREDEFINED_INLINE_MIME_TYPES.includes(type as (typeof PREDEFINED_INLINE_MIME_TYPES)[number]));
+
+    const sanitizedCustomTypesArray = (typeof customInlineAllowlistTypes === 'string' ? customInlineAllowlistTypes : '')
+      .split(',')
+      .map(type => type.trim())
+      .filter(type => type.length > 0 && !DANGEROUS_MIME_TYPES.has(type));
+
+    const combinedAllowlist = Array.from(new Set([
+      ...validPredefinedTypes,
+      ...sanitizedCustomTypesArray,
+    ]));
+
+    try {
+      await configManager.updateConfigs({
+        'security:inlineAllowlistTypes': combinedAllowlist,
+        'security:customInlineAllowlistTypes': sanitizedCustomTypesArray.join(','),
+      });
+
+      const updatedConfig = {
+        inlineAllowlistTypes: combinedAllowlist,
+        customInlineAllowlistTypes: sanitizedCustomTypesArray.join(','),
+      };
+
+      return res.json(ApiResponse.success({ updatedConfig }));
+    } catch (err) {
+      logger.error('Failed to update inline allowlist settings', err);
+      return res.json(ApiResponse.error('Failed to update settings.'));
+    }
+  });
+
+  return router;
+};
+
+// Export the variable as the default module export
+export default adminAttachmentsRouterFactory;

+ 8 - 0
apps/app/src/server/service/config-manager/config-definition.ts

@@ -85,6 +85,8 @@ export const CONFIG_KEYS = [
   'security:trustProxyBool',
   'security:trustProxyCsv',
   'security:trustProxyHops',
+  'security:inlineAllowlistTypes',
+  'security:customInlineAllowlistTypes',
   'security:passport-local:isEnabled',
   'security:passport-local:isPasswordResetEnabled',
   'security:passport-local:isEmailAuthenticationEnabled',
@@ -567,6 +569,12 @@ export const CONFIG_DEFINITIONS = {
     defaultValue: undefined,
     isSecret: true,
   }),
+  'security:inlineAllowlistTypes': defineConfig<string[]>({
+    defaultValue: [],
+  }),
+  'security:customInlineAllowlistTypes': defineConfig<string>({
+    defaultValue: '',
+  }),
   'security:passport-local:isEnabled': defineConfig<boolean>({
     envVarName: 'LOCAL_STRATEGY_ENABLED',
     defaultValue: true,