mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
⚡ Extend googleApi credentials to support user impersonification (#1304)
* ⚡ Extend googleApi credentials to support user impersonification * ⚡ Add service account authentication to Gmail * ⚡ Minor improvements Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
a7dee0aba7
commit
76cee5577b
|
@ -10,7 +10,7 @@ export class GoogleApi implements ICredentialType {
|
|||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Email',
|
||||
displayName: 'Service Account Email',
|
||||
name: 'email',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
|
@ -25,5 +25,25 @@ export class GoogleApi implements ICredentialType {
|
|||
default: '',
|
||||
description: 'Use the multiline editor. Make sure there are exactly 3 lines.<br />-----BEGIN PRIVATE KEY-----<br />KEY IN A SINGLE LINE<br />-----END PRIVATE KEY-----',
|
||||
},
|
||||
{
|
||||
displayName: ' Impersonate a User',
|
||||
name: 'inpersonate',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'delegatedEmail',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
inpersonate: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The email address of the user for which the application is requesting delegated access.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||
const signature = jwt.sign(
|
||||
{
|
||||
'iss': credentials.email as string,
|
||||
'sub': credentials.email as string,
|
||||
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||
'scope': scopes.join(' '),
|
||||
'aud': `https://oauth2.googleapis.com/token`,
|
||||
'iat': now,
|
||||
|
|
|
@ -66,6 +66,8 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
|
||||
} else if (error.response.body.error.message) {
|
||||
errorMessages = error.response.body.error.message;
|
||||
} else if (error.response.body.error_description) {
|
||||
errorMessages = error.response.body.error_description;
|
||||
}
|
||||
|
||||
throw new Error(`Google Drive error response [${error.statusCode}]: ${errorMessages}`);
|
||||
|
@ -107,7 +109,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||
const signature = jwt.sign(
|
||||
{
|
||||
'iss': credentials.email as string,
|
||||
'sub': credentials.email as string,
|
||||
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||
'scope': scopes.join(' '),
|
||||
'aud': `https://oauth2.googleapis.com/token`,
|
||||
'iat': now,
|
||||
|
|
|
@ -23,10 +23,15 @@ import {
|
|||
IEmail,
|
||||
} from './Gmail.node';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
const mailComposer = require('nodemailer/lib/mail-composer');
|
||||
|
||||
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string,
|
||||
endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
|
||||
let options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
|
@ -46,8 +51,22 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
delete options.body;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
|
||||
if (authenticationMethod === 'serviceAccount') {
|
||||
const credentials = this.getCredentials('googleApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
const { access_token } = await getAccessToken.call(this, credentials as IDataObject);
|
||||
|
||||
options.headers!.Authorization = `Bearer ${access_token}`;
|
||||
//@ts-ignore
|
||||
return await this.helpers.request(options);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.response && error.response.body && error.response.body.error) {
|
||||
|
@ -64,6 +83,8 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
|
||||
} else if (error.response.body.error.message) {
|
||||
errorMessages = error.response.body.error.message;
|
||||
} else if (error.response.body.error_description) {
|
||||
errorMessages = error.response.body.error_description;
|
||||
}
|
||||
|
||||
throw new Error(`Gmail error response [${error.statusCode}]: ${errorMessages}`);
|
||||
|
@ -190,3 +211,50 @@ export function extractEmail(s: string) {
|
|||
const data = s.split('<')[1];
|
||||
return data.substring(0, data.length - 1);
|
||||
}
|
||||
|
||||
function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise<IDataObject> {
|
||||
//https://developers.google.com/identity/protocols/oauth2/service-account#httprest
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/books',
|
||||
];
|
||||
|
||||
const now = moment().unix();
|
||||
|
||||
const signature = jwt.sign(
|
||||
{
|
||||
'iss': credentials.email as string,
|
||||
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||
'scope': scopes.join(' '),
|
||||
'aud': `https://oauth2.googleapis.com/token`,
|
||||
'iat': now,
|
||||
'exp': now + 3600,
|
||||
},
|
||||
credentials.privateKey as string,
|
||||
{
|
||||
algorithm: 'RS256',
|
||||
header: {
|
||||
'kid': credentials.privateKey as string,
|
||||
'typ': 'JWT',
|
||||
'alg': 'RS256',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
method: 'POST',
|
||||
form: {
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
assertion: signature,
|
||||
},
|
||||
uri: 'https://oauth2.googleapis.com/token',
|
||||
json: true,
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
return this.helpers.request(options);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,12 +78,46 @@ export class Gmail implements INodeType {
|
|||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'googleApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'serviceAccount',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'gmailOAuth2',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'oAuth2',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Service Account',
|
||||
value: 'serviceAccount',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
],
|
||||
default: 'oAuth2',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
|
|
|
@ -94,7 +94,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||
const signature = jwt.sign(
|
||||
{
|
||||
'iss': credentials.email as string,
|
||||
'sub': credentials.email as string,
|
||||
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||
'scope': scopes.join(' '),
|
||||
'aud': `https://oauth2.googleapis.com/token`,
|
||||
'iat': now,
|
||||
|
|
Loading…
Reference in a new issue