feat: preview allowed origins config (#22138)

* feat: allowed origins config

* chore: update getstarted admin preview config

* fix: do not extend middlewares if allowedOrigins is not defined

* fix: middleware merging
This commit is contained in:
Marc Roig 2024-11-20 09:54:32 +01:00 committed by GitHub
parent 2b3b491922
commit b67f4e6a09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 4 deletions

View File

@ -21,13 +21,12 @@ const getFeature = (): Partial<Plugin.LoadedPlugin> => {
// }
return {
bootstrap() {
// eslint-disable-next-line no-console -- TODO remove when we have real functionality
console.log('Bootstrapping preview server');
register() {
const config = getService(strapi, 'preview-config');
config.validate();
config.register();
},
bootstrap() {},
routes,
controllers,
services,

View File

@ -1,3 +1,5 @@
import { mergeWith } from 'lodash/fp';
import type { Core, UID } from '@strapi/types';
import { errors } from '@strapi/utils';
@ -10,15 +12,75 @@ export type HandlerParams = {
export interface PreviewConfig {
enabled: boolean;
config: {
// List of CSP allowed origins. This is a shortcut to setting it up inside `config/middlewares.js`
allowedOrigins: string[];
handler: (uid: UID.Schema, params: HandlerParams) => string | undefined;
};
}
/**
* Utility to extend Strapi configuration middlewares. Mainly used to extend the CSP directives from the security middleware.
*/
const extendMiddlewareConfiguration = (middleware = { name: '', config: {} }) => {
const middlewares = strapi.config.get('middlewares') as (string | object)[];
const configuredMiddlewares = middlewares.map((currentMiddleware) => {
if (currentMiddleware === middleware.name) {
// Use the new config object if the middleware has no config property yet
return middleware;
}
// @ts-expect-error - currentMiddleware is not a string
if (currentMiddleware.name === middleware.name) {
// Deep merge (+ concat arrays) the new config with the current middleware config
return mergeWith(
(objValue, srcValue) => {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
return undefined;
},
currentMiddleware,
middleware
);
}
return currentMiddleware;
});
strapi.config.set('middlewares', configuredMiddlewares);
};
/**
* Read configuration for static preview
*/
const createPreviewConfigService = ({ strapi }: { strapi: Core.Strapi }) => {
return {
register() {
if (!this.isEnabled()) {
return;
}
const config = strapi.config.get('admin.preview') as PreviewConfig;
/**
* Register the allowed origins for CSP, so the preview URL can be displayed
*/
if (config.config?.allowedOrigins) {
extendMiddlewareConfiguration({
name: 'strapi::security',
config: {
contentSecurityPolicy: {
directives: {
'frame-src': config.config.allowedOrigins,
},
},
},
});
}
},
isEnabled() {
const config = strapi.config.get('admin.preview') as PreviewConfig;

View File

@ -1,8 +1,10 @@
import type { Plugin } from '@strapi/types';
import history from './history';
import preview from './preview';
const register: Plugin.LoadedPlugin['register'] = async ({ strapi }) => {
await history.register?.({ strapi });
await preview.register?.({ strapi });
};
export default register;