mirror of
				https://github.com/n8n-io/n8n-nodes-starter.git
				synced 2025-10-30 14:52:27 -05:00 
			
		
		
		
	S4DSAuth node added. Some other nodes deleted
This commit is contained in:
		
					parent
					
						
							
								21f7bc4eae
							
						
					
				
			
			
				commit
				
					
						392231e695
					
				
			
		
					 16 changed files with 4910 additions and 222 deletions
				
			
		
							
								
								
									
										179
									
								
								README_S4DS.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								README_S4DS.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | ||||||
|  | # S4DS Nodes para n8n | ||||||
|  | 
 | ||||||
|  | Este paquete de nodos personalizados para n8n proporciona integración completa con las APIs de S4DS, incluyendo autenticación y operaciones de ejemplo. | ||||||
|  | 
 | ||||||
|  | ## Nodos Incluidos | ||||||
|  | 
 | ||||||
|  | ### 1. S4DS Authentication | ||||||
|  | Nodo principal para autenticación con las APIs de S4DS. | ||||||
|  | 
 | ||||||
|  | **Características:** | ||||||
|  | - Generación de tokens Bearer | ||||||
|  | - Validación de tokens existentes | ||||||
|  | - Soporte para múltiples ambientes (Test, UAT, Production) | ||||||
|  | - Soporte para múltiples clientes (Demo, Cliente 1, Cliente 2) | ||||||
|  | - Almacenamiento automático de tokens en contexto del workflow | ||||||
|  | - Gestión de expiración de tokens | ||||||
|  | 
 | ||||||
|  | **Operaciones:** | ||||||
|  | - **Generate Token**: Genera un nuevo token de autenticación | ||||||
|  | - **Validate Token**: Valida un token existente | ||||||
|  | 
 | ||||||
|  | ### 2. S4DS Example | ||||||
|  | Nodo de ejemplo que demuestra cómo usar el token de autenticación. | ||||||
|  | 
 | ||||||
|  | **Características:** | ||||||
|  | - Consume tokens del contexto del workflow | ||||||
|  | - Soporte para tokens personalizados | ||||||
|  | - Verificación automática de expiración | ||||||
|  | - Operaciones de ejemplo con la API | ||||||
|  | 
 | ||||||
|  | **Operaciones:** | ||||||
|  | - **Get Product Count**: Obtiene la cantidad total de productos | ||||||
|  | 
 | ||||||
|  | ## Instalación | ||||||
|  | 
 | ||||||
|  | 1. Clone este repositorio | ||||||
|  | 2. Instale las dependencias: | ||||||
|  |    ```bash | ||||||
|  |    npm install | ||||||
|  |    ``` | ||||||
|  | 3. Compile el proyecto: | ||||||
|  |    ```bash | ||||||
|  |    npm run build | ||||||
|  |    ``` | ||||||
|  | 4. Instale el paquete en n8n: | ||||||
|  |    ```bash | ||||||
|  |    npm install -g . | ||||||
|  |    ``` | ||||||
|  | 
 | ||||||
|  | ## Configuración | ||||||
|  | 
 | ||||||
|  | ### Credenciales S4DS API | ||||||
|  | 
 | ||||||
|  | Configure las credenciales de S4DS API en n8n: | ||||||
|  | 
 | ||||||
|  | 1. Vaya a **Settings** → **Credentials** | ||||||
|  | 2. Haga clic en **Add Credential** | ||||||
|  | 3. Seleccione **S4DS API** | ||||||
|  | 4. Configure: | ||||||
|  |    - **Base URL**: Seleccione el ambiente y cliente | ||||||
|  |    - **Custom Base URL**: Para URLs personalizadas | ||||||
|  |    - **Timeout**: Tiempo de espera para peticiones | ||||||
|  | 
 | ||||||
|  | ### URLs Disponibles | ||||||
|  | 
 | ||||||
|  | #### 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` | ||||||
|  | 
 | ||||||
|  | ## Uso | ||||||
|  | 
 | ||||||
|  | ### Workflow Básico | ||||||
|  | 
 | ||||||
|  | 1. **Configurar Credenciales**: | ||||||
|  |    - Configure las credenciales de S4DS API con usuario, contraseña y ambiente | ||||||
|  |    - Agregue el nodo **S4DS Authentication** | ||||||
|  |    - Configure la operación "Generate Token" | ||||||
|  | 
 | ||||||
|  | 2. **Usar el Token**: | ||||||
|  |    - Agregue el nodo **S4DS Example** o **HTTP Request** | ||||||
|  |    - Configure para usar el token del contexto | ||||||
|  |    - Ejecute las operaciones deseadas | ||||||
|  | 
 | ||||||
|  | ### Ejemplo de Workflow | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | [S4DS Auth: Generate Token] → [S4DS Example: Get User Profile] → [Process Data] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Acceso al Token | ||||||
|  | 
 | ||||||
|  | El token se almacena automáticamente en el contexto del workflow y puede ser accedido usando: | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | {{ $context.s4ds_token }} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | O con clave personalizada: | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | {{ $context.your_custom_key }} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Estructura del Token | ||||||
|  | 
 | ||||||
|  | El token se almacena con la siguiente estructura: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "token": "8a6c71b3-fa62-434d-8b38-907de24c3176", | ||||||
|  |   "token_type": "Bearer", | ||||||
|  |   "expires_in": 3600, | ||||||
|  |   "expires_at": "2024-01-01T12:00:00.000Z" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Manejo de Errores | ||||||
|  | 
 | ||||||
|  | Los nodos manejan automáticamente: | ||||||
|  | - Errores de autenticación | ||||||
|  | - Tokens expirados | ||||||
|  | - Errores de red | ||||||
|  | - Timeouts | ||||||
|  | - Respuestas malformadas | ||||||
|  | 
 | ||||||
|  | ## Desarrollo | ||||||
|  | 
 | ||||||
|  | ### Estructura del Proyecto | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | n8n-nodes-starter-s4ds/ | ||||||
|  | ├── nodes/ | ||||||
|  | │   ├── S4DSAuth/           # Nodo de autenticación | ||||||
|  | │   ├── S4DSExample/        # Nodo de ejemplo | ||||||
|  | │   └── ... | ||||||
|  | ├── credentials/ | ||||||
|  | │   └── S4DSApi.credentials.ts | ||||||
|  | ├── package.json | ||||||
|  | └── index.js | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Comandos de Desarrollo | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Compilar | ||||||
|  | npm run build | ||||||
|  | 
 | ||||||
|  | # Desarrollo con watch | ||||||
|  | npm run dev | ||||||
|  | 
 | ||||||
|  | # Linting | ||||||
|  | npm run lint | ||||||
|  | 
 | ||||||
|  | # Formatear código | ||||||
|  | npm run format | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Contribución | ||||||
|  | 
 | ||||||
|  | 1. Fork el repositorio | ||||||
|  | 2. Cree una rama para su feature | ||||||
|  | 3. Haga commit de sus cambios | ||||||
|  | 4. Push a la rama | ||||||
|  | 5. Abra un Pull Request | ||||||
|  | 
 | ||||||
|  | ## Licencia | ||||||
|  | 
 | ||||||
|  | MIT License - vea el archivo LICENSE para más detalles.  | ||||||
|  | @ -1,59 +0,0 @@ | ||||||
| import { |  | ||||||
| 	IAuthenticateGeneric, |  | ||||||
| 	ICredentialTestRequest, |  | ||||||
| 	ICredentialType, |  | ||||||
| 	INodeProperties, |  | ||||||
| } from 'n8n-workflow'; |  | ||||||
| 
 |  | ||||||
| export class ExampleCredentialsApi implements ICredentialType { |  | ||||||
| 	name = 'exampleCredentialsApi'; |  | ||||||
| 	displayName = 'Example Credentials API'; |  | ||||||
| 
 |  | ||||||
| 	documentationUrl = 'https://your-docs-url'; |  | ||||||
| 
 |  | ||||||
| 	properties: INodeProperties[] = [ |  | ||||||
| 		// The credentials to get from user and save encrypted.
 |  | ||||||
| 		// Properties can be defined exactly in the same way
 |  | ||||||
| 		// as node properties.
 |  | ||||||
| 		{ |  | ||||||
| 			displayName: 'User Name', |  | ||||||
| 			name: 'username', |  | ||||||
| 			type: 'string', |  | ||||||
| 			default: '', |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			displayName: 'Password', |  | ||||||
| 			name: 'password', |  | ||||||
| 			type: 'string', |  | ||||||
| 			typeOptions: { |  | ||||||
| 				password: true, |  | ||||||
| 			}, |  | ||||||
| 			default: '', |  | ||||||
| 		}, |  | ||||||
| 	]; |  | ||||||
| 
 |  | ||||||
| 	// This credential is currently not used by any node directly
 |  | ||||||
| 	// but the HTTP Request node can use it to make requests.
 |  | ||||||
| 	// The credential is also testable due to the `test` property below
 |  | ||||||
| 	authenticate: IAuthenticateGeneric = { |  | ||||||
| 		type: 'generic', |  | ||||||
| 		properties: { |  | ||||||
| 			auth: { |  | ||||||
| 				username: '={{ $credentials.username }}', |  | ||||||
| 				password: '={{ $credentials.password }}', |  | ||||||
| 			}, |  | ||||||
| 			qs: { |  | ||||||
| 				// Send this as part of the query string
 |  | ||||||
| 				n8n: 'rocks', |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	// The block below tells how this credential can be tested
 |  | ||||||
| 	test: ICredentialTestRequest = { |  | ||||||
| 		request: { |  | ||||||
| 			baseURL: 'https://example.com/', |  | ||||||
| 			url: '', |  | ||||||
| 		}, |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
							
								
								
									
										100
									
								
								credentials/S4DSApi.credentials.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								credentials/S4DSApi.credentials.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,100 @@ | ||||||
|  | import { | ||||||
|  | 	ICredentialType, | ||||||
|  | 	INodeProperties, | ||||||
|  | } from 'n8n-workflow'; | ||||||
|  | 
 | ||||||
|  | export class S4DSApi implements ICredentialType { | ||||||
|  | 	name = 's4dsApi'; | ||||||
|  | 	displayName = 'S4DS API'; | ||||||
|  | 	documentationUrl = 'https://docs.n8n.io/integrations/builtin/credentials/s4ds/'; | ||||||
|  | 	properties: INodeProperties[] = [ | ||||||
|  | 		{ | ||||||
|  | 			displayName: 'Base URL', | ||||||
|  | 			name: 'baseUrl', | ||||||
|  | 			type: 'options', | ||||||
|  | 			options: [ | ||||||
|  | 				{ | ||||||
|  | 					name: 'Demo Test', | ||||||
|  | 					value: 'https://demotest.s4ds.com/demoapi-test', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Demo UAT', | ||||||
|  | 					value: 'https://demouat.s4ds.com/demoapi-uat', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Demo Production', | ||||||
|  | 					value: 'https://demoprod.s4ds.com/demoapi-prod', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 1 Test', | ||||||
|  | 					value: 'https://cliente1test.s4ds.com/cliente1api-test', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 1 UAT', | ||||||
|  | 					value: 'https://cliente1uat.s4ds.com/cliente1api-uat', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 1 Production', | ||||||
|  | 					value: 'https://cliente1prod.s4ds.com/cliente1api-prod', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 2 Test', | ||||||
|  | 					value: 'https://cliente2test.s4ds.com/cliente2api-test', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 2 UAT', | ||||||
|  | 					value: 'https://cliente2uat.s4ds.com/cliente2api-uat', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Cliente 2 Production', | ||||||
|  | 					value: 'https://cliente2prod.s4ds.com/cliente2api-prod', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name: 'Custom URL', | ||||||
|  | 					value: 'custom', | ||||||
|  | 				}, | ||||||
|  | 			], | ||||||
|  | 			default: 'https://demotest.s4ds.com/demoapi-test', | ||||||
|  | 			description: 'Select the S4DS environment and client', | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			displayName: 'Custom Base URL', | ||||||
|  | 			name: 'customBaseUrl', | ||||||
|  | 			type: 'string', | ||||||
|  | 			default: '', | ||||||
|  | 			placeholder: 'https://your-custom-domain.s4ds.com/yourapi', | ||||||
|  | 			description: 'Custom base URL for S4DS API', | ||||||
|  | 			displayOptions: { | ||||||
|  | 				show: { | ||||||
|  | 					baseUrl: ['custom'], | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			displayName: 'Username', | ||||||
|  | 			name: 'username', | ||||||
|  | 			type: 'string', | ||||||
|  | 			default: '', | ||||||
|  | 			required: true, | ||||||
|  | 			description: 'Username for S4DS authentication', | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			displayName: 'Password', | ||||||
|  | 			name: 'password', | ||||||
|  | 			type: 'string', | ||||||
|  | 			typeOptions: { | ||||||
|  | 				password: true, | ||||||
|  | 			}, | ||||||
|  | 			default: '', | ||||||
|  | 			required: true, | ||||||
|  | 			description: 'Password for S4DS authentication', | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			displayName: 'Timeout', | ||||||
|  | 			name: 'timeout', | ||||||
|  | 			type: 'number', | ||||||
|  | 			default: 30000, | ||||||
|  | 			description: 'Request timeout in milliseconds', | ||||||
|  | 		}, | ||||||
|  | 	]; | ||||||
|  | }  | ||||||
							
								
								
									
										3944
									
								
								cursor_nodo_personalizado.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3944
									
								
								cursor_nodo_personalizado.md
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -21,7 +21,7 @@ if [ -z "$PACKAGE_NAME" ]; then | ||||||
| fi | fi | ||||||
|   |   | ||||||
| # Set the target directory based on the package name. | # Set the target directory based on the package name. | ||||||
| TARGET_DIR="/var/lib/docker/volumes/n8n-self-hosted_n8n_data/_data/custom/$PACKAGE_NAME" | TARGET_DIR="/var/lib/docker/volumes/n8n-nodes-starter-s4ds_n8n_data/_data/custom/$PACKAGE_NAME" | ||||||
|   |   | ||||||
| echo "Detected package name: '$PACKAGE_NAME'" | echo "Detected package name: '$PACKAGE_NAME'" | ||||||
| echo "Target deployment directory: '$TARGET_DIR'" | echo "Target deployment directory: '$TARGET_DIR'" | ||||||
|  | @ -53,7 +53,7 @@ echo "Deployment complete." | ||||||
| # Step 3: Restart n8n | # Step 3: Restart n8n | ||||||
| ############################## | ############################## | ||||||
| echo "Restarting n8n..." | echo "Restarting n8n..." | ||||||
| docker container restart n8n-self-hosted-n8n-1 | docker container restart n8n-nodes-starter-s4ds-n8n-1 | ||||||
|   |   | ||||||
| # Logging for debugging | # Logging for debugging | ||||||
| docker logs -f n8n-self-hosted-n8n-1 | docker logs -f n8n-nodes-starter-s4ds-n8n-1 | ||||||
|  | @ -24,7 +24,7 @@ services: | ||||||
|     image: docker.n8n.io/n8nio/n8n |     image: docker.n8n.io/n8nio/n8n | ||||||
|     restart: always |     restart: always | ||||||
|     ports: |     ports: | ||||||
|       - "127.0.0.1:5678:5678" |       - "5678:5678" | ||||||
|     labels: |     labels: | ||||||
|       - traefik.enable=true |       - traefik.enable=true | ||||||
|       - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) |       - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								index.js
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								index.js
									
										
									
									
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | const { S4DSAuth } = require('./dist/nodes/S4DSAuth/S4DSAuth.node.js'); | ||||||
|  | const { S4DSExample } = require('./dist/nodes/S4DSExample/S4DSExample.node.js'); | ||||||
|  | const { HttpBin } = require('./dist/nodes/HttpBin/HttpBin.node.js'); | ||||||
|  | 
 | ||||||
|  | const { S4DSApi } = require('./dist/credentials/S4DSApi.credentials.js'); | ||||||
|  | const { HttpBinApi } = require('./dist/credentials/HttpBinApi.credentials.js'); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  | 	nodes: [S4DSAuth, S4DSExample, HttpBin], | ||||||
|  | 	credentials: [S4DSApi, HttpBinApi], | ||||||
|  | }; | ||||||
|  | @ -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]; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -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]; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										109
									
								
								nodes/S4DSAuth/AuthDescription.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								nodes/S4DSAuth/AuthDescription.ts
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										111
									
								
								nodes/S4DSAuth/README.md
									
										
									
									
									
										Normal 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 }} | ||||||
|  | ```  | ||||||
							
								
								
									
										168
									
								
								nodes/S4DSAuth/S4DSAuth.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								nodes/S4DSAuth/S4DSAuth.node.ts
									
										
									
									
									
										Normal 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]; | ||||||
|  | 	} | ||||||
|  | }  | ||||||
							
								
								
									
										80
									
								
								nodes/S4DSAuth/S4DSHelper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								nodes/S4DSAuth/S4DSHelper.ts
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										5
									
								
								nodes/S4DSAuth/s4ds.svg
									
										
									
									
									
										Normal 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 | 
							
								
								
									
										194
									
								
								nodes/S4DSExample/S4DSExample.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								nodes/S4DSExample/S4DSExample.node.ts
									
										
									
									
									
										Normal 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]; | ||||||
|  | 	} | ||||||
|  | }  | ||||||
							
								
								
									
										10
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
										
									
									
									
								
							|  | @ -33,13 +33,13 @@ | ||||||
|   "n8n": { |   "n8n": { | ||||||
|     "n8nNodesApiVersion": 1, |     "n8nNodesApiVersion": 1, | ||||||
|     "credentials": [ |     "credentials": [ | ||||||
|       "dist/credentials/ExampleCredentialsApi.credentials.js", |       "dist/credentials/HttpBinApi.credentials.js", | ||||||
|       "dist/credentials/HttpBinApi.credentials.js" |       "dist/credentials/S4DSApi.credentials.js" | ||||||
|     ], |     ], | ||||||
|     "nodes": [ |     "nodes": [ | ||||||
|       "dist/nodes/ExampleNode/ExampleNode.node.js", |       "dist/nodes/HttpBin/HttpBin.node.js", | ||||||
|       "dist/nodes/ExampleNode2/ExampleNode2.node.js", |       "dist/nodes/S4DSAuth/S4DSAuth.node.js", | ||||||
|       "dist/nodes/HttpBin/HttpBin.node.js" |       "dist/nodes/S4DSExample/S4DSExample.node.js" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue