Files
n8n-backup-v2/workflows/3l7tJfcRoA1T1o6g.json

606 lines
21 KiB
JSON

{
"id": "3l7tJfcRoA1T1o6g",
"name": "NPM Auth Token Manager",
"nodes": [
{
"parameters": {},
"id": "start-node",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
240,
320
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "months",
"monthsInterval": 11
}
]
}
},
"id": "schedule-node",
"name": "Schedule Token Refresh",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
240,
480
],
"notes": "Runs every 11 months to check token validity and refresh if needed"
},
{
"parameters": {
"jsCode": "// Check if existing token is still valid based on expiration date\nconst items_data = items || [];\n\nconsole.log('=== Token Validation Starting ===');\nconsole.log('Items received:', items_data.length);\n\n// If no token exists or Supabase query returned no data, proceed to generate new one\nif (items_data.length === 0) {\n console.log('❌ No items received from Supabase query.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_token_found',\n message: 'No token exists in database'\n }\n }\n ];\n}\n\nconst firstItem = items_data[0].json || {};\nconsole.log('First item keys:', Object.keys(firstItem));\n\n// Check if the item has actual token data\nif (Object.keys(firstItem).length === 0 || !firstItem.token) {\n console.log('❌ No token data found in Supabase result.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_token_found',\n message: 'Token field is empty or missing'\n }\n }\n ];\n}\n\nconst tokenRecord = firstItem;\nconst expiresAt = tokenRecord.expires_at;\n\n// Check if expires_at exists and is in the future\nif (!expiresAt) {\n console.log('⚠️ Token has no expiration date.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_expiration_date',\n message: 'Token missing expiration date',\n existingToken: tokenRecord\n }\n }\n ];\n}\n\n// Parse expiration date\nconst expirationDate = new Date(expiresAt);\nconst now = new Date();\nconst daysUntilExpiration = Math.floor((expirationDate - now) / (1000 * 60 * 60 * 24));\n\nconsole.log('=== Token Expiration Check ===');\nconsole.log('Current time:', now.toISOString());\nconsole.log('Token expires:', expirationDate.toISOString());\nconsole.log('Days until expiration:', daysUntilExpiration);\n\n// If token expires in less than 30 days, regenerate\nif (daysUntilExpiration < 30) {\n console.log('⚠️ Token expires in less than 30 days.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'expiring_soon',\n message: `Token expires in ${daysUntilExpiration} days`,\n daysUntilExpiration: daysUntilExpiration,\n existingToken: tokenRecord\n }\n }\n ];\n}\n\n// Token expiration looks good, proceed to API validation\nconsole.log('✅ Token expiration date is valid (', daysUntilExpiration, 'days remaining)');\nconsole.log('→ Next: Testing token against NPM API...');\nreturn [\n {\n json: {\n tokenValid: true,\n reason: 'expiration_valid',\n message: `Token expires in ${daysUntilExpiration} days`,\n daysUntilExpiration: daysUntilExpiration,\n existingToken: tokenRecord\n }\n }\n];"
},
"id": "validate-existing-token-node",
"name": "Check Token Expiration",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
400
],
"alwaysOutputData": true,
"notes": "Step 1: Validates token expiration date. Handles all edge cases for empty/missing data. Always returns a clear tokenValid boolean for routing."
},
{
"parameters": {
"url": "https://npm.dfw.ben.io/api/nginx/proxy-hosts",
"options": {
"timeout": 5000
},
"headerParametersUi": {
"parameter": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.existingToken.token }}"
}
]
}
},
"id": "test-token-validity-node",
"name": "Test Token Against NPM API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1040,
288
],
"notes": "Tests if the existing token actually works by making a test API call to NPM. Returns 200 if valid, 401 if invalid."
},
{
"parameters": {
"jsCode": "// Check if the token actually works with NPM API\nconst response = $input.item.json;\nconst statusCode = response.statusCode || 200;\nconst previousValidation = $('Check Token Expiration').item.json;\n\nconsole.log('=== NPM Token API Test ===');\nconsole.log('API Response Status:', statusCode);\n\n// Token works if we get 200 OK\nif (statusCode === 200 || statusCode === 304) {\n console.log('✅ Token successfully validated against NPM API');\n console.log('Token expires in', previousValidation.daysUntilExpiration, 'days');\n return [{\n json: {\n tokenValid: true,\n apiVerified: true,\n daysUntilExpiration: previousValidation.daysUntilExpiration,\n existingToken: previousValidation.existingToken\n }\n }];\n}\n\n// Token is invalid (401 Unauthorized or other error)\nconsole.log('❌ Token validation failed against NPM API');\nconsole.log('Status code:', statusCode);\nreturn [{\n json: {\n tokenValid: false,\n apiVerified: false,\n reason: 'api_validation_failed',\n statusCode: statusCode,\n daysUntilExpiration: previousValidation.daysUntilExpiration\n }\n}];"
},
"id": "evaluate-token-test-node",
"name": "Evaluate API Test Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1248,
288
],
"notes": "Evaluates the API test response. If 200/304, token is valid. If 401 or other error, token needs regeneration."
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.tokenValid }}",
"value2": true
}
]
},
"options": {}
},
"id": "route-based-on-validity-node",
"name": "API Test Passed?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1440,
400
],
"notes": "Routes based on API validation result. True = token valid and working. False = regenerate token."
},
{
"parameters": {
"jsCode": "console.log('✅ Token fully validated and working!');\nconsole.log('- Expiration: Valid (', $json.daysUntilExpiration, 'days remaining)');\nconsole.log('- API Test: Passed');\nconsole.log('No action needed. Next check in ~11 months.');\n\nreturn [{\n json: {\n status: 'skipped',\n message: 'Existing token is valid and working',\n daysUntilExpiration: $json.daysUntilExpiration,\n expiresAt: $json.existingToken.expires_at,\n apiVerified: true\n }\n}];"
},
"id": "token-still-valid-node",
"name": "Token Valid - Skip",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1648,
288
],
"notes": "Token passed both expiration and API validation. No regeneration needed."
},
{
"parameters": {
"method": "POST",
"url": "https://npm.dfw.ben.io/api/tokens",
"authentication": "genericCredentialType",
"genericAuthType": "httpCustomAuth",
"options": {}
},
"id": "npm-login-node",
"name": "NPM Login",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1168,
656
],
"credentials": {
"httpCustomAuth": {
"id": "7XEtlKn9X5YzJfiJ",
"name": "NPM Admin Credentials"
}
},
"notes": "Authenticates with NPM using stored credentials to generate new token"
},
{
"parameters": {
"url": "https://npm.dfw.ben.io/api/tokens?expiry=1y",
"options": {},
"headerParametersUi": {
"parameter": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.token }}"
}
]
}
},
"id": "get-long-lived-token-node",
"name": "Get Long-Lived Token",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1360,
656
],
"notes": "Requests a 1-year long-lived token"
},
{
"parameters": {
"jsCode": "// Extract the long-lived token from the response\nconst token = $input.item.json.token;\nconst expiresAt = $input.item.json.expires;\n\nconsole.log('Generated new NPM long-lived token');\nconsole.log('Expires at:', expiresAt);\n\nreturn {\n json: {\n token: token,\n expiresAt: expiresAt,\n generatedAt: new Date().toISOString(),\n tokenPreview: token.substring(0, 20) + '...'\n }\n};"
},
"id": "extract-token-node",
"name": "Extract Token",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1568,
656
],
"notes": "Extracts and formats the token for storage"
},
{
"parameters": {
"jsCode": "// Prepare the record for Supabase storage\nconst inputData = $input.item.json;\n\nreturn {\n json: {\n service_name: 'npm_dfw',\n token: inputData.token,\n expires_at: inputData.expiresAt,\n generated_at: inputData.generatedAt,\n last_updated: new Date().toISOString()\n }\n};"
},
"id": "prepare-token-record",
"name": "Prepare Token Record",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1760,
656
],
"notes": "Formats the token data for Supabase storage"
},
{
"parameters": {
"jsCode": "// Check if UPDATE found and updated a row\nconst input = $input.item.json || {};\nconst hasData = Object.keys(input).length > 0;\n\nconsole.log('=== Checking UPDATE Result ===');\nconsole.log('Input item:', JSON.stringify(input, null, 2));\nconsole.log('Has data:', hasData);\nconsole.log('Keys:', Object.keys(input));\n\n// Check if we have the key database fields\nconst hasToken = input.token !== undefined && input.token !== null;\nconst hasServiceName = input.service_name !== undefined && input.service_name !== null;\n\nconsole.log('Has token field:', hasToken);\nconsole.log('Has service_name field:', hasServiceName);\n\n// If UPDATE returned token data, it succeeded\nif (hasData && (hasToken || hasServiceName)) {\n console.log('✅ UPDATE succeeded - row was found and updated');\n return { json: { ...input, needsCreate: false, operation: 'updated' } };\n}\n\n// UPDATE found no rows - need to CREATE\nconsole.log('⚠️ UPDATE found no rows or returned empty - will CREATE new row');\nconst tokenData = $('Prepare Token Record').item.json;\nreturn { json: { ...tokenData, needsCreate: true, operation: 'create_needed' } };"
},
"id": "check-update-result",
"name": "Did Update Succeed?",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2112,
656
],
"alwaysOutputData": true,
"notes": "Checks if UPDATE succeeded. If no rows were updated, routes to CREATE."
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.needsCreate }}",
"value2": true
}
]
},
"options": {}
},
"id": "route-create-check",
"name": "Need to Create?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2288,
656
],
"notes": "Routes to CREATE if UPDATE found no rows (first run). Otherwise skips to success."
},
{
"parameters": {
"jsCode": "console.log('✅ NPM Token successfully refreshed and stored');\nconsole.log('Token expires at:', $input.item.json.expires_at || 'N/A');\nconsole.log('Operation:', $input.item.json.operation || 'completed');\nconsole.log('Next refresh in ~11 months or when token has <30 days remaining');\n\nreturn {\n json: {\n status: 'success',\n operation: 'token_refreshed',\n message: 'NPM long-lived token refreshed and stored in Supabase',\n expiresAt: $input.item.json.expires_at\n }\n};"
},
"id": "success-notification",
"name": "Log Success",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2800,
848
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"leftValue": "={{ $json.tokenValid }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
},
"id": "f484389b-d67b-4d23-a498-4efb7afe257c"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Valid - Test API"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"leftValue": "={{ $json.tokenValid }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals"
},
"id": "ad992289-38cb-42b1-8eff-ff14b3fa47c4"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Invalid - Generate"
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"id": "switch-token-status",
"name": "Route By Token Status",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
848,
400
],
"notes": "Routes based on tokenValid. Case 0: false=Generate. Case 1: true=Test API. Fallback case 2 for unexpected values."
},
{
"id": "check-existing-token-node",
"name": "Check Existing Token (Postgres)",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
448,
400
],
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM npm_tokens WHERE service_name = 'npm_dfw' LIMIT 1;"
},
"credentials": {
"postgres": {
"id": "Ik8CFyap8ic2Md3M",
"name": "n8n-infra"
}
}
},
{
"id": "Update Token in Supabase",
"name": "Update Token in Postgres",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
2000,
400
],
"parameters": {
"operation": "executeQuery",
"options": {
"queryParameterValues": "{{$json.token}},{{$json.expires_at}},{{$json.generated_at}},{{$json.last_updated}},{{$json.service_name}}"
},
"query": "UPDATE npm_tokens SET token = $1, expires_at = $2, generated_at = $3, last_updated = $4 WHERE service_name = $5;"
},
"credentials": {
"postgres": {
"id": "Ik8CFyap8ic2Md3M",
"name": "n8n-infra"
}
}
},
{
"id": "Create Token in Supabase",
"name": "Create Token in Postgres",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
2400,
500
],
"parameters": {
"operation": "executeQuery",
"options": {
"queryParameterValues": "{{$json.service_name}},{{$json.token}},{{$json.expires_at}},{{$json.generated_at}},{{$json.last_updated}}"
},
"query": "INSERT INTO npm_tokens (service_name, token, expires_at, generated_at, last_updated) VALUES ($1, $2, $3, $4, $5);"
},
"credentials": {
"postgres": {
"id": "Ik8CFyap8ic2Md3M",
"name": "n8n-infra"
}
}
}
],
"connections": {
"Test Token Against NPM API": {
"main": [
[
{
"node": "Evaluate API Test Result",
"type": "main",
"index": 0
}
]
]
},
"Evaluate API Test Result": {
"main": [
[
{
"node": "API Test Passed?",
"type": "main",
"index": 0
}
]
]
},
"API Test Passed?": {
"main": [
[
{
"node": "Token Valid - Skip",
"type": "main",
"index": 0
}
],
[
{
"node": "NPM Login",
"type": "main",
"index": 0
}
]
]
},
"NPM Login": {
"main": [
[
{
"node": "Get Long-Lived Token",
"type": "main",
"index": 0
}
]
]
},
"Get Long-Lived Token": {
"main": [
[
{
"node": "Extract Token",
"type": "main",
"index": 0
}
]
]
},
"Extract Token": {
"main": [
[
{
"node": "Prepare Token Record",
"type": "main",
"index": 0
}
]
]
},
"Did Update Succeed?": {
"main": [
[
{
"node": "Need to Create?",
"type": "main",
"index": 0
}
]
]
},
"Need to Create?": {
"main": [
[
{
"node": "Log Success",
"type": "main",
"index": 0
},
{
"node": "Create Token in Postgres",
"type": "main",
"index": 0
}
]
]
},
"Check Token Expiration": {
"main": [
[
{
"node": "Route By Token Status",
"type": "main",
"index": 0
}
]
]
},
"Route By Token Status": {
"main": [
[
{
"node": "Test Token Against NPM API",
"type": "main",
"index": 0
}
],
[
{
"node": "NPM Login",
"type": "main",
"index": 0
}
],
[
{
"node": "NPM Login",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Check Existing Token (Postgres)",
"type": "main",
"index": 0
}
]
]
},
"Schedule Token Refresh": {
"main": [
[
{
"node": "Check Existing Token (Postgres)",
"type": "main",
"index": 0
}
]
]
},
"Check Existing Token (Postgres)": {
"main": [
[
{
"node": "Check Token Expiration",
"type": "main",
"index": 0
}
]
]
},
"Prepare Token Record": {
"main": [
[
{
"node": "Update Token in Postgres",
"type": "main",
"index": 0
}
]
]
},
"Update Token in Postgres": {
"main": [
[
{
"node": "Did Update Succeed?",
"type": "main",
"index": 0
}
]
]
},
"Create Token in Postgres": {
"main": [
[
{
"node": "Log Success",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"triggerCount": 1,
"versionId": "a927774e-18ea-41b2-b3da-9dee36c2b7d1",
"owner": {
"type": "personal",
"projectId": "FeLO36wNUAcn61Wj",
"projectName": "Ben W <admin@ben.io>",
"personalEmail": "admin@ben.io"
},
"parentFolderId": "eWW72giJDI4fxlWw",
"isArchived": false
}