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
JavaScript
;
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 = {
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'&': '&'
};
}
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;
}