@strapi/core 5.0.0-beta.8 → 5.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Strapi.d.ts +3 -1
- package/dist/Strapi.d.ts.map +1 -1
- package/dist/Strapi.js +57 -13
- package/dist/Strapi.js.map +1 -1
- package/dist/Strapi.mjs +38 -13
- package/dist/Strapi.mjs.map +1 -1
- package/dist/core-api/service/pagination.js +1 -1
- package/dist/core-api/service/pagination.js.map +1 -1
- package/dist/core-api/service/pagination.mjs +2 -2
- package/dist/core-api/service/pagination.mjs.map +1 -1
- package/dist/middlewares/cors.d.ts.map +1 -1
- package/dist/middlewares/cors.js +6 -8
- package/dist/middlewares/cors.js.map +1 -1
- package/dist/middlewares/cors.mjs +6 -8
- package/dist/middlewares/cors.mjs.map +1 -1
- package/dist/middlewares/security.d.ts.map +1 -1
- package/dist/middlewares/security.js +10 -3
- package/dist/middlewares/security.js.map +1 -1
- package/dist/middlewares/security.mjs +11 -4
- package/dist/middlewares/security.mjs.map +1 -1
- package/dist/services/document-service/utils/populate.d.ts.map +1 -1
- package/dist/services/document-service/utils/populate.js +4 -0
- package/dist/services/document-service/utils/populate.js.map +1 -1
- package/dist/services/document-service/utils/populate.mjs +4 -0
- package/dist/services/document-service/utils/populate.mjs.map +1 -1
- package/dist/services/entity-validator/index.d.ts +14 -1
- package/dist/services/entity-validator/index.d.ts.map +1 -1
- package/dist/services/entity-validator/index.js +87 -30
- package/dist/services/entity-validator/index.js.map +1 -1
- package/dist/services/entity-validator/index.mjs +87 -30
- package/dist/services/entity-validator/index.mjs.map +1 -1
- package/dist/services/entity-validator/validators.d.ts +10 -8
- package/dist/services/entity-validator/validators.d.ts.map +1 -1
- package/dist/services/entity-validator/validators.js +60 -19
- package/dist/services/entity-validator/validators.js.map +1 -1
- package/dist/services/entity-validator/validators.mjs +53 -11
- package/dist/services/entity-validator/validators.mjs.map +1 -1
- package/dist/services/server/index.js +1 -1
- package/dist/services/server/index.js.map +1 -1
- package/dist/services/server/index.mjs +1 -1
- package/dist/services/server/index.mjs.map +1 -1
- package/dist/services/webhook-runner.d.ts +2 -1
- package/dist/services/webhook-runner.d.ts.map +1 -1
- package/dist/services/webhook-runner.js.map +1 -1
- package/dist/services/webhook-runner.mjs.map +1 -1
- package/dist/services/webhook-store.d.ts +2 -8
- package/dist/services/webhook-store.d.ts.map +1 -1
- package/dist/services/webhook-store.js +1 -1
- package/dist/services/webhook-store.js.map +1 -1
- package/dist/services/webhook-store.mjs +1 -1
- package/dist/services/webhook-store.mjs.map +1 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +4 -3
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/fetch.mjs +4 -3
- package/dist/utils/fetch.mjs.map +1 -1
- package/dist/utils/transform-content-types-to-models.d.ts.map +1 -1
- package/dist/utils/transform-content-types-to-models.js +10 -0
- package/dist/utils/transform-content-types-to-models.js.map +1 -1
- package/dist/utils/transform-content-types-to-models.mjs +10 -0
- package/dist/utils/transform-content-types-to-models.mjs.map +1 -1
- package/package.json +16 -14
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"cors.js","sources":["../../src/middlewares/cors.ts"],"sourcesContent":["import koaCors from '@koa/cors';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = {\n enabled?: boolean;\n origin: string | string[] | ((ctx: any) => string | string[]);\n expose?: string | string[];\n maxAge?: number;\n credentials?: boolean;\n methods?: string | string[];\n headers?: string | string[];\n keepHeadersOnError?: boolean;\n};\n\nconst defaults: Config = {\n origin: '*',\n maxAge: 31536000,\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],\n headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],\n keepHeadersOnError: false,\n};\n\nexport const cors: Core.MiddlewareFactory<Config> = (config) => {\n const { origin, expose, maxAge, credentials, methods, headers, keepHeadersOnError } = {\n ...defaults,\n ...config,\n };\n\n if (config.enabled !== undefined) {\n strapi.log.warn(\n 'The strapi::cors middleware no longer supports the `enabled` option. Using it' +\n ' to conditionally enable CORS might cause an insecure default. To disable strapi::cors, remove it from' +\n ' the exported array in config/middleware.js'\n );\n }\n\n return koaCors({\n async origin(ctx) {\n let originList: string | string[];\n\n if (typeof origin === 'function') {\n originList = await origin(ctx);\n } else {\n originList = origin;\n }\n\n
|
1
|
+
{"version":3,"file":"cors.js","sources":["../../src/middlewares/cors.ts"],"sourcesContent":["import koaCors from '@koa/cors';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = {\n enabled?: boolean;\n origin: string | string[] | ((ctx: any) => string | string[]);\n expose?: string | string[];\n maxAge?: number;\n credentials?: boolean;\n methods?: string | string[];\n headers?: string | string[];\n keepHeadersOnError?: boolean;\n};\n\nconst defaults: Config = {\n origin: '*',\n maxAge: 31536000,\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],\n headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],\n keepHeadersOnError: false,\n};\n\nexport const cors: Core.MiddlewareFactory<Config> = (config) => {\n const { origin, expose, maxAge, credentials, methods, headers, keepHeadersOnError } = {\n ...defaults,\n ...config,\n };\n\n if (config.enabled !== undefined) {\n strapi.log.warn(\n 'The strapi::cors middleware no longer supports the `enabled` option. Using it' +\n ' to conditionally enable CORS might cause an insecure default. To disable strapi::cors, remove it from' +\n ' the exported array in config/middleware.js'\n );\n }\n\n return koaCors({\n async origin(ctx) {\n if (!ctx.get('Origin')) {\n return '*';\n }\n\n let originList: string | string[];\n\n if (typeof origin === 'function') {\n originList = await origin(ctx);\n } else {\n originList = origin;\n }\n\n if (Array.isArray(originList)) {\n return originList.includes(ctx.get('Origin')) ? ctx.get('Origin') : '';\n }\n\n return originList;\n },\n exposeHeaders: expose,\n maxAge,\n credentials,\n allowMethods: methods,\n allowHeaders: headers,\n keepHeadersOnError,\n });\n};\n"],"names":["koaCors"],"mappings":";;;;;AAeA,MAAM,WAAmB;AAAA,EACvB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,SAAS,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAAA,EACpE,SAAS,CAAC,gBAAgB,iBAAiB,UAAU,QAAQ;AAAA,EAC7D,oBAAoB;AACtB;AAEa,MAAA,OAAuC,CAAC,WAAW;AACxD,QAAA,EAAE,QAAQ,QAAQ,QAAQ,aAAa,SAAS,SAAS,uBAAuB;AAAA,IACpF,GAAG;AAAA,IACH,GAAG;AAAA,EAAA;AAGD,MAAA,OAAO,YAAY,QAAW;AAChC,WAAO,IAAI;AAAA,MACT;AAAA,IAAA;AAAA,EAIJ;AAEA,SAAOA,yBAAQ;AAAA,IACb,MAAM,OAAO,KAAK;AAChB,UAAI,CAAC,IAAI,IAAI,QAAQ,GAAG;AACf,eAAA;AAAA,MACT;AAEI,UAAA;AAEA,UAAA,OAAO,WAAW,YAAY;AACnB,qBAAA,MAAM,OAAO,GAAG;AAAA,MAAA,OACxB;AACQ,qBAAA;AAAA,MACf;AAEI,UAAA,MAAM,QAAQ,UAAU,GAAG;AACtB,eAAA,WAAW,SAAS,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,QAAQ,IAAI;AAAA,MACtE;AAEO,aAAA;AAAA,IACT;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,cAAc;AAAA,IACd;AAAA,EAAA,CACD;AACH;;"}
|
@@ -19,21 +19,19 @@ const cors = (config) => {
|
|
19
19
|
}
|
20
20
|
return koaCors({
|
21
21
|
async origin(ctx) {
|
22
|
+
if (!ctx.get("Origin")) {
|
23
|
+
return "*";
|
24
|
+
}
|
22
25
|
let originList;
|
23
26
|
if (typeof origin === "function") {
|
24
27
|
originList = await origin(ctx);
|
25
28
|
} else {
|
26
29
|
originList = origin;
|
27
30
|
}
|
28
|
-
|
29
|
-
|
30
|
-
if (whitelist.includes("*")) {
|
31
|
-
return credentials ? requestOrigin : "*";
|
32
|
-
}
|
33
|
-
if (!whitelist.includes(requestOrigin)) {
|
34
|
-
return ctx.throw(`${requestOrigin} is not a valid origin`);
|
31
|
+
if (Array.isArray(originList)) {
|
32
|
+
return originList.includes(ctx.get("Origin")) ? ctx.get("Origin") : "";
|
35
33
|
}
|
36
|
-
return
|
34
|
+
return originList;
|
37
35
|
},
|
38
36
|
exposeHeaders: expose,
|
39
37
|
maxAge,
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"cors.mjs","sources":["../../src/middlewares/cors.ts"],"sourcesContent":["import koaCors from '@koa/cors';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = {\n enabled?: boolean;\n origin: string | string[] | ((ctx: any) => string | string[]);\n expose?: string | string[];\n maxAge?: number;\n credentials?: boolean;\n methods?: string | string[];\n headers?: string | string[];\n keepHeadersOnError?: boolean;\n};\n\nconst defaults: Config = {\n origin: '*',\n maxAge: 31536000,\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],\n headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],\n keepHeadersOnError: false,\n};\n\nexport const cors: Core.MiddlewareFactory<Config> = (config) => {\n const { origin, expose, maxAge, credentials, methods, headers, keepHeadersOnError } = {\n ...defaults,\n ...config,\n };\n\n if (config.enabled !== undefined) {\n strapi.log.warn(\n 'The strapi::cors middleware no longer supports the `enabled` option. Using it' +\n ' to conditionally enable CORS might cause an insecure default. To disable strapi::cors, remove it from' +\n ' the exported array in config/middleware.js'\n );\n }\n\n return koaCors({\n async origin(ctx) {\n let originList: string | string[];\n\n if (typeof origin === 'function') {\n originList = await origin(ctx);\n } else {\n originList = origin;\n }\n\n
|
1
|
+
{"version":3,"file":"cors.mjs","sources":["../../src/middlewares/cors.ts"],"sourcesContent":["import koaCors from '@koa/cors';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = {\n enabled?: boolean;\n origin: string | string[] | ((ctx: any) => string | string[]);\n expose?: string | string[];\n maxAge?: number;\n credentials?: boolean;\n methods?: string | string[];\n headers?: string | string[];\n keepHeadersOnError?: boolean;\n};\n\nconst defaults: Config = {\n origin: '*',\n maxAge: 31536000,\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],\n headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],\n keepHeadersOnError: false,\n};\n\nexport const cors: Core.MiddlewareFactory<Config> = (config) => {\n const { origin, expose, maxAge, credentials, methods, headers, keepHeadersOnError } = {\n ...defaults,\n ...config,\n };\n\n if (config.enabled !== undefined) {\n strapi.log.warn(\n 'The strapi::cors middleware no longer supports the `enabled` option. Using it' +\n ' to conditionally enable CORS might cause an insecure default. To disable strapi::cors, remove it from' +\n ' the exported array in config/middleware.js'\n );\n }\n\n return koaCors({\n async origin(ctx) {\n if (!ctx.get('Origin')) {\n return '*';\n }\n\n let originList: string | string[];\n\n if (typeof origin === 'function') {\n originList = await origin(ctx);\n } else {\n originList = origin;\n }\n\n if (Array.isArray(originList)) {\n return originList.includes(ctx.get('Origin')) ? ctx.get('Origin') : '';\n }\n\n return originList;\n },\n exposeHeaders: expose,\n maxAge,\n credentials,\n allowMethods: methods,\n allowHeaders: headers,\n keepHeadersOnError,\n });\n};\n"],"names":[],"mappings":";AAeA,MAAM,WAAmB;AAAA,EACvB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,SAAS,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAAA,EACpE,SAAS,CAAC,gBAAgB,iBAAiB,UAAU,QAAQ;AAAA,EAC7D,oBAAoB;AACtB;AAEa,MAAA,OAAuC,CAAC,WAAW;AACxD,QAAA,EAAE,QAAQ,QAAQ,QAAQ,aAAa,SAAS,SAAS,uBAAuB;AAAA,IACpF,GAAG;AAAA,IACH,GAAG;AAAA,EAAA;AAGD,MAAA,OAAO,YAAY,QAAW;AAChC,WAAO,IAAI;AAAA,MACT;AAAA,IAAA;AAAA,EAIJ;AAEA,SAAO,QAAQ;AAAA,IACb,MAAM,OAAO,KAAK;AAChB,UAAI,CAAC,IAAI,IAAI,QAAQ,GAAG;AACf,eAAA;AAAA,MACT;AAEI,UAAA;AAEA,UAAA,OAAO,WAAW,YAAY;AACnB,qBAAA,MAAM,OAAO,GAAG;AAAA,MAAA,OACxB;AACQ,qBAAA;AAAA,MACf;AAEI,UAAA,MAAM,QAAQ,UAAU,GAAG;AACtB,eAAA,WAAW,SAAS,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,QAAQ,IAAI;AAAA,MACtE;AAEO,aAAA;AAAA,IACT;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,cAAc;AAAA,IACd;AAAA,EAAA,CACD;AACH;"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/middlewares/security.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/middlewares/security.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAkC3D,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAkEjD,CAAC"}
|
@@ -27,6 +27,13 @@ const defaults = {
|
|
27
27
|
action: "sameorigin"
|
28
28
|
}
|
29
29
|
};
|
30
|
+
const mergeConfig = (existingConfig, newConfig) => {
|
31
|
+
return _.mergeWith(
|
32
|
+
(obj, src) => Array.isArray(obj) && Array.isArray(src) ? obj.concat(src) : void 0,
|
33
|
+
existingConfig,
|
34
|
+
newConfig
|
35
|
+
);
|
36
|
+
};
|
30
37
|
const security = (config, { strapi }) => (ctx, next) => {
|
31
38
|
let helmetConfig = _.defaultsDeep(defaults, config);
|
32
39
|
const specialPaths = ["/documentation"];
|
@@ -47,7 +54,7 @@ const security = (config, { strapi }) => (ctx, next) => {
|
|
47
54
|
directives["frame-src"].push("sandbox.embed.apollographql.com");
|
48
55
|
}
|
49
56
|
if (ctx.method === "GET" && specialPaths.some((str) => ctx.path.startsWith(str))) {
|
50
|
-
helmetConfig =
|
57
|
+
helmetConfig = mergeConfig(helmetConfig, {
|
51
58
|
crossOriginEmbedderPolicy: false,
|
52
59
|
// TODO: only use this for graphql playground
|
53
60
|
contentSecurityPolicy: {
|
@@ -55,8 +62,8 @@ const security = (config, { strapi }) => (ctx, next) => {
|
|
55
62
|
}
|
56
63
|
});
|
57
64
|
}
|
58
|
-
if (process.env.NODE_ENV
|
59
|
-
helmetConfig =
|
65
|
+
if (["development", "test"].includes(process.env.NODE_ENV ?? "") && ctx.method === "GET" && ["/admin"].some((str) => ctx.path.startsWith(str))) {
|
66
|
+
helmetConfig = mergeConfig(helmetConfig, {
|
60
67
|
contentSecurityPolicy: {
|
61
68
|
directives: {
|
62
69
|
"script-src": ["'self'", "'unsafe-inline'"],
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"security.js","sources":["../../src/middlewares/security.ts"],"sourcesContent":["import { defaultsDeep,
|
1
|
+
{"version":3,"file":"security.js","sources":["../../src/middlewares/security.ts"],"sourcesContent":["import { defaultsDeep, mergeWith } from 'lodash/fp';\nimport helmet, { KoaHelmet } from 'koa-helmet';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = NonNullable<Parameters<KoaHelmet>[0]>;\n\nconst defaults: Config = {\n crossOriginEmbedderPolicy: false,\n crossOriginOpenerPolicy: false,\n crossOriginResourcePolicy: false,\n originAgentCluster: false,\n contentSecurityPolicy: {\n useDefaults: true,\n directives: {\n 'connect-src': [\"'self'\", 'https:'],\n 'img-src': [\"'self'\", 'data:', 'blob:', 'https://market-assets.strapi.io'],\n 'media-src': [\"'self'\", 'data:', 'blob:'],\n upgradeInsecureRequests: null,\n },\n },\n xssFilter: false,\n hsts: {\n maxAge: 31536000,\n includeSubDomains: true,\n },\n frameguard: {\n action: 'sameorigin',\n },\n};\n\nconst mergeConfig = (existingConfig: Config, newConfig: Config) => {\n return mergeWith(\n (obj, src) => (Array.isArray(obj) && Array.isArray(src) ? obj.concat(src) : undefined),\n existingConfig,\n newConfig\n );\n};\n\nexport const security: Core.MiddlewareFactory<Config> =\n (config, { strapi }) =>\n (ctx, next) => {\n let helmetConfig: Config = defaultsDeep(defaults, config);\n\n const specialPaths = ['/documentation'];\n\n const directives: {\n 'script-src': string[];\n 'img-src': string[];\n 'manifest-src': string[];\n 'frame-src': string[];\n } = {\n 'script-src': [\"'self'\", \"'unsafe-inline'\", 'cdn.jsdelivr.net'],\n 'img-src': [\"'self'\", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],\n 'manifest-src': [],\n 'frame-src': [],\n };\n\n // if apollo graphql playground is enabled, add exceptions for it\n if (strapi.plugin('graphql')?.service('utils').playground.isEnabled()) {\n const { config: gqlConfig } = strapi.plugin('graphql');\n specialPaths.push(gqlConfig('endpoint'));\n\n directives['script-src'].push(`https: 'unsafe-inline'`);\n directives['img-src'].push(`'apollo-server-landing-page.cdn.apollographql.com'`);\n directives['manifest-src'].push(`'self'`);\n directives['manifest-src'].push('apollo-server-landing-page.cdn.apollographql.com');\n directives['frame-src'].push(`'self'`);\n directives['frame-src'].push('sandbox.embed.apollographql.com');\n }\n\n // TODO: we shouldn't combine playground exceptions with documentation for all routes, we should first check the path and then return exceptions specific to that\n if (ctx.method === 'GET' && specialPaths.some((str) => ctx.path.startsWith(str))) {\n helmetConfig = mergeConfig(helmetConfig, {\n crossOriginEmbedderPolicy: false, // TODO: only use this for graphql playground\n contentSecurityPolicy: {\n directives,\n },\n });\n }\n\n /**\n * These are for vite's watch mode so it can accurately\n * connect to the HMR websocket & reconnect on failure\n * or when the server restarts.\n *\n * It only applies in development, and only on GET requests\n * that are part of the admin route.\n */\n if (\n ['development', 'test'].includes(process.env.NODE_ENV ?? '') &&\n ctx.method === 'GET' &&\n ['/admin'].some((str) => ctx.path.startsWith(str))\n ) {\n helmetConfig = mergeConfig(helmetConfig, {\n contentSecurityPolicy: {\n directives: {\n 'script-src': [\"'self'\", \"'unsafe-inline'\"],\n 'connect-src': [\"'self'\", 'http:', 'https:', 'ws:'],\n },\n },\n });\n }\n\n return helmet(helmetConfig)(ctx, next);\n };\n"],"names":["mergeWith","defaultsDeep","helmet"],"mappings":";;;;;;AAOA,MAAM,WAAmB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,oBAAoB;AAAA,EACpB,uBAAuB;AAAA,IACrB,aAAa;AAAA,IACb,YAAY;AAAA,MACV,eAAe,CAAC,UAAU,QAAQ;AAAA,MAClC,WAAW,CAAC,UAAU,SAAS,SAAS,iCAAiC;AAAA,MACzE,aAAa,CAAC,UAAU,SAAS,OAAO;AAAA,MACxC,yBAAyB;AAAA,IAC3B;AAAA,EACF;AAAA,EACA,WAAW;AAAA,EACX,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,mBAAmB;AAAA,EACrB;AAAA,EACA,YAAY;AAAA,IACV,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,cAAc,CAAC,gBAAwB,cAAsB;AAC1D,SAAAA,EAAA;AAAA,IACL,CAAC,KAAK,QAAS,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,GAAG,IAAI,IAAI,OAAO,GAAG,IAAI;AAAA,IAC5E;AAAA,IACA;AAAA,EAAA;AAEJ;AAEa,MAAA,WACX,CAAC,QAAQ,EAAE,aACX,CAAC,KAAK,SAAS;AACT,MAAA,eAAuBC,EAAAA,aAAa,UAAU,MAAM;AAElD,QAAA,eAAe,CAAC,gBAAgB;AAEtC,QAAM,aAKF;AAAA,IACF,cAAc,CAAC,UAAU,mBAAmB,kBAAkB;AAAA,IAC9D,WAAW,CAAC,UAAU,SAAS,oBAAoB,WAAW;AAAA,IAC9D,gBAAgB,CAAC;AAAA,IACjB,aAAa,CAAC;AAAA,EAAA;AAIZ,MAAA,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,EAAE,WAAW,aAAa;AACrE,UAAM,EAAE,QAAQ,UAAA,IAAc,OAAO,OAAO,SAAS;AACxC,iBAAA,KAAK,UAAU,UAAU,CAAC;AAE5B,eAAA,YAAY,EAAE,KAAK,wBAAwB;AAC3C,eAAA,SAAS,EAAE,KAAK,oDAAoD;AACpE,eAAA,cAAc,EAAE,KAAK,QAAQ;AAC7B,eAAA,cAAc,EAAE,KAAK,kDAAkD;AACvE,eAAA,WAAW,EAAE,KAAK,QAAQ;AAC1B,eAAA,WAAW,EAAE,KAAK,iCAAiC;AAAA,EAChE;AAGA,MAAI,IAAI,WAAW,SAAS,aAAa,KAAK,CAAC,QAAQ,IAAI,KAAK,WAAW,GAAG,CAAC,GAAG;AAChF,mBAAe,YAAY,cAAc;AAAA,MACvC,2BAA2B;AAAA;AAAA,MAC3B,uBAAuB;AAAA,QACrB;AAAA,MACF;AAAA,IAAA,CACD;AAAA,EACH;AAWE,MAAA,CAAC,eAAe,MAAM,EAAE,SAAS,QAAQ,IAAI,YAAY,EAAE,KAC3D,IAAI,WAAW,SACf,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,WAAW,GAAG,CAAC,GACjD;AACA,mBAAe,YAAY,cAAc;AAAA,MACvC,uBAAuB;AAAA,QACrB,YAAY;AAAA,UACV,cAAc,CAAC,UAAU,iBAAiB;AAAA,UAC1C,eAAe,CAAC,UAAU,SAAS,UAAU,KAAK;AAAA,QACpD;AAAA,MACF;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAOC,gBAAO,QAAA,YAAY,EAAE,KAAK,IAAI;AACvC;;"}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { defaultsDeep,
|
1
|
+
import { defaultsDeep, mergeWith } from "lodash/fp";
|
2
2
|
import helmet from "koa-helmet";
|
3
3
|
const defaults = {
|
4
4
|
crossOriginEmbedderPolicy: false,
|
@@ -23,6 +23,13 @@ const defaults = {
|
|
23
23
|
action: "sameorigin"
|
24
24
|
}
|
25
25
|
};
|
26
|
+
const mergeConfig = (existingConfig, newConfig) => {
|
27
|
+
return mergeWith(
|
28
|
+
(obj, src) => Array.isArray(obj) && Array.isArray(src) ? obj.concat(src) : void 0,
|
29
|
+
existingConfig,
|
30
|
+
newConfig
|
31
|
+
);
|
32
|
+
};
|
26
33
|
const security = (config, { strapi }) => (ctx, next) => {
|
27
34
|
let helmetConfig = defaultsDeep(defaults, config);
|
28
35
|
const specialPaths = ["/documentation"];
|
@@ -43,7 +50,7 @@ const security = (config, { strapi }) => (ctx, next) => {
|
|
43
50
|
directives["frame-src"].push("sandbox.embed.apollographql.com");
|
44
51
|
}
|
45
52
|
if (ctx.method === "GET" && specialPaths.some((str) => ctx.path.startsWith(str))) {
|
46
|
-
helmetConfig =
|
53
|
+
helmetConfig = mergeConfig(helmetConfig, {
|
47
54
|
crossOriginEmbedderPolicy: false,
|
48
55
|
// TODO: only use this for graphql playground
|
49
56
|
contentSecurityPolicy: {
|
@@ -51,8 +58,8 @@ const security = (config, { strapi }) => (ctx, next) => {
|
|
51
58
|
}
|
52
59
|
});
|
53
60
|
}
|
54
|
-
if (process.env.NODE_ENV
|
55
|
-
helmetConfig =
|
61
|
+
if (["development", "test"].includes(process.env.NODE_ENV ?? "") && ctx.method === "GET" && ["/admin"].some((str) => ctx.path.startsWith(str))) {
|
62
|
+
helmetConfig = mergeConfig(helmetConfig, {
|
56
63
|
contentSecurityPolicy: {
|
57
64
|
directives: {
|
58
65
|
"script-src": ["'self'", "'unsafe-inline'"],
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"security.mjs","sources":["../../src/middlewares/security.ts"],"sourcesContent":["import { defaultsDeep,
|
1
|
+
{"version":3,"file":"security.mjs","sources":["../../src/middlewares/security.ts"],"sourcesContent":["import { defaultsDeep, mergeWith } from 'lodash/fp';\nimport helmet, { KoaHelmet } from 'koa-helmet';\n\nimport type { Core } from '@strapi/types';\n\nexport type Config = NonNullable<Parameters<KoaHelmet>[0]>;\n\nconst defaults: Config = {\n crossOriginEmbedderPolicy: false,\n crossOriginOpenerPolicy: false,\n crossOriginResourcePolicy: false,\n originAgentCluster: false,\n contentSecurityPolicy: {\n useDefaults: true,\n directives: {\n 'connect-src': [\"'self'\", 'https:'],\n 'img-src': [\"'self'\", 'data:', 'blob:', 'https://market-assets.strapi.io'],\n 'media-src': [\"'self'\", 'data:', 'blob:'],\n upgradeInsecureRequests: null,\n },\n },\n xssFilter: false,\n hsts: {\n maxAge: 31536000,\n includeSubDomains: true,\n },\n frameguard: {\n action: 'sameorigin',\n },\n};\n\nconst mergeConfig = (existingConfig: Config, newConfig: Config) => {\n return mergeWith(\n (obj, src) => (Array.isArray(obj) && Array.isArray(src) ? obj.concat(src) : undefined),\n existingConfig,\n newConfig\n );\n};\n\nexport const security: Core.MiddlewareFactory<Config> =\n (config, { strapi }) =>\n (ctx, next) => {\n let helmetConfig: Config = defaultsDeep(defaults, config);\n\n const specialPaths = ['/documentation'];\n\n const directives: {\n 'script-src': string[];\n 'img-src': string[];\n 'manifest-src': string[];\n 'frame-src': string[];\n } = {\n 'script-src': [\"'self'\", \"'unsafe-inline'\", 'cdn.jsdelivr.net'],\n 'img-src': [\"'self'\", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],\n 'manifest-src': [],\n 'frame-src': [],\n };\n\n // if apollo graphql playground is enabled, add exceptions for it\n if (strapi.plugin('graphql')?.service('utils').playground.isEnabled()) {\n const { config: gqlConfig } = strapi.plugin('graphql');\n specialPaths.push(gqlConfig('endpoint'));\n\n directives['script-src'].push(`https: 'unsafe-inline'`);\n directives['img-src'].push(`'apollo-server-landing-page.cdn.apollographql.com'`);\n directives['manifest-src'].push(`'self'`);\n directives['manifest-src'].push('apollo-server-landing-page.cdn.apollographql.com');\n directives['frame-src'].push(`'self'`);\n directives['frame-src'].push('sandbox.embed.apollographql.com');\n }\n\n // TODO: we shouldn't combine playground exceptions with documentation for all routes, we should first check the path and then return exceptions specific to that\n if (ctx.method === 'GET' && specialPaths.some((str) => ctx.path.startsWith(str))) {\n helmetConfig = mergeConfig(helmetConfig, {\n crossOriginEmbedderPolicy: false, // TODO: only use this for graphql playground\n contentSecurityPolicy: {\n directives,\n },\n });\n }\n\n /**\n * These are for vite's watch mode so it can accurately\n * connect to the HMR websocket & reconnect on failure\n * or when the server restarts.\n *\n * It only applies in development, and only on GET requests\n * that are part of the admin route.\n */\n if (\n ['development', 'test'].includes(process.env.NODE_ENV ?? '') &&\n ctx.method === 'GET' &&\n ['/admin'].some((str) => ctx.path.startsWith(str))\n ) {\n helmetConfig = mergeConfig(helmetConfig, {\n contentSecurityPolicy: {\n directives: {\n 'script-src': [\"'self'\", \"'unsafe-inline'\"],\n 'connect-src': [\"'self'\", 'http:', 'https:', 'ws:'],\n },\n },\n });\n }\n\n return helmet(helmetConfig)(ctx, next);\n };\n"],"names":[],"mappings":";;AAOA,MAAM,WAAmB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,oBAAoB;AAAA,EACpB,uBAAuB;AAAA,IACrB,aAAa;AAAA,IACb,YAAY;AAAA,MACV,eAAe,CAAC,UAAU,QAAQ;AAAA,MAClC,WAAW,CAAC,UAAU,SAAS,SAAS,iCAAiC;AAAA,MACzE,aAAa,CAAC,UAAU,SAAS,OAAO;AAAA,MACxC,yBAAyB;AAAA,IAC3B;AAAA,EACF;AAAA,EACA,WAAW;AAAA,EACX,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,mBAAmB;AAAA,EACrB;AAAA,EACA,YAAY;AAAA,IACV,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,cAAc,CAAC,gBAAwB,cAAsB;AAC1D,SAAA;AAAA,IACL,CAAC,KAAK,QAAS,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,GAAG,IAAI,IAAI,OAAO,GAAG,IAAI;AAAA,IAC5E;AAAA,IACA;AAAA,EAAA;AAEJ;AAEa,MAAA,WACX,CAAC,QAAQ,EAAE,aACX,CAAC,KAAK,SAAS;AACT,MAAA,eAAuB,aAAa,UAAU,MAAM;AAElD,QAAA,eAAe,CAAC,gBAAgB;AAEtC,QAAM,aAKF;AAAA,IACF,cAAc,CAAC,UAAU,mBAAmB,kBAAkB;AAAA,IAC9D,WAAW,CAAC,UAAU,SAAS,oBAAoB,WAAW;AAAA,IAC9D,gBAAgB,CAAC;AAAA,IACjB,aAAa,CAAC;AAAA,EAAA;AAIZ,MAAA,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,EAAE,WAAW,aAAa;AACrE,UAAM,EAAE,QAAQ,UAAA,IAAc,OAAO,OAAO,SAAS;AACxC,iBAAA,KAAK,UAAU,UAAU,CAAC;AAE5B,eAAA,YAAY,EAAE,KAAK,wBAAwB;AAC3C,eAAA,SAAS,EAAE,KAAK,oDAAoD;AACpE,eAAA,cAAc,EAAE,KAAK,QAAQ;AAC7B,eAAA,cAAc,EAAE,KAAK,kDAAkD;AACvE,eAAA,WAAW,EAAE,KAAK,QAAQ;AAC1B,eAAA,WAAW,EAAE,KAAK,iCAAiC;AAAA,EAChE;AAGA,MAAI,IAAI,WAAW,SAAS,aAAa,KAAK,CAAC,QAAQ,IAAI,KAAK,WAAW,GAAG,CAAC,GAAG;AAChF,mBAAe,YAAY,cAAc;AAAA,MACvC,2BAA2B;AAAA;AAAA,MAC3B,uBAAuB;AAAA,QACrB;AAAA,MACF;AAAA,IAAA,CACD;AAAA,EACH;AAWE,MAAA,CAAC,eAAe,MAAM,EAAE,SAAS,QAAQ,IAAI,YAAY,EAAE,KAC3D,IAAI,WAAW,SACf,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,WAAW,GAAG,CAAC,GACjD;AACA,mBAAe,YAAY,cAAc;AAAA,MACvC,uBAAuB;AAAA,QACrB,YAAY;AAAA,UACV,cAAc,CAAC,UAAU,iBAAiB;AAAA,UAC1C,eAAe,CAAC,UAAU,SAAS,UAAU,KAAK;AAAA,QACpD;AAAA,MACF;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAO,OAAO,YAAY,EAAE,KAAK,IAAI;AACvC;"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"populate.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/populate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGpC,UAAU,OAAO;IACf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAGD,eAAO,MAAM,eAAe,QAAS,IAAI,MAAM,SAAQ,OAAO,
|
1
|
+
{"version":3,"file":"populate.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/populate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGpC,UAAU,OAAO;IACf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAGD,eAAO,MAAM,eAAe,QAAS,IAAI,MAAM,SAAQ,OAAO,QAoD7D,CAAC"}
|
@@ -7,6 +7,10 @@ const getDeepPopulate = (uid, opts = {}) => {
|
|
7
7
|
return attributes.reduce((acc, [attributeName, attribute]) => {
|
8
8
|
switch (attribute.type) {
|
9
9
|
case "relation": {
|
10
|
+
const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
|
11
|
+
if (isMorphRelation) {
|
12
|
+
break;
|
13
|
+
}
|
10
14
|
const isVisible = strapiUtils.contentTypes.isVisibleAttribute(model, attributeName);
|
11
15
|
if (isVisible) {
|
12
16
|
acc[attributeName] = { select: opts.relationalFields };
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"populate.js","sources":["../../../../src/services/document-service/utils/populate.ts"],"sourcesContent":["import { UID } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\ninterface Options {\n /**\n * Fields to select when populating relations\n */\n relationalFields?: string[];\n}\n\n// We want to build a populate object based on the schema\nexport const getDeepPopulate = (uid: UID.Schema, opts: Options = {}) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Should this just be a plain list?\n // Ignore createdBy, updatedBy, ...\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { select: opts.relationalFields };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { select: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component, opts);\n acc[attributeName] = { populate };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = { populate: getDeepPopulate(componentUID, opts) };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n};\n"],"names":["contentTypes","acc"],"mappings":";;;AAWO,MAAM,kBAAkB,CAAC,KAAiB,OAAgB,OAAO;AAChE,QAAA,QAAQ,OAAO,SAAS,GAAG;AACjC,QAAM,aAAa,OAAO,QAAQ,MAAM,UAAU;AAElD,SAAO,WAAW,OAAO,CAAC,KAAU,CAAC,eAAe,SAAS,MAAM;AACjE,YAAQ,UAAU,MAAM;AAAA,MACtB,KAAK,YAAY;
|
1
|
+
{"version":3,"file":"populate.js","sources":["../../../../src/services/document-service/utils/populate.ts"],"sourcesContent":["import { UID } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\ninterface Options {\n /**\n * Fields to select when populating relations\n */\n relationalFields?: string[];\n}\n\n// We want to build a populate object based on the schema\nexport const getDeepPopulate = (uid: UID.Schema, opts: Options = {}) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Support polymorphic relations\n const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');\n if (isMorphRelation) {\n break;\n }\n\n // TODO: Should this just be a plain list?\n // Ignore createdBy, updatedBy, ...\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { select: opts.relationalFields };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { select: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component, opts);\n acc[attributeName] = { populate };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = { populate: getDeepPopulate(componentUID, opts) };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n};\n"],"names":["contentTypes","acc"],"mappings":";;;AAWO,MAAM,kBAAkB,CAAC,KAAiB,OAAgB,OAAO;AAChE,QAAA,QAAQ,OAAO,SAAS,GAAG;AACjC,QAAM,aAAa,OAAO,QAAQ,MAAM,UAAU;AAElD,SAAO,WAAW,OAAO,CAAC,KAAU,CAAC,eAAe,SAAS,MAAM;AACjE,YAAQ,UAAU,MAAM;AAAA,MACtB,KAAK,YAAY;AAEf,cAAM,kBAAkB,UAAU,SAAS,YAAY,EAAE,WAAW,OAAO;AAC3E,YAAI,iBAAiB;AACnB;AAAA,QACF;AAIA,cAAM,YAAYA,YAAA,aAAa,mBAAmB,OAAO,aAAa;AACtE,YAAI,WAAW;AACb,cAAI,aAAa,IAAI,EAAE,QAAQ,KAAK,iBAAiB;AAAA,QACvD;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,YAAI,aAAa,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE;AACtC;AAAA,MACF;AAAA,MAEA,KAAK,aAAa;AAChB,cAAM,WAAW,gBAAgB,UAAU,WAAW,IAAI;AACtD,YAAA,aAAa,IAAI,EAAE;AACvB;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAElB,cAAM,uBAAuB,UAAU,cAAc,CAAI,GAAA;AAAA,UACvD,CAACC,MAAU,iBAAgC;AACzCA,iBAAI,YAAY,IAAI,EAAE,UAAU,gBAAgB,cAAc,IAAI;AAC3DA,mBAAAA;AAAAA,UACT;AAAA,UACA,CAAC;AAAA,QAAA;AAGH,YAAI,aAAa,IAAI,EAAE,IAAI,oBAAoB;AAC/C;AAAA,MACF;AAAA,IAGF;AAEO,WAAA;AAAA,EACT,GAAG,CAAE,CAAA;AACP;;"}
|
@@ -5,6 +5,10 @@ const getDeepPopulate = (uid, opts = {}) => {
|
|
5
5
|
return attributes.reduce((acc, [attributeName, attribute]) => {
|
6
6
|
switch (attribute.type) {
|
7
7
|
case "relation": {
|
8
|
+
const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
|
9
|
+
if (isMorphRelation) {
|
10
|
+
break;
|
11
|
+
}
|
8
12
|
const isVisible = contentTypes.isVisibleAttribute(model, attributeName);
|
9
13
|
if (isVisible) {
|
10
14
|
acc[attributeName] = { select: opts.relationalFields };
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"populate.mjs","sources":["../../../../src/services/document-service/utils/populate.ts"],"sourcesContent":["import { UID } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\ninterface Options {\n /**\n * Fields to select when populating relations\n */\n relationalFields?: string[];\n}\n\n// We want to build a populate object based on the schema\nexport const getDeepPopulate = (uid: UID.Schema, opts: Options = {}) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Should this just be a plain list?\n // Ignore createdBy, updatedBy, ...\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { select: opts.relationalFields };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { select: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component, opts);\n acc[attributeName] = { populate };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = { populate: getDeepPopulate(componentUID, opts) };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n};\n"],"names":["acc"],"mappings":";AAWO,MAAM,kBAAkB,CAAC,KAAiB,OAAgB,OAAO;AAChE,QAAA,QAAQ,OAAO,SAAS,GAAG;AACjC,QAAM,aAAa,OAAO,QAAQ,MAAM,UAAU;AAElD,SAAO,WAAW,OAAO,CAAC,KAAU,CAAC,eAAe,SAAS,MAAM;AACjE,YAAQ,UAAU,MAAM;AAAA,MACtB,KAAK,YAAY;
|
1
|
+
{"version":3,"file":"populate.mjs","sources":["../../../../src/services/document-service/utils/populate.ts"],"sourcesContent":["import { UID } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\ninterface Options {\n /**\n * Fields to select when populating relations\n */\n relationalFields?: string[];\n}\n\n// We want to build a populate object based on the schema\nexport const getDeepPopulate = (uid: UID.Schema, opts: Options = {}) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Support polymorphic relations\n const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');\n if (isMorphRelation) {\n break;\n }\n\n // TODO: Should this just be a plain list?\n // Ignore createdBy, updatedBy, ...\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { select: opts.relationalFields };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { select: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component, opts);\n acc[attributeName] = { populate };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = { populate: getDeepPopulate(componentUID, opts) };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n};\n"],"names":["acc"],"mappings":";AAWO,MAAM,kBAAkB,CAAC,KAAiB,OAAgB,OAAO;AAChE,QAAA,QAAQ,OAAO,SAAS,GAAG;AACjC,QAAM,aAAa,OAAO,QAAQ,MAAM,UAAU;AAElD,SAAO,WAAW,OAAO,CAAC,KAAU,CAAC,eAAe,SAAS,MAAM;AACjE,YAAQ,UAAU,MAAM;AAAA,MACtB,KAAK,YAAY;AAEf,cAAM,kBAAkB,UAAU,SAAS,YAAY,EAAE,WAAW,OAAO;AAC3E,YAAI,iBAAiB;AACnB;AAAA,QACF;AAIA,cAAM,YAAY,aAAa,mBAAmB,OAAO,aAAa;AACtE,YAAI,WAAW;AACb,cAAI,aAAa,IAAI,EAAE,QAAQ,KAAK,iBAAiB;AAAA,QACvD;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,YAAI,aAAa,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE;AACtC;AAAA,MACF;AAAA,MAEA,KAAK,aAAa;AAChB,cAAM,WAAW,gBAAgB,UAAU,WAAW,IAAI;AACtD,YAAA,aAAa,IAAI,EAAE;AACvB;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAElB,cAAM,uBAAuB,UAAU,cAAc,CAAI,GAAA;AAAA,UACvD,CAACA,MAAU,iBAAgC;AACzCA,iBAAI,YAAY,IAAI,EAAE,UAAU,gBAAgB,cAAc,IAAI;AAC3DA,mBAAAA;AAAAA,UACT;AAAA,UACA,CAAC;AAAA,QAAA;AAGH,YAAI,aAAa,IAAI,EAAE,IAAI,oBAAoB;AAC/C;AAAA,MACF;AAAA,IAGF;AAEO,WAAA;AAAA,EACT,GAAG,CAAE,CAAA;AACP;"}
|
@@ -2,7 +2,20 @@
|
|
2
2
|
* Entity validator
|
3
3
|
* Module that will validate input data for entity creation or edition
|
4
4
|
*/
|
5
|
-
import { Modules } from '@strapi/types';
|
5
|
+
import { Modules, Struct } from '@strapi/types';
|
6
|
+
export type ComponentContext = {
|
7
|
+
parentContent: {
|
8
|
+
model: Struct.Schema;
|
9
|
+
id?: number;
|
10
|
+
options?: ValidatorContext;
|
11
|
+
};
|
12
|
+
pathToComponent: string[];
|
13
|
+
repeatableData: Modules.EntityValidator.Entity[];
|
14
|
+
};
|
15
|
+
interface ValidatorContext {
|
16
|
+
isDraft?: boolean;
|
17
|
+
locale?: string | null;
|
18
|
+
}
|
6
19
|
declare const entityValidator: Modules.EntityValidator.EntityValidator;
|
7
20
|
export default entityValidator;
|
8
21
|
//# sourceMappingURL=index.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/entity-validator/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,OAAO,
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/entity-validator/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,OAAO,EAAO,MAAM,EAAU,MAAM,eAAe,CAAC;AAa7D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,aAAa,EAAE;QAEb,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;QAErB,EAAE,CAAC,EAAE,MAAM,CAAC;QAGZ,OAAO,CAAC,EAAE,gBAAgB,CAAC;KAC5B,CAAC;IAEF,eAAe,EAAE,MAAM,EAAE,CAAC;IAG1B,cAAc,EAAE,OAAO,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;CAClD,CAAC;AAWF,UAAU,gBAAgB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AA8hBD,QAAA,MAAM,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,eAG9C,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
@@ -55,7 +55,11 @@ const addDefault = (createOrUpdate) => {
|
|
55
55
|
};
|
56
56
|
};
|
57
57
|
const preventCast = (validator) => validator.transform((val, originalVal) => originalVal);
|
58
|
-
const createComponentValidator = (createOrUpdate) => ({
|
58
|
+
const createComponentValidator = (createOrUpdate) => ({
|
59
|
+
attr,
|
60
|
+
updatedAttribute,
|
61
|
+
componentContext
|
62
|
+
}, { isDraft }) => {
|
59
63
|
const model = strapi.getModel(attr.component);
|
60
64
|
if (!model) {
|
61
65
|
throw new Error("Validation failed: Model not found");
|
@@ -63,7 +67,10 @@ const createComponentValidator = (createOrUpdate) => ({ attr, updatedAttribute }
|
|
63
67
|
if (attr?.repeatable) {
|
64
68
|
let validator2 = yup.array().of(
|
65
69
|
yup.lazy(
|
66
|
-
(item) => createModelValidator(createOrUpdate)(
|
70
|
+
(item) => createModelValidator(createOrUpdate)(
|
71
|
+
{ componentContext, model, data: item },
|
72
|
+
{ isDraft }
|
73
|
+
).notNull()
|
67
74
|
)
|
68
75
|
);
|
69
76
|
validator2 = addRequiredValidation(createOrUpdate)(validator2, {
|
@@ -74,7 +81,11 @@ const createComponentValidator = (createOrUpdate) => ({ attr, updatedAttribute }
|
|
74
81
|
return validator2;
|
75
82
|
}
|
76
83
|
let validator = createModelValidator(createOrUpdate)(
|
77
|
-
{
|
84
|
+
{
|
85
|
+
model,
|
86
|
+
data: updatedAttribute.value,
|
87
|
+
componentContext
|
88
|
+
},
|
78
89
|
{ isDraft }
|
79
90
|
);
|
80
91
|
validator = addRequiredValidation(createOrUpdate)(validator, {
|
@@ -83,7 +94,7 @@ const createComponentValidator = (createOrUpdate) => ({ attr, updatedAttribute }
|
|
83
94
|
});
|
84
95
|
return validator;
|
85
96
|
};
|
86
|
-
const createDzValidator = (createOrUpdate) => ({ attr, updatedAttribute }, { isDraft }) => {
|
97
|
+
const createDzValidator = (createOrUpdate) => ({ attr, updatedAttribute, componentContext }, { isDraft }) => {
|
87
98
|
let validator;
|
88
99
|
validator = yup.array().of(
|
89
100
|
yup.lazy((item) => {
|
@@ -91,7 +102,12 @@ const createDzValidator = (createOrUpdate) => ({ attr, updatedAttribute }, { isD
|
|
91
102
|
const schema = yup.object().shape({
|
92
103
|
__component: yup.string().required().oneOf(Object.keys(strapi.components))
|
93
104
|
}).notNull();
|
94
|
-
return model ? schema.concat(
|
105
|
+
return model ? schema.concat(
|
106
|
+
createModelValidator(createOrUpdate)(
|
107
|
+
{ model, data: item, componentContext },
|
108
|
+
{ isDraft }
|
109
|
+
)
|
110
|
+
) : schema;
|
95
111
|
})
|
96
112
|
// FIXME: yup v1
|
97
113
|
);
|
@@ -102,17 +118,15 @@ const createDzValidator = (createOrUpdate) => ({ attr, updatedAttribute }, { isD
|
|
102
118
|
validator = addMinMax(validator, { attr, updatedAttribute });
|
103
119
|
return validator;
|
104
120
|
};
|
105
|
-
const createRelationValidator = (
|
121
|
+
const createRelationValidator = ({
|
122
|
+
updatedAttribute
|
123
|
+
}) => {
|
106
124
|
let validator;
|
107
125
|
if (Array.isArray(updatedAttribute.value)) {
|
108
126
|
validator = yup.array().of(yup.mixed());
|
109
127
|
} else {
|
110
128
|
validator = yup.mixed();
|
111
129
|
}
|
112
|
-
validator = addRequiredValidation(createOrUpdate)(validator, {
|
113
|
-
attr: { required: !isDraft && attr.required },
|
114
|
-
updatedAttribute
|
115
|
-
});
|
116
130
|
return validator;
|
117
131
|
};
|
118
132
|
const createScalarAttributeValidator = (createOrUpdate) => (metas, options) => {
|
@@ -136,27 +150,49 @@ const createAttributeValidator = (createOrUpdate) => (metas, options) => {
|
|
136
150
|
validator = createScalarAttributeValidator(createOrUpdate)(metas, options);
|
137
151
|
} else {
|
138
152
|
if (metas.attr.type === "component") {
|
153
|
+
const pathToComponent = [
|
154
|
+
...metas?.componentContext?.pathToComponent ?? [],
|
155
|
+
metas.updatedAttribute.name
|
156
|
+
];
|
157
|
+
const repeatableData = metas.attr.repeatable && pathToComponent.length === 1 ? metas.updatedAttribute.value : metas.componentContext?.repeatableData;
|
158
|
+
const newComponentContext = {
|
159
|
+
...metas?.componentContext ?? {},
|
160
|
+
pathToComponent,
|
161
|
+
repeatableData
|
162
|
+
};
|
139
163
|
validator = createComponentValidator(createOrUpdate)(
|
140
|
-
{ attr: metas.attr, updatedAttribute: metas.updatedAttribute },
|
141
|
-
options
|
142
|
-
);
|
143
|
-
} else if (metas.attr.type === "dynamiczone") {
|
144
|
-
validator = createDzValidator(createOrUpdate)(metas, options);
|
145
|
-
} else if (metas.attr.type === "relation") {
|
146
|
-
validator = createRelationValidator(createOrUpdate)(
|
147
164
|
{
|
165
|
+
componentContext: newComponentContext,
|
148
166
|
attr: metas.attr,
|
149
167
|
updatedAttribute: metas.updatedAttribute
|
150
168
|
},
|
151
169
|
options
|
152
170
|
);
|
171
|
+
} else if (metas.attr.type === "dynamiczone") {
|
172
|
+
const pathToComponent = [
|
173
|
+
...metas?.componentContext?.pathToComponent ?? [],
|
174
|
+
metas.updatedAttribute.name
|
175
|
+
];
|
176
|
+
const newComponentContext = {
|
177
|
+
...metas?.componentContext ?? {},
|
178
|
+
pathToComponent
|
179
|
+
};
|
180
|
+
validator = createDzValidator(createOrUpdate)(
|
181
|
+
{ ...metas, componentContext: newComponentContext },
|
182
|
+
options
|
183
|
+
);
|
184
|
+
} else if (metas.attr.type === "relation") {
|
185
|
+
validator = createRelationValidator({
|
186
|
+
attr: metas.attr,
|
187
|
+
updatedAttribute: metas.updatedAttribute
|
188
|
+
});
|
153
189
|
}
|
154
190
|
validator = preventCast(validator);
|
155
191
|
}
|
156
192
|
validator = addDefault(createOrUpdate)(validator, metas);
|
157
193
|
return validator;
|
158
194
|
};
|
159
|
-
const createModelValidator = (createOrUpdate) => ({ model, data, entity }, options) => {
|
195
|
+
const createModelValidator = (createOrUpdate) => ({ componentContext, model, data, entity }, options) => {
|
160
196
|
const writableAttributes = model ? getWritableAttributes(model) : [];
|
161
197
|
const schema = writableAttributes.reduce(
|
162
198
|
(validators2, attributeName) => {
|
@@ -164,7 +200,8 @@ const createModelValidator = (createOrUpdate) => ({ model, data, entity }, optio
|
|
164
200
|
attr: model.attributes[attributeName],
|
165
201
|
updatedAttribute: { name: attributeName, value: _.prop(attributeName, data) },
|
166
202
|
model,
|
167
|
-
entity
|
203
|
+
entity,
|
204
|
+
componentContext
|
168
205
|
};
|
169
206
|
const validator = createAttributeValidator(createOrUpdate)(metas, options);
|
170
207
|
validators2[attributeName] = validator;
|
@@ -183,22 +220,42 @@ const createValidateEntity = (createOrUpdate) => {
|
|
183
220
|
);
|
184
221
|
}
|
185
222
|
const validator = createModelValidator(createOrUpdate)(
|
186
|
-
{
|
223
|
+
{
|
224
|
+
model,
|
225
|
+
data,
|
226
|
+
entity,
|
227
|
+
componentContext: {
|
228
|
+
// Set up the initial component context.
|
229
|
+
// Keeping track of parent content type context in which a component will be used.
|
230
|
+
// This is necessary to validate component field constraints such as uniqueness.
|
231
|
+
parentContent: {
|
232
|
+
id: entity?.id,
|
233
|
+
model,
|
234
|
+
options
|
235
|
+
},
|
236
|
+
pathToComponent: [],
|
237
|
+
repeatableData: []
|
238
|
+
}
|
239
|
+
},
|
187
240
|
{
|
188
241
|
isDraft: options?.isDraft ?? false,
|
189
242
|
locale: options?.locale ?? null
|
190
243
|
}
|
191
|
-
).test(
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
244
|
+
).test(
|
245
|
+
"relations-test",
|
246
|
+
"check that all relations exist",
|
247
|
+
async function relationsValidation(data2) {
|
248
|
+
try {
|
249
|
+
await checkRelationsExist(buildRelationsStore({ uid: model.uid, data: data2 }));
|
250
|
+
} catch (e) {
|
251
|
+
return this.createError({
|
252
|
+
path: this.path,
|
253
|
+
message: e instanceof ValidationError && e.message || "Invalid relations"
|
254
|
+
});
|
255
|
+
}
|
256
|
+
return true;
|
199
257
|
}
|
200
|
-
|
201
|
-
}).required();
|
258
|
+
).required();
|
202
259
|
return validateYupSchema(validator, {
|
203
260
|
strict: false,
|
204
261
|
abortEarly: false
|