diff --git a/packages/cli/src/__tests__/credentials-helper.test.ts b/packages/cli/src/__tests__/credentials-helper.test.ts index 89ca1a3f0b6..a1a5614e3f2 100644 --- a/packages/cli/src/__tests__/credentials-helper.test.ts +++ b/packages/cli/src/__tests__/credentials-helper.test.ts @@ -813,6 +813,7 @@ describe('CredentialsHelper', () => { describe('getDecrypted - credential resolution integration', () => { const mockCredentialResolutionProvider = { resolveIfNeeded: jest.fn(), + getSystemResolverId: jest.fn(), }; const mockAdditionalData = { diff --git a/packages/cli/src/credentials/__tests__/dynamic-credentials-proxy.test.ts b/packages/cli/src/credentials/__tests__/dynamic-credentials-proxy.test.ts index 8ecb4b83d4f..91fe031294f 100644 --- a/packages/cli/src/credentials/__tests__/dynamic-credentials-proxy.test.ts +++ b/packages/cli/src/credentials/__tests__/dynamic-credentials-proxy.test.ts @@ -33,6 +33,7 @@ describe('DynamicCredentialsProxy', () => { mockResolverProvider = { resolveIfNeeded: jest.fn(), + getSystemResolverId: jest.fn(), }; mockStorageProvider = { @@ -207,4 +208,18 @@ describe('DynamicCredentialsProxy', () => { expect(() => proxy.setStorageProvider(mockStorageProvider)).not.toThrow(); }); }); + + describe('getSystemResolverId', () => { + it('returns null when no resolver provider is set', () => { + expect(proxy.getSystemResolverId()).toBeNull(); + }); + + it('delegates to the resolver provider when set', () => { + mockResolverProvider.getSystemResolverId.mockReturnValue('system-n8n'); + proxy.setResolverProvider(mockResolverProvider); + + expect(proxy.getSystemResolverId()).toBe('system-n8n'); + expect(mockResolverProvider.getSystemResolverId).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/cli/src/credentials/credential-resolution-provider.interface.ts b/packages/cli/src/credentials/credential-resolution-provider.interface.ts index e0e25d4c8a4..3091b53767a 100644 --- a/packages/cli/src/credentials/credential-resolution-provider.interface.ts +++ b/packages/cli/src/credentials/credential-resolution-provider.interface.ts @@ -39,4 +39,11 @@ export interface ICredentialResolutionProvider { executionContext?: IExecutionContext, workflowSettings?: IWorkflowSettings, ): Promise; + + /** + * Returns the seeded system resolver id used to store private credentials + * on the running user's behalf (e.g. OAuth2 callback for `isResolvable` + * credentials). Returns null when the provider is unavailable. + */ + getSystemResolverId(): string | null; } diff --git a/packages/cli/src/credentials/dynamic-credentials-proxy.ts b/packages/cli/src/credentials/dynamic-credentials-proxy.ts index 1c391bd19be..3e2582c1128 100644 --- a/packages/cli/src/credentials/dynamic-credentials-proxy.ts +++ b/packages/cli/src/credentials/dynamic-credentials-proxy.ts @@ -37,6 +37,19 @@ export class DynamicCredentialsProxy this.resolvingProvider = provider; } + /** + * Returns the seeded system resolver id used to store private credentials + * on the user's behalf (e.g. OAuth2 callback for `isResolvable` credentials). + * Returns null when the system resolver has not been seeded or the dynamic + * credentials provider is not registered. + */ + getSystemResolverId(): string | null { + if (!this.resolvingProvider) { + return null; + } + return this.resolvingProvider.getSystemResolverId(); + } + async resolveIfNeeded( credentialsResolveMetadata: CredentialResolveMetadata, staticData: ICredentialDataDecryptedObject, diff --git a/packages/cli/src/modules/dynamic-credentials.ee/services/__tests__/dynamic-credential.service.test.ts b/packages/cli/src/modules/dynamic-credentials.ee/services/__tests__/dynamic-credential.service.test.ts index 233c7299e8c..772428f0063 100644 --- a/packages/cli/src/modules/dynamic-credentials.ee/services/__tests__/dynamic-credential.service.test.ts +++ b/packages/cli/src/modules/dynamic-credentials.ee/services/__tests__/dynamic-credential.service.test.ts @@ -1092,4 +1092,10 @@ describe('DynamicCredentialService', () => { }); }); }); + + describe('getSystemResolverId', () => { + it('returns the seeded system resolver id constant', () => { + expect(service.getSystemResolverId()).toBe('system-n8n'); + }); + }); }); diff --git a/packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts b/packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts index 74e53861078..c4a5afc7327 100644 --- a/packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts +++ b/packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts @@ -21,6 +21,7 @@ import type { CredentialResolveMetadata, ICredentialResolutionProvider, } from '../../../credentials/credential-resolution-provider.interface'; +import { SYSTEM_RESOLVER_ID } from '../constants'; import { DynamicCredentialResolverRepository } from '../database/repositories/credential-resolver.repository'; import { DynamicCredentialsConfig } from '../dynamic-credentials.config'; import { CredentialResolutionError } from '../errors/credential-resolution.error'; @@ -143,6 +144,10 @@ export class DynamicCredentialService implements ICredentialResolutionProvider { } } + getSystemResolverId(): string { + return SYSTEM_RESOLVER_ID; + } + /** * Builds credential context from execution context by decrypting the credentials field */