Skip to content

Commit d228a56

Browse files
committed
allow fixing a credential with a blank value (string) in the oauthTokenData field by authorizing again
1 parent e182230 commit d228a56

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

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

+81-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Cipher } from 'n8n-core';
66
import { Logger } from 'n8n-core';
77
import nock from 'nock';
88

9-
import { Time } from '@/constants';
9+
import { CREDENTIAL_BLANKING_VALUE, Time } from '@/constants';
1010
import { OAuth2CredentialController } from '@/controllers/oauth/oauth2-credential.controller';
1111
import { CredentialsHelper } from '@/credentials-helper';
1212
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
@@ -257,5 +257,85 @@ describe('OAuth2CredentialController', () => {
257257
);
258258
expect(res.render).toHaveBeenCalledWith('oauth-callback');
259259
});
260+
261+
it('merges oauthTokenData if it already exists', async () => {
262+
credentialsRepository.findOneBy.mockResolvedValueOnce(credential);
263+
credentialsHelper.getDecrypted.mockResolvedValueOnce({
264+
csrfSecret,
265+
oauthTokenData: { token: true },
266+
});
267+
jest.spyOn(Csrf.prototype, 'verify').mockReturnValueOnce(true);
268+
nock('https://example.domain')
269+
.post(
270+
'/token',
271+
'code=code&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5678%2Frest%2Foauth2-credential%2Fcallback',
272+
)
273+
.reply(200, { access_token: 'access-token', refresh_token: 'refresh-token' });
274+
cipher.encrypt.mockReturnValue('encrypted');
275+
276+
await controller.handleCallback(req, res);
277+
278+
expect(externalHooks.run).toHaveBeenCalledWith('oauth2.callback', [
279+
expect.objectContaining({
280+
clientId: 'test-client-id',
281+
redirectUri: 'http://localhost:5678/rest/oauth2-credential/callback',
282+
}),
283+
]);
284+
expect(cipher.encrypt).toHaveBeenCalledWith({
285+
oauthTokenData: {
286+
token: true,
287+
access_token: 'access-token',
288+
refresh_token: 'refresh-token',
289+
},
290+
});
291+
expect(credentialsRepository.update).toHaveBeenCalledWith(
292+
'1',
293+
expect.objectContaining({
294+
data: 'encrypted',
295+
id: '1',
296+
name: 'Test Credential',
297+
type: 'oAuth2Api',
298+
}),
299+
);
300+
expect(res.render).toHaveBeenCalledWith('oauth-callback');
301+
});
302+
303+
it('overwrites oauthTokenData if it is a string', async () => {
304+
credentialsRepository.findOneBy.mockResolvedValueOnce(credential);
305+
credentialsHelper.getDecrypted.mockResolvedValueOnce({
306+
csrfSecret,
307+
oauthTokenData: CREDENTIAL_BLANKING_VALUE,
308+
});
309+
jest.spyOn(Csrf.prototype, 'verify').mockReturnValueOnce(true);
310+
nock('https://example.domain')
311+
.post(
312+
'/token',
313+
'code=code&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5678%2Frest%2Foauth2-credential%2Fcallback',
314+
)
315+
.reply(200, { access_token: 'access-token', refresh_token: 'refresh-token' });
316+
cipher.encrypt.mockReturnValue('encrypted');
317+
318+
await controller.handleCallback(req, res);
319+
320+
expect(externalHooks.run).toHaveBeenCalledWith('oauth2.callback', [
321+
expect.objectContaining({
322+
clientId: 'test-client-id',
323+
redirectUri: 'http://localhost:5678/rest/oauth2-credential/callback',
324+
}),
325+
]);
326+
expect(cipher.encrypt).toHaveBeenCalledWith({
327+
oauthTokenData: { access_token: 'access-token', refresh_token: 'refresh-token' },
328+
});
329+
expect(credentialsRepository.update).toHaveBeenCalledWith(
330+
'1',
331+
expect.objectContaining({
332+
data: 'encrypted',
333+
id: '1',
334+
name: 'Test Credential',
335+
type: 'oAuth2Api',
336+
}),
337+
);
338+
expect(res.render).toHaveBeenCalledWith('oauth-callback');
339+
});
260340
});
261341
});

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export class OAuth2CredentialController extends AbstractOAuthController {
133133
set(oauthToken.data, 'callbackQueryString', omit(req.query, 'state', 'code'));
134134
}
135135

136-
if (decryptedDataOriginal.oauthTokenData) {
136+
if (typeof decryptedDataOriginal.oauthTokenData === 'object') {
137137
// Only overwrite supplied data as some providers do for example just return the
138138
// refresh_token on the very first request and not on subsequent ones.
139139
Object.assign(decryptedDataOriginal.oauthTokenData, oauthToken.data);

0 commit comments

Comments
 (0)