webspresso 0.0.40 → 0.0.41
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/package.json +1 -1
- package/plugins/admin-panel/index.js +6 -0
- package/src/plugin-manager.js +40 -0
- package/src/server.js +36 -1
package/package.json
CHANGED
|
@@ -58,6 +58,12 @@ function adminPanelPlugin(options = {}) {
|
|
|
58
58
|
name: 'admin-panel',
|
|
59
59
|
version: '2.0.0',
|
|
60
60
|
description: 'Modular admin panel for Webspresso with extensions support',
|
|
61
|
+
|
|
62
|
+
// CSP requirements for Quill.js rich text editor
|
|
63
|
+
csp: {
|
|
64
|
+
styleSrc: ['https://cdn.quilljs.com'],
|
|
65
|
+
scriptSrc: ['https://cdn.quilljs.com'],
|
|
66
|
+
},
|
|
61
67
|
enabled,
|
|
62
68
|
registry, // Expose registry for external configuration
|
|
63
69
|
|
package/src/plugin-manager.js
CHANGED
|
@@ -122,6 +122,7 @@ class PluginManager {
|
|
|
122
122
|
this.registeredFilters = new Map(); // name -> filter function
|
|
123
123
|
this.routes = []; // Collected route metadata
|
|
124
124
|
this.customRoutes = []; // Routes added by plugins
|
|
125
|
+
this.cspDirectives = new Map(); // directive -> Set of sources (from plugins)
|
|
125
126
|
this.app = null;
|
|
126
127
|
this.nunjucksEnv = null;
|
|
127
128
|
}
|
|
@@ -263,6 +264,11 @@ class PluginManager {
|
|
|
263
264
|
this.pluginAPIs.set(plugin.name, boundAPI);
|
|
264
265
|
}
|
|
265
266
|
|
|
267
|
+
// Collect CSP requirements from plugin
|
|
268
|
+
if (plugin.csp) {
|
|
269
|
+
this._collectCspDirectives(plugin.csp);
|
|
270
|
+
}
|
|
271
|
+
|
|
266
272
|
// Call register hook
|
|
267
273
|
if (typeof plugin.register === 'function') {
|
|
268
274
|
await plugin.register(ctx);
|
|
@@ -295,6 +301,11 @@ class PluginManager {
|
|
|
295
301
|
this.pluginAPIs.set(plugin.name, boundAPI);
|
|
296
302
|
}
|
|
297
303
|
|
|
304
|
+
// Collect CSP requirements from plugin
|
|
305
|
+
if (plugin.csp) {
|
|
306
|
+
this._collectCspDirectives(plugin.csp);
|
|
307
|
+
}
|
|
308
|
+
|
|
298
309
|
// Call register hook (sync - if plugin has async register, it won't wait)
|
|
299
310
|
if (typeof plugin.register === 'function') {
|
|
300
311
|
plugin.register(ctx);
|
|
@@ -436,6 +447,35 @@ class PluginManager {
|
|
|
436
447
|
}
|
|
437
448
|
}
|
|
438
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Collect CSP directives from a plugin
|
|
452
|
+
* @param {Object} csp - CSP directives object { styleSrc: [...], scriptSrc: [...], ... }
|
|
453
|
+
*/
|
|
454
|
+
_collectCspDirectives(csp) {
|
|
455
|
+
for (const [directive, sources] of Object.entries(csp)) {
|
|
456
|
+
if (!this.cspDirectives.has(directive)) {
|
|
457
|
+
this.cspDirectives.set(directive, new Set());
|
|
458
|
+
}
|
|
459
|
+
const directiveSet = this.cspDirectives.get(directive);
|
|
460
|
+
const sourceArray = Array.isArray(sources) ? sources : [sources];
|
|
461
|
+
for (const source of sourceArray) {
|
|
462
|
+
directiveSet.add(source);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get merged CSP directives from all plugins
|
|
469
|
+
* @returns {Object} CSP directives object to merge with helmet config
|
|
470
|
+
*/
|
|
471
|
+
getCspDirectives() {
|
|
472
|
+
const directives = {};
|
|
473
|
+
for (const [directive, sources] of this.cspDirectives) {
|
|
474
|
+
directives[directive] = Array.from(sources);
|
|
475
|
+
}
|
|
476
|
+
return directives;
|
|
477
|
+
}
|
|
478
|
+
|
|
439
479
|
/**
|
|
440
480
|
* Get all registered helpers (to be merged with fsy)
|
|
441
481
|
*/
|
package/src/server.js
CHANGED
|
@@ -203,10 +203,45 @@ function createApp(options = {}) {
|
|
|
203
203
|
// Security headers with Helmet
|
|
204
204
|
if (helmetConfig !== false) {
|
|
205
205
|
const defaultConfig = getDefaultHelmetConfig(isDev);
|
|
206
|
-
|
|
206
|
+
let finalConfig = helmetConfig === undefined || helmetConfig === true
|
|
207
207
|
? defaultConfig
|
|
208
208
|
: { ...defaultConfig, ...helmetConfig };
|
|
209
209
|
|
|
210
|
+
// Collect CSP requirements from plugins (before they're fully registered)
|
|
211
|
+
if (plugins && Array.isArray(plugins) && finalConfig.contentSecurityPolicy) {
|
|
212
|
+
const pluginCspSources = {
|
|
213
|
+
styleSrc: new Set(),
|
|
214
|
+
scriptSrc: new Set(),
|
|
215
|
+
imgSrc: new Set(),
|
|
216
|
+
fontSrc: new Set(),
|
|
217
|
+
connectSrc: new Set(),
|
|
218
|
+
frameSrc: new Set(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
for (const plugin of plugins) {
|
|
222
|
+
if (plugin && plugin.csp) {
|
|
223
|
+
for (const [directive, sources] of Object.entries(plugin.csp)) {
|
|
224
|
+
if (pluginCspSources[directive]) {
|
|
225
|
+
const sourceArray = Array.isArray(sources) ? sources : [sources];
|
|
226
|
+
for (const source of sourceArray) {
|
|
227
|
+
pluginCspSources[directive].add(source);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Merge plugin CSP sources with default config
|
|
235
|
+
if (finalConfig.contentSecurityPolicy && finalConfig.contentSecurityPolicy.directives) {
|
|
236
|
+
const directives = finalConfig.contentSecurityPolicy.directives;
|
|
237
|
+
for (const [directive, sources] of Object.entries(pluginCspSources)) {
|
|
238
|
+
if (sources.size > 0 && directives[directive]) {
|
|
239
|
+
directives[directive] = [...directives[directive], ...Array.from(sources)];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
210
245
|
app.use(helmet(finalConfig));
|
|
211
246
|
}
|
|
212
247
|
|