import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import OTPAuth from 'otpauth'; export class Totp implements INodeType { description: INodeTypeDescription = { displayName: 'TOTP', name: 'totp', icon: 'fa:fingerprint', group: ['transform'], version: 1, subtitle: '={{ $parameter["operation"] }}', description: 'Generate a time-based one-time password', defaults: { name: 'TOTP', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'totpApi', required: true, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Generate Secret', value: 'generateSecret', action: 'Generate secret', }, ], default: 'generateSecret', }, { displayName: 'Options', name: 'options', type: 'collection', displayOptions: { show: { operation: ['generateSecret'], }, }, default: {}, placeholder: 'Add Option', options: [ { displayName: 'Algorithm', name: 'algorithm', type: 'options', default: 'SHA1', description: 'HMAC hashing algorithm. Defaults to SHA1.', options: [ { name: 'SHA1', value: 'SHA1', }, { name: 'SHA224', value: 'SHA224', }, { name: 'SHA256', value: 'SHA256', }, { name: 'SHA3-224', value: 'SHA3-224', }, { name: 'SHA3-256', value: 'SHA3-256', }, { name: 'SHA3-384', value: 'SHA3-384', }, { name: 'SHA3-512', value: 'SHA3-512', }, { name: 'SHA384', value: 'SHA384', }, { name: 'SHA512', value: 'SHA512', }, ], }, { displayName: 'Digits', name: 'digits', type: 'number', default: 6, description: 'Number of digits in the generated TOTP code. Defaults to 6 digits.', }, { displayName: 'Period', name: 'period', type: 'number', default: 30, description: 'How many seconds the generated TOTP code is valid for. Defaults to 30 seconds.', }, ], }, ], }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const operation = this.getNodeParameter('operation', 0); const credentials = (await this.getCredentials('totpApi')) as { label: string; secret: string }; if (!credentials.label.includes(':')) { throw new NodeOperationError(this.getNode(), 'Malformed label - expected `issuer:username`'); } const options = this.getNodeParameter('options', 0) as { algorithm?: string; digits?: number; period?: number; }; if (!options.algorithm) options.algorithm = 'SHA1'; if (!options.digits) options.digits = 6; if (!options.period) options.period = 30; const [issuer] = credentials.label.split(':'); const totp = new OTPAuth.TOTP({ issuer, label: credentials.label, secret: credentials.secret, algorithm: options.algorithm, digits: options.digits, period: options.period, }); const token = totp.generate(); const secondsRemaining = (options.period * (1 - ((Date.now() / 1000 / options.period) % 1))) | 0; if (operation === 'generateSecret') { for (let i = 0; i < items.length; i++) { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ token, secondsRemaining }), { itemData: { item: i } }, ); returnData.push(...executionData); } } return [returnData]; } }