mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-25 21:43:32 +00:00
- py/stack-trace-exposure: Remove str(e) and traceback.format_exc() from all HTTP responses across api_v3.py, pages_v3.py, and app.py; replace with generic messages and logger.error(exc_info=True) - py/reflective-xss: Escape partial_name via markupsafe.escape in the load_partial 404 response - py/path-injection: Add regex validation of plugin_id before filesystem use in _load_plugin_config_partial - py/incomplete-url-substring-sanitization: Replace 'github.com' in substring checks with urlparse hostname comparison in store_manager.py - py/clear-text-logging-sensitive-data: Remove football-scoreboard debug prints and sensitive request-body prints from update endpoint - js/bad-tag-filter: Replace script-only regex in BaseWidget.sanitizeValue with DOM-based textContent stripping that removes all HTML - js/incomplete-sanitization: Fix escapeAttr to properly encode &, ", ', <, > using HTML entities instead of backslash escaping - js/prototype-pollution-utility: Add __proto__/constructor/prototype key guards to deepMerge function in plugins_manager.js - app.py error handlers: Always return generic messages; remove debug-mode branches that could expose tracebacks in production Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
6.3 KiB
JavaScript
195 lines
6.3 KiB
JavaScript
/**
|
|
* LEDMatrix Base Widget Class
|
|
*
|
|
* Provides common functionality and utilities for all widgets.
|
|
* Widgets can extend this or use it as a reference for best practices.
|
|
*
|
|
* @module BaseWidget
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Base Widget Class
|
|
* Provides common utilities and patterns for widgets
|
|
*/
|
|
class BaseWidget {
|
|
constructor(name, version) {
|
|
this.name = name;
|
|
this.version = version || '1.0.0';
|
|
}
|
|
|
|
/**
|
|
* Validate widget configuration
|
|
* @param {Object} config - Configuration object from schema
|
|
* @param {Object} schema - Full schema object
|
|
* @returns {Object} Validation result {valid: boolean, errors: Array}
|
|
*/
|
|
validateConfig(config, schema) {
|
|
const errors = [];
|
|
|
|
if (!config) {
|
|
errors.push('Configuration is required');
|
|
return { valid: false, errors };
|
|
}
|
|
|
|
// Add widget-specific validation here
|
|
// This is a base implementation that can be overridden
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Sanitize value for storage
|
|
* @param {*} value - Raw value from widget
|
|
* @returns {*} Sanitized value
|
|
*/
|
|
sanitizeValue(value) {
|
|
// Base implementation - widgets should override for specific needs
|
|
if (typeof value === 'string') {
|
|
// Strip all HTML tags via the DOM parser to prevent XSS
|
|
const div = document.createElement('div');
|
|
div.textContent = value;
|
|
return div.textContent;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Get field ID from container or options
|
|
* @param {HTMLElement} container - Container element
|
|
* @param {Object} options - Options object
|
|
* @returns {string} Field ID
|
|
*/
|
|
getFieldId(container, options) {
|
|
if (options && options.fieldId) {
|
|
return options.fieldId;
|
|
}
|
|
if (container && container.id) {
|
|
return container.id.replace(/_widget_container$/, '');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
* @param {HTMLElement} container - Container element
|
|
* @param {string} message - Error message
|
|
*/
|
|
showError(container, message) {
|
|
if (!container) return;
|
|
|
|
// Remove existing error
|
|
const existingError = container.querySelector('.widget-error');
|
|
if (existingError) {
|
|
existingError.remove();
|
|
}
|
|
|
|
// Create error element using DOM APIs to prevent XSS
|
|
const errorEl = document.createElement('div');
|
|
errorEl.className = 'widget-error text-sm text-red-600 mt-2';
|
|
|
|
const icon = document.createElement('i');
|
|
icon.className = 'fas fa-exclamation-circle mr-1';
|
|
errorEl.appendChild(icon);
|
|
|
|
const messageText = document.createTextNode(message);
|
|
errorEl.appendChild(messageText);
|
|
|
|
container.appendChild(errorEl);
|
|
}
|
|
|
|
/**
|
|
* Clear error message
|
|
* @param {HTMLElement} container - Container element
|
|
*/
|
|
clearError(container) {
|
|
if (!container) return;
|
|
const errorEl = container.querySelector('.widget-error');
|
|
if (errorEl) {
|
|
errorEl.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Escape HTML to prevent XSS
|
|
* Always escapes the input, even for non-strings, by coercing to string first
|
|
* @param {*} text - Text to escape (will be coerced to string)
|
|
* @returns {string} Escaped text
|
|
*/
|
|
escapeHtml(text) {
|
|
// Always coerce to string first, then escape
|
|
const textStr = String(text);
|
|
const div = document.createElement('div');
|
|
div.textContent = textStr;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Sanitize identifier for use in DOM IDs and CSS selectors
|
|
* @param {string} id - Identifier to sanitize
|
|
* @returns {string} Sanitized identifier safe for DOM/CSS
|
|
*/
|
|
sanitizeId(id) {
|
|
if (typeof id !== 'string') {
|
|
id = String(id);
|
|
}
|
|
// Allow only alphanumeric, underscore, and hyphen
|
|
return id.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
}
|
|
|
|
/**
|
|
* Trigger widget change event
|
|
* @param {string} fieldId - Field ID
|
|
* @param {*} value - New value
|
|
*/
|
|
triggerChange(fieldId, value) {
|
|
const event = new CustomEvent('widget-change', {
|
|
detail: { fieldId, value },
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Get notification function (if available)
|
|
* @returns {Function|null} Notification function or null
|
|
*/
|
|
getNotificationFunction() {
|
|
if (typeof window.showNotification === 'function') {
|
|
return window.showNotification;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Show notification
|
|
* @param {string} message - Message to show
|
|
* @param {string} type - Notification type (success, error, info, warning)
|
|
*/
|
|
notify(message, type) {
|
|
// Normalize type to prevent errors when undefined/null
|
|
const normalizedType = type ? String(type) : 'info';
|
|
|
|
const notifyFn = this.getNotificationFunction();
|
|
if (notifyFn) {
|
|
notifyFn(message, normalizedType);
|
|
} else {
|
|
console.log(`[${normalizedType.toUpperCase()}] ${message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export for use in widget implementations
|
|
if (typeof window !== 'undefined') {
|
|
window.BaseWidget = BaseWidget;
|
|
}
|
|
|
|
console.log('[BaseWidget] Base widget class loaded');
|
|
})();
|