606 lines
21 KiB
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": "of8yoeyjjIAhYdnQ",
|
|
"isArchived": false
|
|
} |