autotel
Version:
Write Once, Observe Anywhere
1 lines • 13.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/validation.ts","../src/operation-context.ts"],"names":["AsyncLocalStorage"],"mappings":"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","file":"chunk-D5LMF53P.cjs","sourcesContent":["/**\n * Input validation for events events and attributes\n *\n * Prevents:\n * - Invalid event names\n * - Oversized payloads\n * - Circular references\n * - Sensitive data leaks\n */\n\nimport type { EventAttributes } from './event-subscriber';\n\nexport interface ValidationConfig {\n /** Max event name length (default: 100) */\n maxEventNameLength: number;\n /** Max attribute key length (default: 100) */\n maxAttributeKeyLength: number;\n /** Max attribute value length for strings (default: 1000) */\n maxAttributeValueLength: number;\n /** Max total attributes per event (default: 50) */\n maxAttributeCount: number;\n /** Max nesting depth for objects (default: 3) */\n maxNestingDepth: number;\n /** Sensitive field patterns to redact */\n sensitivePatterns: RegExp[];\n}\n\nconst DEFAULT_CONFIG: ValidationConfig = {\n maxEventNameLength: 100,\n maxAttributeKeyLength: 100,\n maxAttributeValueLength: 1000,\n maxAttributeCount: 50,\n maxNestingDepth: 3,\n sensitivePatterns: [\n /password/i,\n /secret/i,\n /token/i,\n /api[_-]?key/i,\n /access[_-]?key/i,\n /private[_-]?key/i,\n /auth/i,\n /credential/i,\n /ssn/i,\n /credit[_-]?card/i,\n ],\n};\n\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Validate and sanitize event name\n * Throws ValidationError if invalid\n */\nexport function validateEventName(\n eventName: string,\n config: ValidationConfig = DEFAULT_CONFIG,\n): string {\n // Check type\n if (typeof eventName !== 'string') {\n throw new ValidationError(\n `Event name must be a string, got ${typeof eventName}`,\n );\n }\n\n // Check non-empty\n const trimmed = eventName.trim();\n if (trimmed.length === 0) {\n throw new ValidationError('Event name cannot be empty');\n }\n\n // Check length\n if (trimmed.length > config.maxEventNameLength) {\n throw new ValidationError(\n `Event name too long (${trimmed.length} chars). ` +\n `Max: ${config.maxEventNameLength}`,\n );\n }\n\n // Check valid characters (alphanumeric, dots, underscores, hyphens)\n if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {\n throw new ValidationError(\n `Event name contains invalid characters: \"${trimmed}\". ` +\n 'Use only letters, numbers, dots, underscores, and hyphens.',\n );\n }\n\n return trimmed;\n}\n\n/**\n * Validate and sanitize attributes\n * Returns sanitized attributes (sensitive data redacted)\n */\nexport function validateAttributes(\n attributes: EventAttributes | undefined,\n config: ValidationConfig = DEFAULT_CONFIG,\n): EventAttributes | undefined {\n if (attributes === undefined || attributes === null) {\n return undefined;\n }\n\n // Check type\n if (typeof attributes !== 'object' || Array.isArray(attributes)) {\n throw new ValidationError('Attributes must be an object');\n }\n\n // Count attributes\n const keys = Object.keys(attributes);\n if (keys.length > config.maxAttributeCount) {\n throw new ValidationError(\n `Too many attributes (${keys.length}). ` +\n `Max: ${config.maxAttributeCount}`,\n );\n }\n\n // Validate and sanitize each attribute\n const sanitized: EventAttributes = {};\n\n for (const key of keys) {\n // Validate key\n if (key.length > config.maxAttributeKeyLength) {\n throw new ValidationError(\n `Attribute key too long: \"${key.slice(0, 20)}...\" ` +\n `(${key.length} chars). Max: ${config.maxAttributeKeyLength}`,\n );\n }\n\n // Check for sensitive field\n const isSensitive = config.sensitivePatterns.some((pattern) =>\n pattern.test(key),\n );\n\n if (isSensitive) {\n // Redact sensitive data\n sanitized[key] = '[REDACTED]';\n continue;\n }\n\n // Sanitize value\n const value = attributes[key];\n sanitized[key] = sanitizeValue(value, config, 1) as\n | string\n | number\n | boolean;\n }\n\n return sanitized;\n}\n\n/**\n * Sanitize attribute value (recursive)\n */\nfunction sanitizeValue(\n value: unknown,\n config: ValidationConfig,\n depth: number,\n): unknown {\n // Check nesting depth\n if (depth > config.maxNestingDepth) {\n return '[MAX_DEPTH_EXCEEDED]';\n }\n\n // Handle null/undefined\n if (value === null || value === undefined) {\n return value;\n }\n\n // Handle primitives\n if (typeof value === 'string') {\n if (value.length > config.maxAttributeValueLength) {\n return value.slice(0, config.maxAttributeValueLength) + '...';\n }\n return value;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeValue(item, config, depth + 1));\n }\n\n // Handle objects\n if (typeof value === 'object') {\n try {\n // Check for circular references\n JSON.stringify(value);\n\n const sanitized: Record<string, unknown> = {};\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n // Check for sensitive field in nested objects\n const isSensitive = config.sensitivePatterns.some((pattern) =>\n pattern.test(key),\n );\n\n if (isSensitive) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = sanitizeValue(\n (value as Record<string, unknown>)[key],\n config,\n depth + 1,\n );\n }\n }\n }\n return sanitized;\n } catch {\n // Circular reference detected\n return '[CIRCULAR]';\n }\n }\n\n // Unsupported type (function, symbol, etc.)\n return `[${typeof value}]`;\n}\n\n/**\n * Validate and sanitize an events event\n * Returns { eventName, attributes } with sanitized values\n */\nexport function validateEvent(\n eventName: string,\n attributes?: EventAttributes,\n config?: Partial<ValidationConfig>,\n): { eventName: string; attributes?: EventAttributes } {\n const fullConfig = { ...DEFAULT_CONFIG, ...config };\n\n return {\n eventName: validateEventName(eventName, fullConfig),\n attributes: validateAttributes(attributes, fullConfig),\n };\n}\n\n/**\n * Get default validation config (for testing/customization)\n */\nexport function getDefaultValidationConfig(): ValidationConfig {\n return { ...DEFAULT_CONFIG };\n}\n","/**\n * Operation context tracking using AsyncLocalStorage\n *\n * This module provides a way to track operation names across async boundaries\n * so they can be automatically captured in events events.\n *\n * We cannot read span attributes from OpenTelemetry's API (it's write-only),\n * so we maintain our own async context storage.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Operation context that flows through async operations\n */\nexport interface OperationContext {\n /**\n * The name of the current operation\n * This is set by trace() or span() and can be read by events\n */\n name: string;\n}\n\n/**\n * AsyncLocalStorage instance for tracking operation context\n */\nconst operationStorage = new AsyncLocalStorage<OperationContext>();\n\n/**\n * Get the current operation context (if any)\n *\n * @returns The current operation context, or undefined if not in an operation\n *\n * @example\n * ```typescript\n * const ctx = getOperationContext();\n * if (ctx) {\n * console.log('Current operation:', ctx.name);\n * }\n * ```\n */\nexport function getOperationContext(): OperationContext | undefined {\n return operationStorage.getStore();\n}\n\n/**\n * Run a function within an operation context\n *\n * This sets the operation name for the duration of the function execution,\n * including all async operations spawned from it.\n *\n * @param name - The operation name to set\n * @param fn - The function to execute within the context\n * @returns The result of the function\n *\n * @example\n * ```typescript\n * const result = await runInOperationContext('user.create', async () => {\n * // Any events.trackEvent() calls here will automatically capture\n * // 'operation.name': 'user.create'\n * await createUser();\n * return 'success';\n * });\n * ```\n */\nexport function runInOperationContext<T>(name: string, fn: () => T): T {\n return operationStorage.run({ name }, fn);\n}\n\n/**\n * Update the operation name in the current context\n *\n * This is useful when you want to change the operation name within\n * an already-established context (e.g., when entering a nested span).\n *\n * @param name - The new operation name\n *\n * @example\n * ```typescript\n * runInOperationContext('parent.operation', () => {\n * // operation.name is 'parent.operation'\n *\n * updateOperationName('nested.operation');\n * // operation.name is now 'nested.operation'\n * });\n * ```\n */\nexport function updateOperationName(name: string): void {\n const store = operationStorage.getStore();\n if (store) {\n store.name = name;\n }\n}\n"]}