File size: 7,971 Bytes
dd480ef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | /**
* Credential Intelligence System β UPGRADED
* Provider-agnostic, uses Node Registry for credential mapping
* Inspects workflow to identify required credentials
* Scans existing n8n credentials and maps types
* Detects missing credentials and blocks activation if needed
*/
import type {
N8nWorkflow,
CredentialAnalysis,
RequiredCredential,
AvailableCredential,
MissingCredential,
} from '../types/workflow';
import { N8N_NODE_REGISTRY } from '../knowledge/nodeRegistry';
// Extended node β credential map (derived from registry + additional mappings)
const NODE_CREDENTIAL_MAP: Record<string, string | undefined> = {
'n8n-nodes-base.telegram': 'telegramApi',
'n8n-nodes-base.telegramTrigger': 'telegramApi',
'n8n-nodes-base.openAi': 'openAiApi',
'@n8n/n8n-nodes-langchain.openAi': 'openAiApi',
'@n8n/n8n-nodes-langchain.agent': undefined, // depends on sub-node
'@n8n/n8n-nodes-langchain.lmChatAnthropic': 'anthropicApi',
'@n8n/n8n-nodes-langchain.memoryBufferWindow': undefined,
'@n8n/n8n-nodes-langchain.toolCode': undefined,
'n8n-nodes-base.gmail': 'googleOAuth2Api',
'n8n-nodes-base.emailReadImap': 'imap',
'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api',
'n8n-nodes-base.slack': 'slackApi',
'n8n-nodes-base.slackTrigger': 'slackOAuth2Api',
'n8n-nodes-base.airtable': 'airtableTokenApi',
'n8n-nodes-base.notion': 'notionApi',
'n8n-nodes-base.notionTrigger': 'notionApi',
'n8n-nodes-base.github': 'githubApi',
'n8n-nodes-base.githubTrigger': 'githubApi',
'n8n-nodes-base.stripe': 'stripeApi',
'n8n-nodes-base.hubspot': 'hubspotApi',
'n8n-nodes-base.postgres': 'postgres',
'n8n-nodes-base.mysql': 'mySql',
'n8n-nodes-base.mongodb': 'mongoDb',
'n8n-nodes-base.redis': 'redis',
// No credential needed
'n8n-nodes-base.httpRequest': undefined,
'n8n-nodes-base.webhook': undefined,
'n8n-nodes-base.scheduleTrigger': undefined,
'n8n-nodes-base.manualTrigger': undefined,
'n8n-nodes-base.code': undefined,
'n8n-nodes-base.set': undefined,
'n8n-nodes-base.if': undefined,
'n8n-nodes-base.switch': undefined,
'n8n-nodes-base.merge': undefined,
'n8n-nodes-base.splitInBatches': undefined,
'n8n-nodes-base.itemLists': undefined,
'n8n-nodes-base.filter': undefined,
'n8n-nodes-base.noOp': undefined,
'n8n-nodes-base.stopAndError': undefined,
'n8n-nodes-base.wait': undefined,
'n8n-nodes-base.respondToWebhook': undefined,
'n8n-nodes-base.stickyNote': undefined,
};
const CREDENTIAL_SETUP_INSTRUCTIONS: Record<string, string> = {
telegramApi: 'Create a Telegram Bot via @BotFather, copy the bot token, then add "Telegram API" credential in n8n Settings β Credentials.',
openAiApi: 'Generate an API key at https://platform.openai.com/api-keys, then add "OpenAI" credential in n8n.',
anthropicApi: 'Get your API key at https://console.anthropic.com/, then add "Anthropic" credential in n8n.',
googleOAuth2Api: 'Set up a Google Cloud project, enable Gmail API, create OAuth2 credentials, then add "Google OAuth2 API" in n8n.',
googleSheetsOAuth2Api: 'Set up Google Cloud project, enable Sheets API, create OAuth2 credentials, then add "Google Sheets OAuth2 API" in n8n.',
slackApi: 'Create a Slack App at https://api.slack.com/apps with required scopes, then add "Slack API" credential in n8n.',
slackOAuth2Api: 'Create a Slack App with OAuth2, then add "Slack OAuth2 API" credential in n8n.',
airtableTokenApi: 'Generate a Personal Access Token at https://airtable.com/account, then add "Airtable Token API" in n8n.',
notionApi: 'Create an integration at https://www.notion.so/my-integrations, then add "Notion API" credential in n8n.',
githubApi: 'Generate a Personal Access Token at https://github.com/settings/tokens, then add "GitHub API" in n8n.',
stripeApi: 'Get your API key from https://dashboard.stripe.com/apikeys, then add "Stripe API" in n8n.',
hubspotApi: 'Create a Private App in HubSpot settings, copy the access token, then add "HubSpot API" in n8n.',
postgres: 'Provide your PostgreSQL host, port, database, username, and password in "Postgres" credential in n8n.',
mySql: 'Provide your MySQL host, port, database, username, and password in "MySQL" credential in n8n.',
mongoDb: 'Provide your MongoDB connection string in "MongoDB" credential in n8n.',
redis: 'Provide your Redis host, port, and optional password in "Redis" credential in n8n.',
imap: 'Provide your IMAP server host, port, username, and password in "IMAP" credential in n8n.',
};
export class CredentialIntelligence {
private n8nBaseUrl: string;
private n8nApiKey: string;
constructor(n8nBaseUrl: string, n8nApiKey: string) {
this.n8nBaseUrl = n8nBaseUrl;
this.n8nApiKey = n8nApiKey;
}
async analyse(jobId: string, workflow: N8nWorkflow): Promise<CredentialAnalysis> {
// βββ 1. Identify required credentials from workflow nodes ββββββββββββββ
const required: RequiredCredential[] = [];
const requiredTypes = new Set<string>();
workflow.nodes.forEach((node) => {
// Check registry-based credential (more accurate)
const registryDef = N8N_NODE_REGISTRY[node.type];
const credType = registryDef?.credentialType ?? NODE_CREDENTIAL_MAP[node.type];
if (credType !== undefined && credType !== '') {
if (!requiredTypes.has(credType)) {
required.push({
nodeId: node.id,
nodeName: node.name,
credentialType: credType,
credentialDisplayName: registryDef?.credentialDisplayName,
required: true,
});
requiredTypes.add(credType);
}
}
// Also check node.credentials field (explicitly set by compiler)
if (node.credentials) {
Object.keys(node.credentials).forEach((cType) => {
if (!requiredTypes.has(cType)) {
required.push({
nodeId: node.id,
nodeName: node.name,
credentialType: cType,
required: true,
});
requiredTypes.add(cType);
}
});
}
});
// βββ 2. Fetch available credentials from n8n βββββββββββββββββββββββββββ
let available: AvailableCredential[] = [];
let n8nReachable = false;
try {
const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, {
headers: { 'X-N8N-API-KEY': this.n8nApiKey },
signal: AbortSignal.timeout(8000),
});
if (resp.ok) {
const data = await resp.json() as { data: AvailableCredential[] };
available = data.data ?? [];
n8nReachable = true;
}
} catch {
// n8n not accessible β non-fatal, report as warning
}
const availableTypes = new Set(available.map((c) => c.type));
// βββ 3. Identify missing credentials ββββββββββββββββββββββββββββββββββ
const missingTypeMap = new Map<string, string[]>();
required.forEach((req) => {
// Only report as missing if n8n is reachable and credential is not there
if (n8nReachable && !availableTypes.has(req.credentialType)) {
const existing = missingTypeMap.get(req.credentialType) ?? [];
if (!existing.includes(req.nodeName)) existing.push(req.nodeName);
missingTypeMap.set(req.credentialType, existing);
}
});
const missing: MissingCredential[] = [...missingTypeMap.entries()].map(([credType, nodeNames]) => ({
credentialType: credType,
requiredByNodes: nodeNames,
setupInstructions: CREDENTIAL_SETUP_INSTRUCTIONS[credType] ?? `Set up "${credType}" credential in n8n Settings β Credentials.`,
}));
return {
jobId,
allCredentialsPresent: missing.length === 0,
n8nReachable,
required,
available,
missing,
analysedAt: new Date().toISOString(),
};
}
}
|