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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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
 
@@ -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
- const finalConfig = helmetConfig === undefined || helmetConfig === true
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