|
|
@@ -42,21 +42,51 @@ const configSchema = {
|
|
|
description: 'Secret key for HMAC generation. Supports {{variable}} interpolation.',
|
|
|
showWhen: { field: 'operation', value: 'hmac' }
|
|
|
},
|
|
|
+ randomType: {
|
|
|
+ type: 'string',
|
|
|
+ title: 'Random Type',
|
|
|
+ enum: ['uuid', 'hex', 'alphanumeric', 'password'],
|
|
|
+ enumLabels: ['UUID v4', 'Hex Token', 'Alphanumeric String', 'Secure Password'],
|
|
|
+ default: 'uuid',
|
|
|
+ description: 'Type of random value to generate',
|
|
|
+ showWhen: { field: 'operation', value: 'random' }
|
|
|
+ },
|
|
|
randomLength: {
|
|
|
type: 'number',
|
|
|
- title: 'Random Bytes Length',
|
|
|
+ title: 'Length',
|
|
|
default: 32,
|
|
|
- description: 'Number of random bytes to generate (1-1024)',
|
|
|
+ minimum: 1,
|
|
|
+ maximum: 1024,
|
|
|
+ description: 'Length of the generated value (1-1024). For hex, this is number of bytes (output will be 2x characters).',
|
|
|
showWhen: { field: 'operation', value: 'random' }
|
|
|
},
|
|
|
- randomFormat: {
|
|
|
- type: 'string',
|
|
|
- title: 'Random Output Format',
|
|
|
- enum: ['hex', 'base64', 'uuid'],
|
|
|
- enumLabels: ['Hexadecimal', 'Base64', 'UUID v4'],
|
|
|
- default: 'hex',
|
|
|
- description: 'Format of the random output',
|
|
|
- showWhen: { field: 'operation', value: 'random' }
|
|
|
+ passwordUppercase: {
|
|
|
+ type: 'boolean',
|
|
|
+ title: 'Include Uppercase',
|
|
|
+ default: true,
|
|
|
+ description: 'Include uppercase letters (A-Z)',
|
|
|
+ showWhen: { field: 'randomType', value: 'password' }
|
|
|
+ },
|
|
|
+ passwordLowercase: {
|
|
|
+ type: 'boolean',
|
|
|
+ title: 'Include Lowercase',
|
|
|
+ default: true,
|
|
|
+ description: 'Include lowercase letters (a-z)',
|
|
|
+ showWhen: { field: 'randomType', value: 'password' }
|
|
|
+ },
|
|
|
+ passwordNumbers: {
|
|
|
+ type: 'boolean',
|
|
|
+ title: 'Include Numbers',
|
|
|
+ default: true,
|
|
|
+ description: 'Include numbers (0-9)',
|
|
|
+ showWhen: { field: 'randomType', value: 'password' }
|
|
|
+ },
|
|
|
+ passwordSymbols: {
|
|
|
+ type: 'boolean',
|
|
|
+ title: 'Include Symbols',
|
|
|
+ default: true,
|
|
|
+ description: 'Include symbols (!@#$%^&*()_+-=[]{}|;:,.<>?)',
|
|
|
+ showWhen: { field: 'randomType', value: 'password' }
|
|
|
}
|
|
|
},
|
|
|
required: ['operation']
|
|
|
@@ -95,6 +125,14 @@ const outputSchema = {
|
|
|
type: 'string',
|
|
|
description: 'Output format (hex or base64)'
|
|
|
},
|
|
|
+ randomType: {
|
|
|
+ type: 'string',
|
|
|
+ description: 'Type of random value generated (uuid, hex, alphanumeric, password)'
|
|
|
+ },
|
|
|
+ length: {
|
|
|
+ type: 'number',
|
|
|
+ description: 'Length of the generated random value'
|
|
|
+ },
|
|
|
inputLength: {
|
|
|
type: 'number',
|
|
|
description: 'Length of the input data in bytes'
|
|
|
@@ -118,6 +156,43 @@ function interpolate(template, data) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+function generateRandomString(length, charset) {
|
|
|
+ const charsetLength = charset.length;
|
|
|
+ let result = '';
|
|
|
+
|
|
|
+ // Generate enough random bytes to select characters
|
|
|
+ // We need to handle modulo bias by rejecting values that would cause bias
|
|
|
+ const bytesNeeded = length;
|
|
|
+ const randomHex = smartbotic.crypto.randomBytes(bytesNeeded * 2);
|
|
|
+
|
|
|
+ let pos = 0;
|
|
|
+ while (result.length < length) {
|
|
|
+ if (pos >= randomHex.length) {
|
|
|
+ // Need more random bytes
|
|
|
+ const moreBytes = smartbotic.crypto.randomBytes((length - result.length) * 2);
|
|
|
+ pos = 0;
|
|
|
+ for (let i = 0; i < moreBytes.length && result.length < length; i += 2) {
|
|
|
+ const byte = parseInt(moreBytes.substr(i, 2), 16);
|
|
|
+ // Use rejection sampling to avoid modulo bias
|
|
|
+ const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
|
|
|
+ if (byte < maxUnbiased) {
|
|
|
+ result += charset[byte % charsetLength];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const byte = parseInt(randomHex.substr(pos, 2), 16);
|
|
|
+ pos += 2;
|
|
|
+ // Use rejection sampling to avoid modulo bias
|
|
|
+ const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
|
|
|
+ if (byte < maxUnbiased) {
|
|
|
+ result += charset[byte % charsetLength];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
async function execute(config, input, context) {
|
|
|
const operation = config.operation || 'hash';
|
|
|
|
|
|
@@ -126,42 +201,74 @@ async function execute(config, input, context) {
|
|
|
}
|
|
|
|
|
|
if (operation === 'random') {
|
|
|
- const format = config.randomFormat || 'hex';
|
|
|
+ const randomType = config.randomType || 'uuid';
|
|
|
+ const length = config.randomLength || 32;
|
|
|
+
|
|
|
+ if (randomType !== 'uuid' && (length < 1 || length > 1024)) {
|
|
|
+ throw new Error('Length must be between 1 and 1024');
|
|
|
+ }
|
|
|
|
|
|
- if (format === 'uuid') {
|
|
|
+ if (randomType === 'uuid') {
|
|
|
const uuid = smartbotic.crypto.randomUUID();
|
|
|
- smartbotic.log.info('Generated random UUID');
|
|
|
+ smartbotic.log.info('Generated random UUID v4');
|
|
|
return {
|
|
|
result: uuid,
|
|
|
operation: 'random',
|
|
|
- format: 'uuid'
|
|
|
+ randomType: 'uuid',
|
|
|
+ length: 36
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- const length = config.randomLength || 32;
|
|
|
- if (length < 1 || length > 1024) {
|
|
|
- throw new Error('Random length must be between 1 and 1024');
|
|
|
+ if (randomType === 'hex') {
|
|
|
+ const randomHex = smartbotic.crypto.randomBytes(length);
|
|
|
+ smartbotic.log.info('Generated ' + length + '-byte hex token (' + randomHex.length + ' characters)');
|
|
|
+ return {
|
|
|
+ result: randomHex,
|
|
|
+ operation: 'random',
|
|
|
+ randomType: 'hex',
|
|
|
+ length: randomHex.length
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- const randomHex = smartbotic.crypto.randomBytes(length);
|
|
|
- let result = randomHex;
|
|
|
+ if (randomType === 'alphanumeric') {
|
|
|
+ const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
+ const result = generateRandomString(length, charset);
|
|
|
+ smartbotic.log.info('Generated ' + length + '-character alphanumeric string');
|
|
|
+ return {
|
|
|
+ result: result,
|
|
|
+ operation: 'random',
|
|
|
+ randomType: 'alphanumeric',
|
|
|
+ length: length
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- if (format === 'base64') {
|
|
|
- let binary = '';
|
|
|
- for (let i = 0; i < randomHex.length; i += 2) {
|
|
|
- binary += String.fromCharCode(parseInt(randomHex.substr(i, 2), 16));
|
|
|
+ if (randomType === 'password') {
|
|
|
+ const includeUppercase = config.passwordUppercase !== false;
|
|
|
+ const includeLowercase = config.passwordLowercase !== false;
|
|
|
+ const includeNumbers = config.passwordNumbers !== false;
|
|
|
+ const includeSymbols = config.passwordSymbols !== false;
|
|
|
+
|
|
|
+ let charset = '';
|
|
|
+ if (includeUppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
+ if (includeLowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
|
|
|
+ if (includeNumbers) charset += '0123456789';
|
|
|
+ if (includeSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
|
+
|
|
|
+ if (charset.length === 0) {
|
|
|
+ throw new Error('At least one character set must be enabled for password generation');
|
|
|
}
|
|
|
- result = smartbotic.utils.base64Encode(binary);
|
|
|
- }
|
|
|
|
|
|
- smartbotic.log.info('Generated ' + length + ' random bytes in ' + format + ' format');
|
|
|
+ const result = generateRandomString(length, charset);
|
|
|
+ smartbotic.log.info('Generated ' + length + '-character secure password');
|
|
|
+ return {
|
|
|
+ result: result,
|
|
|
+ operation: 'random',
|
|
|
+ randomType: 'password',
|
|
|
+ length: length
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- return {
|
|
|
- result: result,
|
|
|
- operation: 'random',
|
|
|
- format: format,
|
|
|
- inputLength: length
|
|
|
- };
|
|
|
+ throw new Error('Unknown random type: ' + randomType);
|
|
|
}
|
|
|
|
|
|
const inputData = input.data !== undefined ? input.data : input;
|