UNPKG

n8n-nodes-api-key-validator

Version:

N8N Community Node for validating API keys, tokens and sensitive authentication data

321 lines (320 loc) 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityValidator = void 0; const n8n_workflow_1 = require("n8n-workflow"); // Helper function to mask sensitive data - only masks the exact validated value function maskSensitiveData(data, sensitiveValue) { if (typeof data === 'string') { // Only mask if it's the exact sensitive value that was validated if (data === sensitiveValue) { return '***HIDDEN***'; } return data; } if (Array.isArray(data)) { return data.map(item => maskSensitiveData(item, sensitiveValue)); } if (data && typeof data === 'object') { const masked = {}; for (const [key, value] of Object.entries(data)) { masked[key] = maskSensitiveData(value, sensitiveValue); } return masked; } return data; } class SecurityValidator { constructor() { this.description = { displayName: 'Security Validator', name: 'securityValidator', icon: 'file:securityValidator.svg', group: ['transform'], version: 1, subtitle: 'API Key/Token Validator', description: 'Validate API keys, tokens and sensitive data - stops workflow if invalid, passes all data if valid', defaults: { name: 'Security Validator', }, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], properties: [ { displayName: 'Input Mode', name: 'inputMode', type: 'options', options: [ { name: 'Direct Input', value: 'direct', description: 'Enter API key directly (may show in debug)', }, { name: 'Environment Variable', value: 'env', description: 'Use environment variable (secure)', }, { name: 'Code Expression', value: 'code', description: 'Use JavaScript code (most flexible)', }, ], default: 'direct', description: 'How to provide the input API key', }, { displayName: 'Input Key (API Key/Token)', name: 'inputValue', type: 'string', typeOptions: { password: true, }, default: '', placeholder: 'Enter your secret API Key', description: 'The API key to validate (supports expressions, hidden for security)', required: true, noDataExpression: false, displayOptions: { show: { inputMode: ['direct'], }, }, }, { displayName: 'Environment Variable Name', name: 'envVarName', type: 'string', default: 'API_KEY', placeholder: 'API_KEY', description: 'Name of the environment variable containing the API key', required: true, displayOptions: { show: { inputMode: ['env'], }, }, }, { displayName: 'JavaScript Code', name: 'codeExpression', type: 'string', typeOptions: { editor: 'codeNodeEditor', editorLanguage: 'javascript', rows: 8, }, default: `// Return the API key to validate return $env.API_KEY;`, placeholder: `// Examples: // return $env.API_KEY; // return $json.headers['x-api-key']; // return $json.token;`, description: 'JavaScript code that returns the API key to validate', required: true, displayOptions: { show: { inputMode: ['code'], }, }, }, { displayName: 'Expected Key (API Key/Token)', name: 'expectedValue', type: 'string', typeOptions: { password: true, }, default: '', placeholder: 'Enter your secret API Key', description: 'Your secret API key (supports expressions, hidden for security)', required: true, noDataExpression: false, displayOptions: { show: { inputMode: ['direct'], }, }, }, { displayName: 'Validation Type', name: 'operation', type: 'options', noDataExpression: false, options: [ { name: 'Exact Match', value: 'equals', description: 'API key must match exactly', }, { name: 'Starts With', value: 'startsWith', description: 'API key must start with the expected prefix', }, { name: 'Contains', value: 'contains', description: 'API key must contain the expected substring', }, ], default: 'equals', description: 'How to validate the API key/token', }, { displayName: 'Case Sensitive', name: 'caseSensitive', type: 'boolean', default: true, description: 'Whether the validation should be case sensitive', }, { displayName: 'Error Message', name: 'errorMessage', type: 'string', default: 'Invalid API key or token', placeholder: 'Custom error message', description: 'Message to show when validation fails', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; for (let i = 0; i < items.length; i++) { try { const inputMode = this.getNodeParameter('inputMode', i); const operation = this.getNodeParameter('operation', i); const caseSensitive = this.getNodeParameter('caseSensitive', i, true); const errorMessage = this.getNodeParameter('errorMessage', i, 'Invalid API key or token'); // Only get expectedValue for direct mode let expectedValue = ''; if (inputMode === 'direct') { expectedValue = this.getNodeParameter('expectedValue', i); } // Get input value based on selected mode let inputValue; switch (inputMode) { case 'direct': inputValue = this.getNodeParameter('inputValue', i); break; case 'env': const envVarName = this.getNodeParameter('envVarName', i); inputValue = process.env[envVarName] || ''; if (!inputValue) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Environment variable '${envVarName}' is not set or empty`); } break; case 'code': const codeExpression = this.getNodeParameter('codeExpression', i); try { // Create a safe execution context const context = { $json: items[i].json, $env: process.env, $input: items[i], $item: items[i], $now: new Date(), $today: new Date().toISOString().split('T')[0], }; // Execute the code in a function context const func = new Function('$json', '$env', '$input', '$item', '$now', '$today', codeExpression); inputValue = func(context.$json, context.$env, context.$input, context.$item, context.$now, context.$today); if (typeof inputValue !== 'string') { inputValue = String(inputValue); } } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Code execution error: ${error instanceof Error ? error.message : String(error)}`); } break; default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown input mode: ${inputMode}`); } // Validate input value is provided if (!inputValue) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input value is required'); } // For direct mode, expected value is required if (inputMode === 'direct' && !expectedValue) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Expected key is required for direct input mode'); } let validationResult = false; if (inputMode === 'direct') { // Direct mode: compare input with expected value let processedInput = inputValue; let processedExpected = expectedValue; // Apply case sensitivity if (!caseSensitive) { processedInput = inputValue.toLowerCase(); processedExpected = expectedValue.toLowerCase(); } // Perform validation based on operation switch (operation) { case 'equals': validationResult = processedInput === processedExpected; break; case 'startsWith': validationResult = processedInput.startsWith(processedExpected); break; case 'contains': validationResult = processedInput.includes(processedExpected); break; default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown validation type: ${operation}`); } } else { // Environment Variable and Code Expression modes: // The validation logic is built into the code/environment setup // If we got a value, it means the validation passed validationResult = !!inputValue; } // Security validation logic: Pass or Stop if (validationResult) { // ✅ VALIDATION PASSED - Continue with ALL original data (masked for security) + validation message const maskedJsonData = maskSensitiveData(items[i].json, inputValue); // Preserve ALL original item properties (binary, pairedItem, etc.) const outputItem = { ...items[i], // Copy all original properties json: { ...maskedJsonData, _validation: { status: 'VALID', message: 'API key validation passed successfully', timestamp: new Date().toISOString(), validationType: operation, inputMode: inputMode, }, }, pairedItem: { item: i, }, }; returnData.push(outputItem); } else { // ❌ VALIDATION FAILED - Stop execution immediately throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage); } } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error instanceof Error ? error.message : String(error), }, pairedItem: { item: i, }, }); continue; } throw error; } } return [returnData]; } } exports.SecurityValidator = SecurityValidator;