feat: expand internationalization and add identity management
This release significantly expands internationalization support and adds comprehensive identity management features. Internationalization (i18n): - Add 5 new languages: Spanish, Italian, German, Dutch, Portuguese - Expand from 3 to 8 total supported languages - Redesign language switcher for better scalability (dropdown UI) - Complete translations for all features across all languages Identity Management: - Multiple sender identities with per-identity signatures - Sub-addressing support (user+tag@domain.com) - Context-aware tag suggestions for sub-addresses - Identity badges in email viewer and list - Full CRUD operations for managing identities Newsletter Management: - RFC 2369 List-Unsubscribe support (one-click unsubscribe) - HTTP and mailto unsubscribe methods - Security validation prevents XSS attacks - Two-step confirmation with persistent dismissal Security & Accessibility: - Dark mode email readability (intelligent color transformation) - WCAG 2.0 Level AA color contrast compliance - Comprehensive XSS prevention with validation utilities - Unit test coverage for security-critical code (57 validation tests) Testing: - Add unit tests for validation utilities - Add unit tests for email sanitization - Add unit tests for color transformation - Full test coverage for XSS attack vectors
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
/**
|
||||
* Unified DOMPurify configuration for email content
|
||||
* Blocks all script execution vectors while preserving formatting
|
||||
* NOTE: <style> tags are forbidden to prevent global CSS injection
|
||||
* Inline style attributes are still allowed for element-specific styling
|
||||
*/
|
||||
export const EMAIL_SANITIZE_CONFIG = {
|
||||
ADD_TAGS: [],
|
||||
ADD_ATTR: ['target', 'style', 'class', 'width', 'height', 'align', 'valign', 'bgcolor', 'color'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
FORCE_BODY: true,
|
||||
FORBID_TAGS: [
|
||||
'script', 'iframe', 'object', 'embed', 'form',
|
||||
'input', 'button', 'meta', 'link', 'base',
|
||||
'svg', 'math', 'style'
|
||||
],
|
||||
FORBID_ATTR: [
|
||||
'onerror', 'onload', 'onclick', 'onmouseover',
|
||||
'onfocus', 'onblur', 'onchange', 'onsubmit',
|
||||
'onkeydown', 'onkeyup', 'onmousedown', 'onmouseup'
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize email HTML content
|
||||
* @param html - Raw HTML content from email
|
||||
* @returns Sanitized HTML safe for rendering
|
||||
*/
|
||||
export function sanitizeEmailHtml(html: string): string {
|
||||
return DOMPurify.sanitize(html, EMAIL_SANITIZE_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTML signature with stricter rules
|
||||
* Only allows basic formatting, no external resources
|
||||
*/
|
||||
export const SIGNATURE_SANITIZE_CONFIG = {
|
||||
ALLOWED_TAGS: ['p', 'br', 'b', 'strong', 'i', 'em', 'u', 'a', 'span', 'div'],
|
||||
ALLOWED_ATTR: ['href', 'style', 'class'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'img', 'video', 'audio'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize HTML signature for storage and display
|
||||
* @param html - User-provided HTML signature
|
||||
* @returns Sanitized signature (no scripts, no external resources)
|
||||
*/
|
||||
export function sanitizeSignatureHtml(html: string): string {
|
||||
if (!html?.trim()) return '';
|
||||
return DOMPurify.sanitize(html, SIGNATURE_SANITIZE_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe HTML parsing without execution
|
||||
* Use instead of innerHTML for detection/parsing
|
||||
*/
|
||||
export function parseHtmlSafely(html: string): Document {
|
||||
const parser = new DOMParser();
|
||||
return parser.parseFromString(html, 'text/html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if HTML content has rich formatting
|
||||
* Safe alternative to innerHTML parsing
|
||||
*/
|
||||
export function hasRichFormatting(html: string): boolean {
|
||||
const doc = parseHtmlSafely(html);
|
||||
return !!doc.querySelector(
|
||||
'table, img, style, b, strong, i, em, u, font, ' +
|
||||
'div[style], span[style], p[style], ' +
|
||||
'h1, h2, h3, h4, h5, h6, ul, ol, blockquote'
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user