diff --git a/packages/nodes-base/credentials/GoogleApi.credentials.ts b/packages/nodes-base/credentials/GoogleApi.credentials.ts index fcd0484895593..2ecf1ec67c73e 100644 --- a/packages/nodes-base/credentials/GoogleApi.credentials.ts +++ b/packages/nodes-base/credentials/GoogleApi.credentials.ts @@ -1,9 +1,22 @@ -import type { ICredentialType, INodeProperties } from 'n8n-workflow'; +import type { + ICredentialDataDecryptedObject, + ICredentialType, + IHttpRequestOptions, + INodeProperties, +} from 'n8n-workflow'; + +import moment from 'moment-timezone'; + +import jwt from 'jsonwebtoken'; + +import type { AxiosRequestConfig } from 'axios'; + +import axios from 'axios'; export class GoogleApi implements ICredentialType { name = 'googleApi'; - displayName = 'Google API'; + displayName = 'Google Service Account API'; documentationUrl = 'google/service-account'; @@ -52,5 +65,101 @@ export class GoogleApi implements ICredentialType { description: 'The email address of the user for which the application is requesting delegated access', }, + { + displayName: 'Set up for use in HTTP Request node', + name: 'httpNode', + type: 'boolean', + default: false, + }, + { + displayName: + "When using the HTTP Request node, you must specify the scopes you want to send. In other nodes, they're added automatically", + name: 'httpWarning', + type: 'notice', + default: '', + displayOptions: { + show: { + httpNode: [true], + }, + }, + }, + { + displayName: 'Scope(s)', + name: 'scopes', + type: 'string', + default: '', + description: + 'You can find the scopes for services here', + displayOptions: { + show: { + httpNode: [true], + }, + }, + }, ]; + + async authenticate( + credentials: ICredentialDataDecryptedObject, + requestOptions: IHttpRequestOptions, + ): Promise { + if (!credentials.httpNode) return requestOptions; + + const privateKey = (credentials.privateKey as string).replace(/\\n/g, '\n').trim(); + const credentialsScopes = (credentials.scopes as string).replace(/\\n/g, '\n').trim(); + credentials.email = (credentials.email as string).trim(); + + const regex = /[,\s\n]+/; + const scopes = credentialsScopes + .split(regex) + .filter((scope) => scope) + .join(' '); + + const now = moment().unix(); + + const signature = jwt.sign( + { + iss: credentials.email, + sub: credentials.delegatedEmail || credentials.email, + scope: scopes, + aud: 'https://oauth2.googleapis.com/token', + iat: now, + exp: now + 3600, + }, + privateKey, + { + algorithm: 'RS256', + header: { + kid: privateKey, + typ: 'JWT', + alg: 'RS256', + }, + }, + ); + + const axiosRequestConfig: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + data: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: signature, + }).toString(), + url: 'https://oauth2.googleapis.com/token', + }; + + const result = await axios(axiosRequestConfig); + + const { access_token } = result.data; + + const requestOptionsWithAuth: IHttpRequestOptions = { + ...requestOptions, + headers: { + ...requestOptions.headers, + Authorization: `Bearer ${access_token}`, + }, + }; + + return requestOptionsWithAuth; + } } diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts index 4e51690e2b417..0618bd957f9fc 100644 --- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts @@ -137,6 +137,18 @@ export class HttpRequestV3 implements INodeType { }, }, }, + { + displayName: + 'Make sure you have specified the scope(s) for the Service Account in the credential', + name: 'googleApiWarning', + type: 'notice', + default: '', + displayOptions: { + show: { + nodeCredentialType: ['googleApi'], + }, + }, + }, { displayName: 'Generic Auth Type', name: 'genericAuthType',