webspresso 0.0.39 → 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
|
@@ -263,23 +263,23 @@ function createExtensionApiHandlers(options) {
|
|
|
263
263
|
return res.status(404).json({ error: 'Model not found or not enabled' });
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
// Get field metadata
|
|
267
|
-
const
|
|
268
|
-
if (!
|
|
266
|
+
// Get field metadata - model.columns is Map<string, ColumnMeta>
|
|
267
|
+
const columnMeta = model.columns.get(field);
|
|
268
|
+
if (!columnMeta) {
|
|
269
269
|
return res.status(400).json({ error: `Field "${field}" not found in model` });
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// Validate field type - only allow enum and boolean
|
|
273
|
-
const
|
|
274
|
-
const isEnum =
|
|
275
|
-
const isBoolean = columnMeta.type === 'boolean'
|
|
273
|
+
const enumValues = columnMeta.enumValues || columnMeta.enum;
|
|
274
|
+
const isEnum = enumValues && Array.isArray(enumValues);
|
|
275
|
+
const isBoolean = columnMeta.type === 'boolean';
|
|
276
276
|
|
|
277
277
|
if (!isEnum && !isBoolean) {
|
|
278
278
|
return res.status(400).json({ error: `Field "${field}" is not an enum or boolean type` });
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
// Validate value for enum fields
|
|
282
|
-
if (isEnum && !
|
|
282
|
+
if (isEnum && !enumValues.includes(value)) {
|
|
283
283
|
return res.status(400).json({ error: `Invalid value "${value}" for enum field "${field}"` });
|
|
284
284
|
}
|
|
285
285
|
|
|
@@ -352,17 +352,19 @@ function createExtensionApiHandlers(options) {
|
|
|
352
352
|
|
|
353
353
|
const bulkFields = [];
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
355
|
+
// model.columns is a Map<string, ColumnMeta> - values are already metadata objects
|
|
356
|
+
for (const [fieldName, columnMeta] of model.columns.entries()) {
|
|
357
|
+
// Check for enum - schema uses 'enumValues' property
|
|
358
|
+
const enumValues = columnMeta.enumValues || columnMeta.enum;
|
|
359
|
+
const isEnum = enumValues && Array.isArray(enumValues);
|
|
360
|
+
const isBoolean = columnMeta.type === 'boolean';
|
|
359
361
|
|
|
360
362
|
if (isEnum) {
|
|
361
363
|
bulkFields.push({
|
|
362
364
|
name: fieldName,
|
|
363
365
|
type: 'enum',
|
|
364
366
|
label: columnMeta.label || fieldName,
|
|
365
|
-
options:
|
|
367
|
+
options: enumValues.map(v => ({ value: v, label: v })),
|
|
366
368
|
});
|
|
367
369
|
} else if (isBoolean) {
|
|
368
370
|
bulkFields.push({
|
|
@@ -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
|
|