S4DSAuth node added. Some other nodes deleted

This commit is contained in:
MrMatiz2 2025-07-06 18:44:45 -05:00
commit 392231e695
16 changed files with 4910 additions and 222 deletions

View file

@ -1,77 +0,0 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
export class ExampleNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example Node',
name: 'exampleNode',
group: ['transform'],
version: 1,
description: 'Basic Example Node',
defaults: {
name: 'Example Node',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
properties: [
// Node properties which the user gets displayed and
// can change on the node.
{
displayName: 'My String',
name: 'myString',
type: 'string',
default: '',
placeholder: 'Placeholder value',
description: 'The description text',
},
],
};
// The function below is responsible for actually doing whatever this node
// is supposed to do. In this case, we're just appending the `myString` property
// with whatever the user has entered.
// You can make async calls and use `await`.
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let item: INodeExecutionData;
let myString: string;
// Iterates over all input items and add the key "myString" with the
// value the parameter "myString" resolves to.
// (This could be a different value for each item in case it contains an expression)
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
myString = this.getNodeParameter('myString', itemIndex, '') as string;
item = items[itemIndex];
item.json.myString = myString;
} catch (error) {
// This node should never fail but we want to showcase how
// to handle errors.
if (this.continueOnFail()) {
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
} else {
// Adding `itemIndex` allows other workflows to handle this error
if (error.context) {
// If the error thrown already contains the context property,
// only append the itemIndex
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
}
}

View file

@ -1,77 +0,0 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
export class ExampleNode2 implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example Node 2',
name: 'exampleNode2',
group: ['transform'],
version: 1,
description: 'Basic Example Node',
defaults: {
name: 'Example Node',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
properties: [
// Node properties which the user gets displayed and
// can change on the node.
{
displayName: 'My String',
name: 'myString',
type: 'string',
default: '',
placeholder: 'Placeholder value',
description: 'The description text',
},
],
};
// The function below is responsible for actually doing whatever this node
// is supposed to do. In this case, we're just appending the `myString` property
// with whatever the user has entered.
// You can make async calls and use `await`.
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let item: INodeExecutionData;
let myString: string;
// Iterates over all input items and add the key "myString" with the
// value the parameter "myString" resolves to.
// (This could be a different value for each item in case it contains an expression)
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
myString = this.getNodeParameter('myString', itemIndex, '') as string;
item = items[itemIndex];
item.json.myString = myString;
} catch (error) {
// This node should never fail but we want to showcase how
// to handle errors.
if (this.continueOnFail()) {
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
} else {
// Adding `itemIndex` allows other workflows to handle this error
if (error.context) {
// If the error thrown already contains the context property,
// only append the itemIndex
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
}
}

View file

@ -0,0 +1,109 @@
import { INodeProperties } from 'n8n-workflow';
// Operaciones disponibles para el recurso de autenticación
export const authOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['auth'],
},
},
options: [
{
name: 'Generate Token',
value: 'generateToken',
description: 'Generate authentication token using credentials',
action: 'Generate authentication token',
routing: {
request: {
method: 'POST',
url: '/login/generateToken',
body: {
username: '={{$credentials.s4dsApi.username}}',
password: '={{$credentials.s4dsApi.password}}',
},
},
},
},
{
name: 'Validate Token',
value: 'validateToken',
description: 'Validate existing token',
action: 'Validate existing token',
routing: {
request: {
method: 'GET',
url: '/auth/validate',
},
},
},
],
default: 'generateToken',
},
];
// Campos para la operación de generación de token
const generateTokenOperation: INodeProperties[] = [
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['auth'],
operation: ['generateToken'],
},
},
options: [
{
displayName: 'Store Token in Context',
name: 'storeInContext',
type: 'boolean',
default: true,
description: 'Whether to store the token in the workflow context for other nodes to use',
},
{
displayName: 'Token Context Key',
name: 'tokenContextKey',
type: 'string',
default: 's4ds_token',
description: 'Key to store the token in the workflow context',
displayOptions: {
show: {
storeInContext: [true],
},
},
},
],
},
];
// Campos para la operación de validación de token
const validateTokenOperation: INodeProperties[] = [
{
displayName: 'Token',
name: 'token',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['auth'],
operation: ['validateToken'],
},
},
description: 'Token to validate',
},
];
// Exportar todos los campos
export const authFields: INodeProperties[] = [
...generateTokenOperation,
...validateTokenOperation,
];

111
nodes/S4DSAuth/README.md Normal file
View file

@ -0,0 +1,111 @@
# S4DS Authentication Node
Este nodo permite autenticarse con las APIs de S4DS y gestionar tokens de acceso.
## Características
- **Generación de tokens**: Autenticación con usuario y contraseña
- **Validación de tokens**: Verificar si un token es válido
- **Múltiples ambientes**: Soporte para Test, UAT y Production
- **Múltiples clientes**: Configuración para diferentes clientes (Demo, Cliente 1, Cliente 2)
- **Gestión de contexto**: Almacenamiento automático de tokens para uso en otros nodos
## Configuración
### Credenciales
1. Configure las credenciales de S4DS API:
- **Base URL**: Seleccione el ambiente y cliente correspondiente
- **Username**: Nombre de usuario para autenticación
- **Password**: Contraseña para autenticación
- **Custom Base URL**: Para URLs personalizadas
- **Timeout**: Tiempo de espera para las peticiones (ms)
### Operaciones
#### Generate Token
Genera un token de autenticación usando las credenciales configuradas.
**Parámetros opcionales:**
- **Store Token in Context**: Almacenar token en el contexto del workflow
- **Token Context Key**: Clave para almacenar el token (por defecto: `s4ds_token`)
**Parámetros opcionales:**
- **Store Token in Context**: Almacenar token en el contexto del workflow
- **Token Context Key**: Clave para almacenar el token (por defecto: `s4ds_token`)
**Respuesta:**
```json
{
"token_type": "Bearer",
"expires_in": 3600,
"token": "8a6c71b3-fa62-434d-8b38-907de24c3176"
}
```
#### Validate Token
Valida si un token existente es válido.
**Parámetros requeridos:**
- **Token**: Token a validar
## Uso en Workflows
### Flujo básico de autenticación
1. Configure el nodo **S4DS Auth** con la operación "Generate Token"
2. Configure las credenciales y parámetros de autenticación
3. El token se almacenará automáticamente en el contexto del workflow
4. Use el token en otros nodos de API S4DS
### Acceso al token desde otros nodos
El token se almacena en el contexto del workflow y puede ser accedido usando:
```
{{ $context.s4ds_token }}
```
O si configuró una clave personalizada:
```
{{ $context.your_custom_key }}
```
## URLs de API
### Demo
- **Test**: `https://demotest.s4ds.com/demoapi-test`
- **UAT**: `https://demouat.s4ds.com/demoapi-uat`
- **Production**: `https://demoprod.s4ds.com/demoapi-prod`
### Cliente 1
- **Test**: `https://cliente1test.s4ds.com/cliente1api-test`
- **UAT**: `https://cliente1uat.s4ds.com/cliente1api-uat`
- **Production**: `https://cliente1prod.s4ds.com/cliente1api-prod`
### Cliente 2
- **Test**: `https://cliente2test.s4ds.com/cliente2api-test`
- **UAT**: `https://cliente2uat.s4ds.com/cliente2api-uat`
- **Production**: `https://cliente2prod.s4ds.com/cliente2api-prod`
## Manejo de Errores
El nodo maneja automáticamente:
- Errores de autenticación (credenciales inválidas)
- Errores de red y timeout
- Tokens expirados
- Errores de formato de respuesta
## Ejemplo de Workflow
```
[S4DS Auth: Generate Token] → [HTTP Request: API Call] → [Process Data]
```
En el nodo HTTP Request, configure el header de autorización:
```
Authorization: Bearer {{ $context.s4ds_token }}
```

View file

@ -0,0 +1,168 @@
import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { authOperations, authFields } from './AuthDescription';
export class S4DSAuth implements INodeType {
description: INodeTypeDescription = {
displayName: 'S4DS Authentication',
name: 's4dsAuth',
icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' },
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Authenticate with S4DS API and manage tokens',
defaults: {
name: 'S4DS Auth',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
credentials: [
{
name: 's4dsApi',
required: true,
},
],
requestDefaults: {
baseURL: '={{$credentials.s4dsApi.baseUrl === "custom" ? $credentials.s4dsApi.customBaseUrl : $credentials.s4dsApi.baseUrl}}',
url: '',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Authentication',
value: 'auth',
},
],
default: 'auth',
},
...authOperations,
...authFields,
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const resource = this.getNodeParameter('resource', i) as string;
const operation = this.getNodeParameter('operation', i) as string;
if (resource === 'auth') {
if (operation === 'generateToken') {
// Obtener credenciales
const credentials = await this.getCredentials('s4dsApi');
// Determinar la URL base
let baseUrl = credentials.baseUrl;
if (baseUrl === 'custom') {
baseUrl = credentials.customBaseUrl;
}
if (!baseUrl) {
throw new Error('Base URL is required. Please configure the S4DS API credentials.');
}
// Ejecutar la petición HTTP para generar token
const response = await this.helpers.httpRequest({
method: 'POST',
url: `${baseUrl}/login/generateToken`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: {
username: credentials.username,
password: credentials.password,
},
});
// Almacenar token en contexto
const additionalFields = this.getNodeParameter('additionalFields', i) as {
storeInContext?: boolean;
tokenContextKey?: string;
};
const contextKey = additionalFields.tokenContextKey || 's4ds_token';
const tokenData = {
token: response.token,
token_type: response.token_type,
expires_in: response.expires_in,
expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(),
authorization_header: `${response.token_type} ${response.token}`,
};
// Almacenar en contexto del workflow
this.getWorkflowStaticData('global')[contextKey] = tokenData;
// Retornar respuesta sin el token por seguridad
const secureResponse = {
success: true,
token_type: response.token_type,
expires_in: response.expires_in,
expires_at: tokenData.expires_at,
message: 'Token generated successfully and stored in workflow context',
context_key: contextKey,
};
returnData.push({
json: secureResponse,
});
} else if (operation === 'validateToken') {
const token = this.getNodeParameter('token', i) as string;
// Obtener credenciales
const credentials = await this.getCredentials('s4dsApi');
// Determinar la URL base
let baseUrl = credentials.baseUrl;
if (baseUrl === 'custom') {
baseUrl = credentials.customBaseUrl;
}
if (!baseUrl) {
throw new Error('Base URL is required. Please configure the S4DS API credentials.');
}
// Ejecutar validación de token
const response = await this.helpers.httpRequest({
method: 'GET',
url: `${baseUrl}/auth/validate`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
returnData.push({
json: response,
});
}
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}
return [returnData];
}
}

View file

@ -0,0 +1,80 @@
import { IExecuteFunctions } from 'n8n-workflow';
export interface S4DSTokenData {
token: string;
token_type: string;
expires_in: number;
expires_at: string;
authorization_header: string;
}
export class S4DSHelper {
/**
* Obtiene el token de autorización del contexto del workflow
* @param this - Contexto de ejecución del nodo
* @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token')
* @returns Objeto con datos del token incluyendo el header de autorización
*/
static getTokenFromContext(this: IExecuteFunctions, contextKey: string = 's4ds_token'): S4DSTokenData {
const tokenData = this.getWorkflowStaticData('global')[contextKey] as S4DSTokenData;
if (!tokenData || !tokenData.token) {
throw new Error(`No S4DS token found in context with key: ${contextKey}. Please run S4DS Auth node first.`);
}
// Verificar si el token ha expirado
if (tokenData.expires_at && new Date(tokenData.expires_at) <= new Date()) {
throw new Error('S4DS token has expired. Please regenerate token using S4DS Auth node.');
}
return tokenData;
}
/**
* Obtiene el header de autorización listo para usar
* @param this - Contexto de ejecución del nodo
* @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token')
* @returns Header de autorización completo (ej: "Bearer 8a6c71b3-fa62-434d-8b38-907de24c3176")
*/
static getAuthorizationHeader(this: IExecuteFunctions, contextKey: string = 's4ds_token'): string {
const tokenData = S4DSHelper.getTokenFromContext.call(this, contextKey);
return tokenData.authorization_header;
}
/**
* Obtiene headers HTTP completos con autorización
* @param this - Contexto de ejecución del nodo
* @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token')
* @param additionalHeaders - Headers adicionales opcionales
* @returns Objeto con headers HTTP completos
*/
static getHeadersWithAuth(
this: IExecuteFunctions,
contextKey: string = 's4ds_token',
additionalHeaders: Record<string, string> = {}
): Record<string, string> {
const authHeader = S4DSHelper.getAuthorizationHeader.call(this, contextKey);
return {
Authorization: authHeader,
Accept: 'application/json',
'Content-Type': 'application/json',
...additionalHeaders,
};
}
/**
* Verifica si existe un token válido en el contexto
* @param this - Contexto de ejecución del nodo
* @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token')
* @returns true si existe un token válido, false en caso contrario
*/
static hasValidToken(this: IExecuteFunctions, contextKey: string = 's4ds_token'): boolean {
try {
S4DSHelper.getTokenFromContext.call(this, contextKey);
return true;
} catch {
return false;
}
}
}

5
nodes/S4DSAuth/s4ds.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View file

@ -0,0 +1,194 @@
import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { S4DSHelper } from '../S4DSAuth/S4DSHelper';
export class S4DSExample implements INodeType {
description: INodeTypeDescription = {
displayName: 'S4DS Example',
name: 's4dsExample',
icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' },
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Example node that uses S4DS authentication token',
defaults: {
name: 'S4DS Example',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 's4dsApi',
required: true,
},
],
requestDefaults: {
baseURL: '={{$credentials.s4dsApi.baseUrl}}',
url: '',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Get Product Count',
value: 'getProductCount',
description: 'Get the total count of products',
action: 'Get product count',
routing: {
request: {
method: 'GET',
url: '/product/count',
},
},
},
],
default: 'getProductCount',
},
{
displayName: 'Token Source',
name: 'tokenSource',
type: 'options',
required: true,
default: 'context',
options: [
{
name: 'From Context',
value: 'context',
description: 'Use token stored in workflow context',
},
{
name: 'Custom Token',
value: 'custom',
description: 'Provide custom token',
},
],
},
{
displayName: 'Context Token Key',
name: 'contextTokenKey',
type: 'string',
default: 's4ds_token',
required: true,
displayOptions: {
show: {
tokenSource: ['context'],
},
},
description: 'Key used to store token in workflow context',
},
{
displayName: 'Custom Token',
name: 'customToken',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
tokenSource: ['custom'],
},
},
description: 'Custom authentication token',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i) as string;
const tokenSource = this.getNodeParameter('tokenSource', i) as string;
// Obtener headers según la fuente
let headers: Record<string, string>;
if (tokenSource === 'context') {
const contextKey = this.getNodeParameter('contextTokenKey', i) as string;
// Debug: Verificar qué hay en el contexto
const contextData = this.getWorkflowStaticData('global');
const availableKeys = Object.keys(contextData);
// Usar el helper para obtener headers con autorización
try {
headers = S4DSHelper.getHeadersWithAuth.call(this, contextKey);
} catch (error) {
// Si falla, retornar información de debug
returnData.push({
json: {
error: error.message,
debug_info: {
context_key_requested: contextKey,
available_context_keys: availableKeys,
context_data: contextData,
message: 'Please ensure S4DS Auth node runs before this node and generates a token successfully.'
}
},
});
continue;
}
} else {
const customToken = this.getNodeParameter('customToken', i) as string;
headers = {
Authorization: customToken,
Accept: 'application/json',
'Content-Type': 'application/json',
};
}
// Ejecutar la operación
let response;
if (operation === 'getProductCount') {
// Obtener credenciales para construir la URL base
const credentials = await this.getCredentials('s4dsApi');
// Determinar la URL base
let baseUrl = credentials.baseUrl;
if (baseUrl === 'custom') {
baseUrl = credentials.customBaseUrl;
}
if (!baseUrl) {
throw new Error('Base URL is required. Please configure the S4DS API credentials.');
}
response = await this.helpers.httpRequest({
method: 'GET',
url: `${baseUrl}/product/count`,
headers,
});
}
returnData.push({
json: {
...response,
_token_info: {
source: tokenSource,
},
},
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}
return [returnData];
}
}