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
JavaScript
;
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;