UNPKG

n8n-nodes-sevdesk-v2

Version:

n8n community node for SevDesk API v2 integration with 24 production-ready workflow templates. Direct API access without external dependencies - simplified, secure, and optimized for German accounting automation (n8n 1.101.1 compatible).

265 lines (264 loc) 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultSanitizer = exports.InputSanitizer = void 0; exports.sanitizeInput = sanitizeInput; exports.sanitizeForSevDesk = sanitizeForSevDesk; class InputSanitizer { constructor() { this.defaultOptions = { trimWhitespace: true, stripHtml: true, escapeSpecialChars: true, normalizeUnicode: true, maxLength: 10000, preventSqlInjection: true, preventScriptInjection: true, customPatterns: [] }; this.sqlInjectionPatterns = [ /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi, /(--|\/\*|\*\/|;|'|"|`)/g, /(\bOR\b|\bAND\b)\s+\d+\s*=\s*\d+/gi, /(\bOR\b|\bAND\b)\s+['"]?\w+['"]?\s*=\s*['"]?\w+['"]?/gi ]; this.scriptInjectionPatterns = [ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, /<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, /javascript:/gi, /vbscript:/gi, /on\w+\s*=/gi ]; this.dangerousHtmlTags = /<[^>]*>/g; this.specialCharacters = { '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;', '/': '&#x2F;', '&': '&amp;' }; } sanitize(data, options) { const opts = { ...this.defaultOptions, ...options }; const result = { data: null, wasSanitized: false, actions: [], warnings: [] }; try { result.data = this.sanitizeValue(data, opts, result); return result; } catch (error) { result.warnings.push(`Sanitization error: ${error instanceof Error ? error.message : String(error)}`); result.data = data; return result; } } sanitizeValue(value, options, result) { if (value === null || value === undefined) { return value; } if (typeof value === 'string') { return this.sanitizeString(value, options, result); } if (typeof value === 'number' || typeof value === 'boolean') { return value; } if (Array.isArray(value)) { return value.map(item => this.sanitizeValue(item, options, result)); } if (typeof value === 'object') { const sanitizedObject = {}; for (const [key, val] of Object.entries(value)) { const sanitizedKey = this.sanitizeString(key, options, result); sanitizedObject[sanitizedKey] = this.sanitizeValue(val, options, result); } return sanitizedObject; } return value; } sanitizeString(str, options, result) { let sanitized = str; const originalLength = str.length; if (options.trimWhitespace) { const trimmed = sanitized.trim(); if (trimmed !== sanitized) { sanitized = trimmed; result.wasSanitized = true; result.actions.push('Trimmed whitespace'); } } if (options.normalizeUnicode) { const normalized = sanitized.normalize('NFC'); if (normalized !== sanitized) { sanitized = normalized; result.wasSanitized = true; result.actions.push('Normalized Unicode'); } } if (options.preventSqlInjection) { for (const pattern of this.sqlInjectionPatterns) { if (pattern.test(sanitized)) { result.warnings.push('Potential SQL injection pattern detected and sanitized'); sanitized = sanitized.replace(pattern, ''); result.wasSanitized = true; result.actions.push('Removed SQL injection patterns'); } } } if (options.preventScriptInjection) { for (const pattern of this.scriptInjectionPatterns) { if (pattern.test(sanitized)) { result.warnings.push('Potential script injection pattern detected and sanitized'); sanitized = sanitized.replace(pattern, ''); result.wasSanitized = true; result.actions.push('Removed script injection patterns'); } } } if (options.stripHtml) { const withoutHtml = sanitized.replace(this.dangerousHtmlTags, ''); if (withoutHtml !== sanitized) { sanitized = withoutHtml; result.wasSanitized = true; result.actions.push('Stripped HTML tags'); } } if (options.escapeSpecialChars) { let escaped = sanitized; for (const [char, replacement] of Object.entries(this.specialCharacters)) { escaped = escaped.replace(new RegExp(char, 'g'), replacement); } if (escaped !== sanitized) { sanitized = escaped; result.wasSanitized = true; result.actions.push('Escaped special characters'); } } if (options.customPatterns && options.customPatterns.length > 0) { for (const pattern of options.customPatterns) { const cleaned = sanitized.replace(pattern, ''); if (cleaned !== sanitized) { sanitized = cleaned; result.wasSanitized = true; result.actions.push('Applied custom sanitization pattern'); } } } if (options.maxLength && sanitized.length > options.maxLength) { sanitized = sanitized.substring(0, options.maxLength); result.wasSanitized = true; result.actions.push(`Truncated to ${options.maxLength} characters`); result.warnings.push(`Input was truncated from ${originalLength} to ${options.maxLength} characters`); } return sanitized; } sanitizeForSevDesk(data, resourceType) { const sevDeskOptions = { ...this.defaultOptions, maxLength: this.getMaxLengthForResource(resourceType), customPatterns: this.getCustomPatternsForResource(resourceType) }; const result = this.sanitize(data, sevDeskOptions); if (typeof result.data === 'object' && result.data !== null) { result.data = this.applySevDeskSpecificSanitization(result.data, resourceType, result); } return result; } getMaxLengthForResource(resourceType) { const limits = { 'contact': 5000, 'invoice': 10000, 'voucher': 5000, 'order': 10000, 'part': 2000, 'default': 5000 }; return limits[resourceType.toLowerCase()] || limits.default; } getCustomPatternsForResource(resourceType) { const patterns = { 'contact': [ /[^\w\s@.\-+()]/g, ], 'invoice': [ /[^\w\s@.\-+(),€$%]/g, ], 'default': [ /[^\w\s@.\-+()]/g ] }; return patterns[resourceType.toLowerCase()] || patterns.default; } applySevDeskSpecificSanitization(data, resourceType, result) { const sanitized = { ...data }; if (sanitized.email && typeof sanitized.email === 'string') { const originalEmail = sanitized.email; sanitized.email = sanitized.email.toLowerCase().trim(); if (sanitized.email !== originalEmail) { result.wasSanitized = true; result.actions.push('Normalized email address'); } } if (sanitized.phone && typeof sanitized.phone === 'string') { const originalPhone = sanitized.phone; sanitized.phone = sanitized.phone.replace(/[^\d+\-() ]/g, ''); if (sanitized.phone !== originalPhone) { result.wasSanitized = true; result.actions.push('Sanitized phone number'); } } if (sanitized.customerNumber && typeof sanitized.customerNumber === 'string') { const originalCustomerNumber = sanitized.customerNumber; sanitized.customerNumber = sanitized.customerNumber.replace(/[^\w\-]/g, ''); if (sanitized.customerNumber !== originalCustomerNumber) { result.wasSanitized = true; result.actions.push('Sanitized customer number'); } } if (sanitized.vatNumber && typeof sanitized.vatNumber === 'string') { const originalVatNumber = sanitized.vatNumber; sanitized.vatNumber = sanitized.vatNumber.replace(/[^\w]/g, '').toUpperCase(); if (sanitized.vatNumber !== originalVatNumber) { result.wasSanitized = true; result.actions.push('Sanitized VAT number'); } } return sanitized; } static quickSanitize(input) { if (typeof input !== 'string') { return input; } return input .trim() .replace(/<[^>]*>/g, '') .replace(/[<>"'&]/g, '') .replace(/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi, '') .substring(0, 1000); } static validateSanitization(original, sanitized) { if (typeof original !== 'string' || typeof sanitized !== 'string') { return true; } const dangerousPatterns = [ /<script/i, /javascript:/i, /on\w+\s*=/i, /(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b/i ]; return !dangerousPatterns.some(pattern => pattern.test(sanitized)); } } exports.InputSanitizer = InputSanitizer; exports.defaultSanitizer = new InputSanitizer(); function sanitizeInput(data, options) { return exports.defaultSanitizer.sanitize(data, options).data; } function sanitizeForSevDesk(data, resourceType) { return exports.defaultSanitizer.sanitizeForSevDesk(data, resourceType).data; }