Skip to content

Conversation

@guillaumejacquart
Copy link
Contributor

Summary

This PR implements credential resolving mechanism, using the resolver id setup in the credential or fallback on the workflow settings.

  • Service-based proxy pattern: CredentialsHelper delegates to ICredentialResolutionProvider interface
  • DynamicCredentialService: EE module implementing resolution logic via pluggable resolvers

Related Linear tickets, Github issues, and Community forum posts

https://linear.app/n8n/issue/PAY-4218/implement-pass-through-credential-resolution-service

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

@n8n-assistant n8n-assistant bot added core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team labels Dec 5, 2025
@guillaumejacquart guillaumejacquart force-pushed the pay-4218-implement-pass-through-credential-resolution-service branch from f63952b to ce26bee Compare December 5, 2025 10:30
@codecov
Copy link

codecov bot commented Dec 5, 2025

@blacksmith-sh

This comment has been minimized.

@currents-bot
Copy link

currents-bot bot commented Dec 5, 2025

E2E Tests: n8n tests passed after 6m 57.5s

🟢 57 · 🔴 0 · ⚪️ 12

View Run Details

Run Details

  • Project: n8n

  • Groups: 1

  • Framework: Playwright

  • Run Status: Passed

  • Commit: 2f692e9

  • Spec files: 114

  • Overall tests: 556

  • Duration: 6m 57.5s

  • Parallelization: 1


This message was posted automatically by currents.dev | Integration Settings

@guillaumejacquart guillaumejacquart force-pushed the pay-4218-implement-pass-through-credential-resolution-service branch from ce26bee to ac3239e Compare December 5, 2025 13:02
@guillaumejacquart guillaumejacquart force-pushed the pay-4218-implement-pass-through-credential-resolution-service branch from 305d84f to dd76799 Compare December 5, 2025 14:03
@guillaumejacquart guillaumejacquart force-pushed the pay-4218-implement-pass-through-credential-resolution-service branch from dd76799 to 2f692e9 Compare December 5, 2025 14:37
@blacksmith-sh

This comment has been minimized.

@guillaumejacquart guillaumejacquart marked this pull request as ready for review December 5, 2025 16:06
@guillaumejacquart guillaumejacquart requested a review from a team as a code owner December 5, 2025 16:06
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 26 files

Prompt for AI agents (all 4 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts">

<violation number="1" location="packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts:80">
P1: Decrypt resolver configuration before parsing it; otherwise jsonParse runs on ciphertext and throws before dynamic credentials can resolve.</violation>

<violation number="2" location="packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts:118">
P2: Rule violated: **Prefer Typeguards over Type casting**

The fallback logger in `handleResolutionError` also relies on `error as Error` for type narrowing, conflicting with the guideline to prefer type guards. Use an `instanceof Error` check (or similar) before accessing `.message`.</violation>
</file>

<file name="packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts">

<violation number="1" location="packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts:235">
P2: This assertion only verifies `executionContext` after `_getCredentials` finishes, so the test would still pass if the helper set the field after calling `getDecrypted`. Capture the argument passed into `getDecrypted` and assert its `executionContext` is already populated so the test truly guards the behavior.</violation>
</file>

<file name="packages/cli/src/modules/dynamic-credentials.ee/database/repositories/__tests__/credential-resolver.repository.integration.test.ts">

<violation number="1" location="packages/cli/src/modules/dynamic-credentials.ee/database/repositories/__tests__/credential-resolver.repository.integration.test.ts:80">
P2: The test assumes a deterministic ordering of `credentialsRepository.find` results, so it will flap whenever the database returns the two credentials in the opposite order. Make the assertion order-independent (e.g., search by property or add an explicit ORDER BY).</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

}

// Parse resolver configuration
const resolverConfig = jsonParse<Record<string, unknown>>(resolverEntity.config);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Decrypt resolver configuration before parsing it; otherwise jsonParse runs on ciphertext and throws before dynamic credentials can resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts, line 80:

<comment>Decrypt resolver configuration before parsing it; otherwise jsonParse runs on ciphertext and throws before dynamic credentials can resolve.</comment>

<file context>
@@ -0,0 +1,203 @@
+		}
+
+		// Parse resolver configuration
+		const resolverConfig = jsonParse&lt;Record&lt;string, unknown&gt;&gt;(resolverEntity.config);
+
+		try {
</file context>
Fix with Cubic


await contextWithCredentials['_getCredentials']('testCredential');

expect(mockAdditionalData.executionContext).toEqual(runtimeData);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This assertion only verifies executionContext after _getCredentials finishes, so the test would still pass if the helper set the field after calling getDecrypted. Capture the argument passed into getDecrypted and assert its executionContext is already populated so the test truly guards the behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts, line 235:

<comment>This assertion only verifies `executionContext` after `_getCredentials` finishes, so the test would still pass if the helper set the field after calling `getDecrypted`. Capture the argument passed into `getDecrypted` and assert its `executionContext` is already populated so the test truly guards the behavior.
</comment>

<file context>
@@ -195,6 +195,56 @@ describe(&#39;NodeExecutionContext&#39;, () =&gt; {
+
+			await contextWithCredentials[&#39;_getCredentials&#39;](&#39;testCredential&#39;);
+
+			expect(mockAdditionalData.executionContext).toEqual(runtimeData);
+			expect(mockCredentialsHelper.getDecrypted).toHaveBeenCalledWith(
+				mockAdditionalData,
</file context>
Suggested change
expect(mockAdditionalData.executionContext).toEqual(runtimeData);
const additionalDataArg = mockCredentialsHelper.getDecrypted.mock.calls[0][0];
expect(additionalDataArg.executionContext).toEqual(runtimeData);
expect(mockAdditionalData.executionContext).toEqual(runtimeData);
Fix with Cubic


expect(linked).toHaveLength(2);
expect(linked[0].resolverId).toBe(resolver.id);
expect(linked[1].resolvableAllowFallback).toBe(true);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The test assumes a deterministic ordering of credentialsRepository.find results, so it will flap whenever the database returns the two credentials in the opposite order. Make the assertion order-independent (e.g., search by property or add an explicit ORDER BY).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/modules/dynamic-credentials.ee/database/repositories/__tests__/credential-resolver.repository.integration.test.ts, line 80:

<comment>The test assumes a deterministic ordering of `credentialsRepository.find` results, so it will flap whenever the database returns the two credentials in the opposite order. Make the assertion order-independent (e.g., search by property or add an explicit ORDER BY).</comment>

<file context>
@@ -0,0 +1,112 @@
+
+			expect(linked).toHaveLength(2);
+			expect(linked[0].resolverId).toBe(resolver.id);
+			expect(linked[1].resolvableAllowFallback).toBe(true);
+		});
+
</file context>
Fix with Cubic

return toCredentialContext(decrypted);
} catch (error) {
this.logger.error('Failed to decrypt credential context from execution context', {
error: (error as Error).message,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Prefer Typeguards over Type casting

The fallback logger in handleResolutionError also relies on error as Error for type narrowing, conflicting with the guideline to prefer type guards. Use an instanceof Error check (or similar) before accessing .message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts, line 118:

<comment>The fallback logger in `handleResolutionError` also relies on `error as Error` for type narrowing, conflicting with the guideline to prefer type guards. Use an `instanceof Error` check (or similar) before accessing `.message`.</comment>

<file context>
@@ -0,0 +1,203 @@
+			return toCredentialContext(decrypted);
+		} catch (error) {
+			this.logger.error(&#39;Failed to decrypt credential context from execution context&#39;, {
+				error: (error as Error).message,
+			});
+			return undefined;
</file context>
Suggested change
error: (error as Error).message,
error: error instanceof Error ? error.message : String(error),
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants