feat(core): Add "Client Credentials" grant type to OAuth2 (#3489)

*  Add OAuth2 client credentials grant type

*  Improvements

* 🐛 Fix linting issue

* 🐛 Fix typo

* 🐛 Fix small issue with type

* 🐛 When token expire get a new one instead of refreshing it

*  Fix issue that it did not display it correctly for OAuth1

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ricardo Espinoza
2022-06-14 01:27:19 -04:00
committed by GitHub
parent 51663c1fcb
commit e29c5975e1
55 changed files with 417 additions and 13 deletions

View File

@@ -57,6 +57,8 @@ import {
WorkflowExecuteMode,
LoggerProxy as Logger,
IExecuteData,
OAuth2GrantType,
IOAuth2Credentials,
} from 'n8n-workflow';
import { Agent } from 'https';
@@ -881,19 +883,46 @@ export async function requestOAuth2(
oAuth2Options?: IOAuth2Options,
isN8nRequest = false,
) {
const credentials = await this.getCredentials(credentialsType);
const credentials = (await this.getCredentials(credentialsType)) as unknown as IOAuth2Credentials;
if (credentials.oauthTokenData === undefined) {
// Only the OAuth2 with authorization code grant needs connection
if (
credentials.grantType === OAuth2GrantType.authorizationCode &&
credentials.oauthTokenData === undefined
) {
throw new Error('OAuth credentials not connected!');
}
const oAuthClient = new clientOAuth2({
clientId: credentials.clientId as string,
clientSecret: credentials.clientSecret as string,
accessTokenUri: credentials.accessTokenUrl as string,
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
accessTokenUri: credentials.accessTokenUrl,
scopes: credentials.scope.split(' '),
});
const oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data;
let oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data;
// if it's the first time using the credentials, get the access token and save it into the DB.
if (credentials.grantType === OAuth2GrantType.clientCredentials && oauthTokenData === undefined) {
const { data } = await oAuthClient.credentials.getToken();
// Find the credentials
if (!node.credentials || !node.credentials[credentialsType]) {
throw new Error(
`The node "${node.name}" does not have credentials of type "${credentialsType}"!`,
);
}
const nodeCredentials = node.credentials[credentialsType];
// Save the refreshed token
await additionalData.credentialsHelper.updateCredentials(
nodeCredentials,
credentialsType,
credentials as unknown as ICredentialDataDecryptedObject,
);
oauthTokenData = data;
}
const token = oAuthClient.createToken(
get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData.accessToken,
@@ -926,8 +955,8 @@ export async function requestOAuth2(
if (oAuth2Options?.includeCredentialsOnRefreshOnBody) {
const body: IDataObject = {
client_id: credentials.clientId as string,
client_secret: credentials.clientSecret as string,
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
};
tokenRefreshOptions.body = body;
// Override authorization property so the credentails are not included in it
@@ -940,7 +969,15 @@ export async function requestOAuth2(
`OAuth2 token for "${credentialsType}" used by node "${node.name}" expired. Should revalidate.`,
);
const newToken = await token.refresh(tokenRefreshOptions);
let newToken;
// if it's OAuth2 with client credentials grant type, get a new token
// instead of refreshing it.
if (OAuth2GrantType.clientCredentials === credentials.grantType) {
newToken = await token.client.credentials.getToken();
} else {
newToken = await token.refresh(tokenRefreshOptions);
}
Logger.debug(
`OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`,
@@ -960,7 +997,7 @@ export async function requestOAuth2(
await additionalData.credentialsHelper.updateCredentials(
nodeCredentials,
credentialsType,
credentials,
credentials as unknown as ICredentialDataDecryptedObject,
);
Logger.debug(