Skip to content

Commit cae98e7

Browse files
authored
fix(core): Allow secrets manager secrets to be used in credentials (n8n-io#13110)
1 parent 4577ce0 commit cae98e7

File tree

5 files changed

+75
-5
lines changed

5 files changed

+75
-5
lines changed

packages/cli/src/controllers/oauth/__tests__/oauth1-credential.controller.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Response } from 'express';
44
import { mock } from 'jest-mock-extended';
55
import { Cipher } from 'n8n-core';
66
import { Logger } from 'n8n-core';
7+
import type { IWorkflowExecuteAdditionalData } from 'n8n-workflow';
78
import nock from 'nock';
89

910
import { Time } from '@/constants';
@@ -19,15 +20,21 @@ import { NotFoundError } from '@/errors/response-errors/not-found.error';
1920
import { ExternalHooks } from '@/external-hooks';
2021
import type { OAuthRequest } from '@/requests';
2122
import { SecretsHelper } from '@/secrets-helpers.ee';
23+
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
2224
import { mockInstance } from '@test/mocking';
2325

26+
jest.mock('@/workflow-execute-additional-data');
27+
2428
describe('OAuth1CredentialController', () => {
2529
mockInstance(Logger);
2630
mockInstance(ExternalHooks);
2731
mockInstance(SecretsHelper);
2832
mockInstance(VariablesService, {
2933
getAllCached: async () => [],
3034
});
35+
const additionalData = mock<IWorkflowExecuteAdditionalData>();
36+
(WorkflowExecuteAdditionalData.getBase as jest.Mock).mockReturnValue(additionalData);
37+
3138
const cipher = mockInstance(Cipher);
3239
const credentialsHelper = mockInstance(CredentialsHelper);
3340
const credentialsRepository = mockInstance(CredentialsRepository);
@@ -106,6 +113,14 @@ describe('OAuth1CredentialController', () => {
106113
}),
107114
);
108115
expect(cipher.encrypt).toHaveBeenCalledWith({ csrfSecret });
116+
expect(credentialsHelper.getDecrypted).toHaveBeenCalledWith(
117+
additionalData,
118+
credential,
119+
credential.type,
120+
'internal',
121+
undefined,
122+
false,
123+
);
109124
});
110125
});
111126

@@ -235,6 +250,14 @@ describe('OAuth1CredentialController', () => {
235250
}),
236251
);
237252
expect(res.render).toHaveBeenCalledWith('oauth-callback');
253+
expect(credentialsHelper.getDecrypted).toHaveBeenCalledWith(
254+
additionalData,
255+
credential,
256+
credential.type,
257+
'internal',
258+
undefined,
259+
true,
260+
);
238261
});
239262
});
240263
});

packages/cli/src/controllers/oauth/__tests__/oauth2-credential.controller.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type Response } from 'express';
44
import { mock } from 'jest-mock-extended';
55
import { Cipher } from 'n8n-core';
66
import { Logger } from 'n8n-core';
7+
import type { IWorkflowExecuteAdditionalData } from 'n8n-workflow';
78
import nock from 'nock';
89

910
import { CREDENTIAL_BLANKING_VALUE, Time } from '@/constants';
@@ -19,14 +20,20 @@ import { NotFoundError } from '@/errors/response-errors/not-found.error';
1920
import { ExternalHooks } from '@/external-hooks';
2021
import type { OAuthRequest } from '@/requests';
2122
import { SecretsHelper } from '@/secrets-helpers.ee';
23+
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
2224
import { mockInstance } from '@test/mocking';
2325

26+
jest.mock('@/workflow-execute-additional-data');
27+
2428
describe('OAuth2CredentialController', () => {
2529
mockInstance(Logger);
2630
mockInstance(SecretsHelper);
2731
mockInstance(VariablesService, {
2832
getAllCached: async () => [],
2933
});
34+
const additionalData = mock<IWorkflowExecuteAdditionalData>();
35+
(WorkflowExecuteAdditionalData.getBase as jest.Mock).mockReturnValue(additionalData);
36+
3037
const cipher = mockInstance(Cipher);
3138
const externalHooks = mockInstance(ExternalHooks);
3239
const credentialsHelper = mockInstance(CredentialsHelper);
@@ -108,6 +115,14 @@ describe('OAuth2CredentialController', () => {
108115
type: 'oAuth2Api',
109116
}),
110117
);
118+
expect(credentialsHelper.getDecrypted).toHaveBeenCalledWith(
119+
additionalData,
120+
credential,
121+
credential.type,
122+
'internal',
123+
undefined,
124+
false,
125+
);
111126
});
112127
});
113128

@@ -256,6 +271,14 @@ describe('OAuth2CredentialController', () => {
256271
}),
257272
);
258273
expect(res.render).toHaveBeenCalledWith('oauth-callback');
274+
expect(credentialsHelper.getDecrypted).toHaveBeenCalledWith(
275+
additionalData,
276+
credential,
277+
credential.type,
278+
'internal',
279+
undefined,
280+
true,
281+
);
259282
});
260283

261284
it('merges oauthTokenData if it already exists', async () => {

packages/cli/src/controllers/oauth/abstract-oauth.controller.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,38 @@ export abstract class AbstractOAuthController {
8686
return await WorkflowExecuteAdditionalData.getBase();
8787
}
8888

89-
protected async getDecryptedData(
89+
/**
90+
* Allow decrypted data to evaluate expressions that include $secrets and apply overwrites
91+
*/
92+
protected async getDecryptedDataForAuthUri(
9093
credential: ICredentialsDb,
9194
additionalData: IWorkflowExecuteAdditionalData,
95+
) {
96+
return await this.getDecryptedData(credential, additionalData, false);
97+
}
98+
99+
/**
100+
* Do not apply overwrites here because that removes the CSRF state, and breaks the oauth flow
101+
*/
102+
protected async getDecryptedDataForCallback(
103+
credential: ICredentialsDb,
104+
additionalData: IWorkflowExecuteAdditionalData,
105+
) {
106+
return await this.getDecryptedData(credential, additionalData, true);
107+
}
108+
109+
private async getDecryptedData(
110+
credential: ICredentialsDb,
111+
additionalData: IWorkflowExecuteAdditionalData,
112+
raw: boolean,
92113
) {
93114
return await this.credentialsHelper.getDecrypted(
94115
additionalData,
95116
credential,
96117
credential.type,
97118
'internal',
98119
undefined,
99-
true,
120+
raw,
100121
);
101122
}
102123

@@ -183,7 +204,10 @@ export abstract class AbstractOAuthController {
183204
}
184205

185206
const additionalData = await this.getAdditionalData();
186-
const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData);
207+
const decryptedDataOriginal = await this.getDecryptedDataForCallback(
208+
credential,
209+
additionalData,
210+
);
187211
const oauthCredentials = this.applyDefaultsAndOverwrites<T>(
188212
credential,
189213
decryptedDataOriginal,

packages/cli/src/controllers/oauth/oauth1-credential.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class OAuth1CredentialController extends AbstractOAuthController {
3535
async getAuthUri(req: OAuthRequest.OAuth1Credential.Auth): Promise<string> {
3636
const credential = await this.getCredential(req);
3737
const additionalData = await this.getAdditionalData();
38-
const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData);
38+
const decryptedDataOriginal = await this.getDecryptedDataForAuthUri(credential, additionalData);
3939
const oauthCredentials = this.applyDefaultsAndOverwrites<OAuth1CredentialData>(
4040
credential,
4141
decryptedDataOriginal,

packages/cli/src/controllers/oauth/oauth2-credential.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class OAuth2CredentialController extends AbstractOAuthController {
2323
async getAuthUri(req: OAuthRequest.OAuth2Credential.Auth): Promise<string> {
2424
const credential = await this.getCredential(req);
2525
const additionalData = await this.getAdditionalData();
26-
const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData);
26+
const decryptedDataOriginal = await this.getDecryptedDataForAuthUri(credential, additionalData);
2727

2828
// At some point in the past we saved hidden scopes to credentials (but shouldn't)
2929
// Delete scope before applying defaults to make sure new scopes are present on reconnect

0 commit comments

Comments
 (0)