n8n
Version:
n8n Workflow Automation Tool
205 lines • 9.79 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CredentialResolverWorkflowService = void 0;
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const dynamic_credentials_proxy_1 = require("../../../credentials/dynamic-credentials-proxy");
const bad_request_error_1 = require("../../../errors/response-errors/bad-request.error");
const not_found_error_1 = require("../../../errors/response-errors/not-found.error");
const workflow_finder_service_1 = require("../../../workflows/workflow-finder.service");
const credential_resolver_registry_service_1 = require("./credential-resolver-registry.service");
const credential_resolver_repository_1 = require("../database/repositories/credential-resolver.repository");
const MAX_TRAVERSED_WORKFLOWS = 100;
function isCredentialStatus(obj) {
if (typeof obj !== 'object' || obj === null) {
return false;
}
return ('credentialId' in obj &&
typeof obj.credentialId === 'string' &&
'credentialType' in obj &&
typeof obj.credentialType === 'string' &&
'status' in obj &&
(obj.status === 'missing' || obj.status === 'configured' || obj.status === 'resolver_missing'));
}
let CredentialResolverWorkflowService = class CredentialResolverWorkflowService {
constructor(workflowRepository, credentialRepository, resolverRegistry, resolverRepository, cipher, dynamicCredentialsProxy, workflowFinderService) {
this.workflowRepository = workflowRepository;
this.credentialRepository = credentialRepository;
this.resolverRegistry = resolverRegistry;
this.resolverRepository = resolverRepository;
this.cipher = cipher;
this.dynamicCredentialsProxy = dynamicCredentialsProxy;
this.workflowFinderService = workflowFinderService;
}
async getResolver(resolverId) {
const resolver = await this.resolverRepository.findOneBy({ id: resolverId });
if (!resolver) {
throw new Error('Credential resolver not found');
}
const resolverInstance = this.resolverRegistry.getResolverByTypename(resolver.type);
if (!resolverInstance) {
throw new Error('Credential resolver implementation not found');
}
try {
const decryptedConfig = await this.cipher.decryptV2(resolver.config);
const resolverConfig = (0, n8n_workflow_1.jsonParse)(decryptedConfig);
return {
resolverInstance,
resolverConfig,
};
}
catch (error) {
throw new Error('Failed to decrypt or parse resolver configuration');
}
}
async getWorkflowStatus(workflowId, credentialContext, user) {
const visited = new Set();
const credentialFallbackResolvers = new Map();
await this.collectResolvableCredentials(workflowId, credentialFallbackResolvers, visited, user, true);
if (credentialFallbackResolvers.size === 0) {
return [];
}
const credentials = await this.credentialRepository.find({
where: {
id: (0, db_1.In)([...credentialFallbackResolvers.keys()]),
isResolvable: true,
},
});
const resolverCache = new Map();
const credentialChecks = credentials.flatMap((credential) => {
const fallbacks = credential.resolverId
? [null]
: [...(credentialFallbackResolvers.get(credential.id) ?? [null])];
return fallbacks.map((fallbackResolverId) => ({ credential, fallbackResolverId }));
});
const credentialStatusPromises = credentialChecks.map(async ({ credential, fallbackResolverId }) => await this.checkCredentialStatus(credential, {
fallbackResolverId,
credentialContext,
resolverCache,
}));
return (await Promise.all(credentialStatusPromises)).filter(isCredentialStatus);
}
async collectResolvableCredentials(workflowId, acc, visited, user, isRoot) {
if (visited.has(workflowId)) {
return;
}
if (visited.size >= MAX_TRAVERSED_WORKFLOWS) {
throw new bad_request_error_1.BadRequestError(`Workflow references too many sub-workflows to check (limit ${MAX_TRAVERSED_WORKFLOWS})`);
}
visited.add(workflowId);
const workflow = isRoot && user
? await this.workflowFinderService.findWorkflowForUser(workflowId, user, ['workflow:read'])
: await this.workflowRepository.get({ id: workflowId });
if (!workflow) {
if (isRoot) {
throw new not_found_error_1.NotFoundError('Workflow not found');
}
return;
}
const resolverId = this.dynamicCredentialsProxy.getEffectiveResolverId(workflow.settings);
for (const node of workflow.nodes ?? []) {
for (const credentialName in node.credentials ?? {}) {
const credentialId = node.credentials?.[credentialName]?.id;
if (!credentialId) {
continue;
}
const fallbacks = acc.get(credentialId) ?? new Set();
fallbacks.add(resolverId);
acc.set(credentialId, fallbacks);
}
}
for (const subWorkflowId of this.extractSubWorkflowIds(workflow.nodes ?? [])) {
await this.collectResolvableCredentials(subWorkflowId, acc, visited, user, false);
}
}
extractSubWorkflowIds(nodes) {
const ids = new Set();
for (const node of nodes) {
if (node.disabled || !(0, n8n_workflow_1.isNodeWithWorkflowSelector)(node)) {
continue;
}
const source = node.parameters?.source;
if (source === 'parameter' || source === 'localFile' || source === 'url') {
continue;
}
const subWorkflowId = this.extractWorkflowId(node.parameters?.workflowId);
if (subWorkflowId && !subWorkflowId.includes('{')) {
ids.add(subWorkflowId);
}
}
return [...ids];
}
extractWorkflowId(workflowIdParam) {
if (typeof workflowIdParam === 'string') {
return workflowIdParam;
}
if (typeof workflowIdParam === 'object' &&
workflowIdParam !== null &&
'value' in workflowIdParam &&
typeof workflowIdParam.value === 'string') {
return workflowIdParam.value;
}
return undefined;
}
async getCachedResolver(resolverId, cache) {
const cached = cache.get(resolverId);
if (cached) {
return cached;
}
const resolved = await this.getResolver(resolverId);
cache.set(resolverId, resolved);
return resolved;
}
async checkCredentialStatus(credential, options) {
const credentialResolverId = credential.resolverId ?? options.fallbackResolverId;
if (!credentialResolverId) {
return {
credentialId: credential.id,
credentialName: credential.name,
status: 'resolver_missing',
credentialType: credential.type,
};
}
const { resolverInstance, resolverConfig } = await this.getCachedResolver(credentialResolverId, options.resolverCache);
try {
await resolverInstance.getSecret(credential.id, options.credentialContext, {
configuration: resolverConfig,
resolverName: resolverInstance.metadata.name,
resolverId: credentialResolverId,
});
return {
credentialId: credential.id,
resolverId: credentialResolverId,
credentialName: credential.name,
status: 'configured',
credentialType: credential.type,
};
}
catch (error) {
return {
credentialId: credential.id,
resolverId: credentialResolverId,
credentialName: credential.name,
status: 'missing',
credentialType: credential.type,
};
}
}
};
exports.CredentialResolverWorkflowService = CredentialResolverWorkflowService;
exports.CredentialResolverWorkflowService = CredentialResolverWorkflowService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [db_1.WorkflowRepository, db_1.CredentialsRepository, credential_resolver_registry_service_1.DynamicCredentialResolverRegistry, credential_resolver_repository_1.DynamicCredentialResolverRepository, n8n_core_1.Cipher, dynamic_credentials_proxy_1.DynamicCredentialsProxy, workflow_finder_service_1.WorkflowFinderService])
], CredentialResolverWorkflowService);
//# sourceMappingURL=credential-resolver-workflow.service.js.map