mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
162 lines
4.5 KiB
TypeScript
162 lines
4.5 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// Credential seeding and cleanup for evaluation runs
|
|
//
|
|
// External service credentials (Slack, GitHub, etc.) require real tokens
|
|
// via environment variables. If the env var is not set, the credential
|
|
// is skipped. Generic HTTP credentials use placeholder values and are
|
|
// always seeded.
|
|
//
|
|
// POST /rest/credentials takes raw values -- n8n encrypts them server-side.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
import type { N8nClient } from '../clients/n8n-client';
|
|
import type { EvalLogger } from '../harness/logger';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface CredentialConfig {
|
|
envVar: string;
|
|
type: string;
|
|
name: string;
|
|
dataBuilder: (token: string) => Record<string, unknown>;
|
|
}
|
|
|
|
interface GenericCredentialConfig {
|
|
type: string;
|
|
name: string;
|
|
data: Record<string, unknown>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Credential definitions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const CREDENTIAL_CONFIGS: CredentialConfig[] = [
|
|
{
|
|
envVar: 'EVAL_SLACK_ACCESS_TOKEN',
|
|
type: 'slackApi',
|
|
name: '[eval] Slack',
|
|
dataBuilder: (token) => ({ accessToken: token }),
|
|
},
|
|
{
|
|
envVar: 'EVAL_NOTION_API_KEY',
|
|
type: 'notionApi',
|
|
name: '[eval] Notion',
|
|
dataBuilder: (key) => ({ apiKey: key }),
|
|
},
|
|
{
|
|
envVar: 'EVAL_GITHUB_ACCESS_TOKEN',
|
|
type: 'githubApi',
|
|
name: '[eval] GitHub',
|
|
dataBuilder: (token) => ({ accessToken: token }),
|
|
},
|
|
{
|
|
envVar: 'EVAL_GMAIL_ACCESS_TOKEN',
|
|
type: 'gmailOAuth2Api',
|
|
name: '[eval] Gmail',
|
|
dataBuilder: (token) => ({ oauthTokenData: { access_token: token } }),
|
|
},
|
|
{
|
|
envVar: 'EVAL_TEAMS_ACCESS_TOKEN',
|
|
type: 'microsoftTeamsOAuth2Api',
|
|
name: '[eval] Teams',
|
|
dataBuilder: (token) => ({
|
|
scope: 'openid',
|
|
oauthTokenData: { access_token: token },
|
|
}),
|
|
},
|
|
];
|
|
|
|
const GENERIC_CREDENTIALS: GenericCredentialConfig[] = [
|
|
{
|
|
type: 'httpHeaderAuth',
|
|
name: '[eval] HTTP Header',
|
|
data: { name: 'Authorization', value: 'Bearer eval-placeholder' },
|
|
},
|
|
{
|
|
type: 'httpBasicAuth',
|
|
name: '[eval] HTTP Basic',
|
|
data: { user: 'eval-user', password: 'eval-pass' },
|
|
},
|
|
];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface SeedResult {
|
|
credentialIds: string[];
|
|
seededTypes: string[];
|
|
}
|
|
|
|
/**
|
|
* Seed credentials into the n8n instance for evaluation runs.
|
|
*
|
|
* For env-var-based configs, the credential is skipped if the env var is not
|
|
* set. Generic credentials (HTTP Header, HTTP Basic) are always seeded.
|
|
*
|
|
* When `requiredTypes` is provided, only credentials matching those types are
|
|
* seeded. When omitted, all available credentials are seeded.
|
|
*/
|
|
export async function seedCredentials(
|
|
client: N8nClient,
|
|
requiredTypes?: string[],
|
|
logger?: EvalLogger,
|
|
): Promise<SeedResult> {
|
|
const credentialIds: string[] = [];
|
|
const seededTypes: string[] = [];
|
|
const typeFilter = requiredTypes ? new Set(requiredTypes) : undefined;
|
|
|
|
// Seed env-var-based credentials
|
|
for (const config of CREDENTIAL_CONFIGS) {
|
|
if (typeFilter && !typeFilter.has(config.type)) continue;
|
|
|
|
const token = process.env[config.envVar];
|
|
if (!token) {
|
|
logger?.verbose(` Skipping ${config.name}: ${config.envVar} not set`);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const data = config.dataBuilder(token);
|
|
const { id } = await client.createCredential(config.name, config.type, data);
|
|
credentialIds.push(id);
|
|
seededTypes.push(config.type);
|
|
} catch {
|
|
// Non-fatal -- credential type may not exist on this n8n version
|
|
}
|
|
}
|
|
|
|
// Always seed generic credentials
|
|
for (const generic of GENERIC_CREDENTIALS) {
|
|
if (typeFilter && !typeFilter.has(generic.type)) continue;
|
|
|
|
try {
|
|
const { id } = await client.createCredential(generic.name, generic.type, generic.data);
|
|
credentialIds.push(id);
|
|
seededTypes.push(generic.type);
|
|
} catch {
|
|
// Non-fatal
|
|
}
|
|
}
|
|
|
|
return { credentialIds, seededTypes };
|
|
}
|
|
|
|
/**
|
|
* Best-effort cleanup of seeded credentials after an evaluation run.
|
|
*/
|
|
export async function cleanupCredentials(
|
|
client: N8nClient,
|
|
credentialIds: string[],
|
|
): Promise<void> {
|
|
for (const id of credentialIds) {
|
|
try {
|
|
await client.deleteCredential(id);
|
|
} catch {
|
|
// Best-effort cleanup
|
|
}
|
|
}
|
|
}
|