mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(core): Improve the security on OAuth callback endpoints (#11593)
This commit is contained in:
committed by
GitHub
parent
c265d44841
commit
274fcf45d3
@@ -8,11 +8,11 @@ import { jsonStringify } from 'n8n-workflow';
|
||||
import pkceChallenge from 'pkce-challenge';
|
||||
import * as qs from 'querystring';
|
||||
|
||||
import { GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE as GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE } from '@/constants';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { OAuthRequest } from '@/requests';
|
||||
|
||||
import { AbstractOAuthController, type CsrfStateParam } from './abstract-oauth.controller';
|
||||
import { GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE as GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE } from '../../constants';
|
||||
import { AbstractOAuthController, skipAuthOnOAuthCallback } from './abstract-oauth.controller';
|
||||
|
||||
@RestController('/oauth2-credential')
|
||||
export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
@@ -44,7 +44,10 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
);
|
||||
|
||||
// Generate a CSRF prevention token and send it as an OAuth2 state string
|
||||
const [csrfSecret, state] = this.createCsrfState(credential.id);
|
||||
const [csrfSecret, state] = this.createCsrfState(
|
||||
credential.id,
|
||||
skipAuthOnOAuthCallback ? undefined : req.user.id,
|
||||
);
|
||||
|
||||
const oAuthOptions = {
|
||||
...this.convertCredentialToOptions(oauthCredentials),
|
||||
@@ -82,7 +85,7 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
}
|
||||
|
||||
/** Verify and store app code. Generate access tokens and store for respective credential */
|
||||
@Get('/callback', { usesTemplates: true, skipAuth: true })
|
||||
@Get('/callback', { usesTemplates: true, skipAuth: skipAuthOnOAuthCallback })
|
||||
async handleCallback(req: OAuthRequest.OAuth2Credential.Callback, res: Response) {
|
||||
try {
|
||||
const { code, state: encodedState } = req.query;
|
||||
@@ -94,34 +97,8 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
);
|
||||
}
|
||||
|
||||
let state: CsrfStateParam;
|
||||
try {
|
||||
state = this.decodeCsrfState(encodedState);
|
||||
} catch (error) {
|
||||
return this.renderCallbackError(res, (error as Error).message);
|
||||
}
|
||||
|
||||
const credentialId = state.cid;
|
||||
const credential = await this.getCredentialWithoutUser(credentialId);
|
||||
if (!credential) {
|
||||
const errorMessage = 'OAuth2 callback failed because of insufficient permissions';
|
||||
this.logger.error(errorMessage, { credentialId });
|
||||
return this.renderCallbackError(res, errorMessage);
|
||||
}
|
||||
|
||||
const additionalData = await this.getAdditionalData();
|
||||
const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData);
|
||||
const oauthCredentials = this.applyDefaultsAndOverwrites<OAuth2CredentialData>(
|
||||
credential,
|
||||
decryptedDataOriginal,
|
||||
additionalData,
|
||||
);
|
||||
|
||||
if (this.verifyCsrfState(decryptedDataOriginal, state)) {
|
||||
const errorMessage = 'The OAuth2 callback state is invalid!';
|
||||
this.logger.debug(errorMessage, { credentialId });
|
||||
return this.renderCallbackError(res, errorMessage);
|
||||
}
|
||||
const [credential, decryptedDataOriginal, oauthCredentials] =
|
||||
await this.resolveCredential<OAuth2CredentialData>(req);
|
||||
|
||||
let options: Partial<ClientOAuth2Options> = {};
|
||||
|
||||
@@ -156,12 +133,6 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
set(oauthToken.data, 'callbackQueryString', omit(req.query, 'state', 'code'));
|
||||
}
|
||||
|
||||
if (oauthToken === undefined) {
|
||||
const errorMessage = 'Unable to get OAuth2 access tokens!';
|
||||
this.logger.error(errorMessage, { credentialId });
|
||||
return this.renderCallbackError(res, errorMessage);
|
||||
}
|
||||
|
||||
if (decryptedDataOriginal.oauthTokenData) {
|
||||
// Only overwrite supplied data as some providers do for example just return the
|
||||
// refresh_token on the very first request and not on subsequent ones.
|
||||
@@ -175,7 +146,7 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
await this.encryptAndSaveData(credential, decryptedDataOriginal);
|
||||
|
||||
this.logger.debug('OAuth2 callback successful for credential', {
|
||||
credentialId,
|
||||
credentialId: credential.id,
|
||||
});
|
||||
|
||||
return res.render('oauth-callback');
|
||||
|
||||
Reference in New Issue
Block a user