diff --git a/packages/nodes-base/credentials/GoogleFirebaseApi.credentials.ts b/packages/nodes-base/credentials/GoogleFirebaseApi.credentials.ts
new file mode 100644
index 0000000000..1191491e8a
--- /dev/null
+++ b/packages/nodes-base/credentials/GoogleFirebaseApi.credentials.ts
@@ -0,0 +1,27 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class GoogleFirebaseApi implements ICredentialType {
+ name = 'googleFirebaseApi';
+ displayName = 'Google Firebase API';
+ documentationUrl = 'google';
+ properties = [
+ {
+ displayName: 'Service Account Email',
+ name: 'email',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ description: 'The Google Service account similar to user-808@project.iam.gserviceaccount.com.
See the tutorial on how to create one.',
+ },
+ {
+ displayName: 'Private Key',
+ name: 'privateKey',
+ lines: 5,
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ description: 'Use the multiline editor. Make sure there are exactly 3 lines.
-----BEGIN PRIVATE KEY-----
KEY IN A SINGLE LINE
-----END PRIVATE KEY-----',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/CloudFirestore.node.ts b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/CloudFirestore.node.ts
index ef1d63b283..3a3a49ebaf 100644
--- a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/CloudFirestore.node.ts
+++ b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/CloudFirestore.node.ts
@@ -44,12 +44,46 @@ export class CloudFirestore implements INodeType {
inputs: ['main'],
outputs: ['main'],
credentials: [
+ {
+ name: 'googleFirebaseApi',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: [
+ 'serviceAccount',
+ ],
+ },
+ },
+ },
{
name: 'googleFirebaseCloudFirestoreOAuth2Api',
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',
diff --git a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts
index f78bbaa824..74490aad55 100644
--- a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts
+++ b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts
@@ -12,7 +12,12 @@ import {
IDataObject,
} from 'n8n-workflow';
+import * as moment from 'moment-timezone';
+
+import * as jwt from 'jsonwebtoken';
+
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri: string | null = null): Promise { // tslint:disable-line:no-any
+ const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
const options: OptionsWithUri = {
headers: {
@@ -32,8 +37,22 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
delete options.body;
}
- //@ts-ignore
- return await this.helpers.requestOAuth2.call(this, 'googleFirebaseCloudFirestoreOAuth2Api', options);
+ if (authenticationMethod === 'serviceAccount') {
+ const credentials = this.getCredentials('googleFirebaseApi');
+
+ 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, 'googleFirebaseCloudFirestoreOAuth2Api', options);
+ }
} catch (error) {
let errors;
@@ -152,3 +171,51 @@ export function documentToJson(fields: IDataObject): IDataObject {
}
return result;
}
+
+function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise {
+ //https://developers.google.com/identity/protocols/oauth2/service-account#httprest
+
+ const scopes = [
+ 'https://www.googleapis.com/auth/datastore',
+ 'https://www.googleapis.com/auth/firebase',
+ ];
+
+ 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);
+}
+
diff --git a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts
index 66a38a0666..1a178a0170 100644
--- a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts
+++ b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts
@@ -1,4 +1,5 @@
import {
+ OptionsWithUri,
OptionsWithUrl,
} from 'request';
@@ -12,7 +13,12 @@ import {
IDataObject,
} from 'n8n-workflow';
+import * as moment from 'moment-timezone';
+
+import * as jwt from 'jsonwebtoken';
+
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, projectId: string, method: string, resource: string, body: any = {}, qs: IDataObject = {}, headers: IDataObject = {}, uri: string | null = null): Promise { // tslint:disable-line:no-any
+ const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
const options: OptionsWithUrl = {
headers: {
@@ -32,7 +38,22 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
delete options.body;
}
- return await this.helpers.requestOAuth2!.call(this, 'googleFirebaseRealtimeDatabaseOAuth2Api', options);
+ if (authenticationMethod === 'serviceAccount') {
+ const credentials = this.getCredentials('googleFirebaseApi');
+
+ 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, 'googleFirebaseRealtimeDatabaseOAuth2Api', options);
+ }
} catch (error) {
if (error.response && error.response.body && error.response.body.error) {
@@ -76,3 +97,51 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | IExecut
return returnData;
}
+
+function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise {
+ //https://developers.google.com/identity/protocols/oauth2/service-account#httprest
+
+ const scopes = [
+ 'https://www.googleapis.com/auth/userinfo.email',
+ 'https://www.googleapis.com/auth/firebase.database',
+ 'https://www.googleapis.com/auth/firebase',
+ ];
+
+ 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);
+}
diff --git a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts
index 38eaac6d0c..037a2ac55b 100644
--- a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts
+++ b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts
@@ -32,11 +32,45 @@ export class RealtimeDatabase implements INodeType {
inputs: ['main'],
outputs: ['main'],
credentials: [
+ {
+ name: 'googleFirebaseApi',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: [
+ 'serviceAccount',
+ ],
+ },
+ },
+ },
{
name: 'googleFirebaseRealtimeDatabaseOAuth2Api',
+ displayOptions: {
+ show: {
+ authentication: [
+ 'oAuth2',
+ ],
+ },
+ },
},
],
properties: [
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ {
+ name: 'Service Account',
+ value: 'serviceAccount',
+ },
+ {
+ name: 'OAuth2',
+ value: 'oAuth2',
+ },
+ ],
+ default: 'oAuth2',
+ },
{
displayName: 'Project ID',
name: 'projectId',
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 1ace196314..4613d504b4 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -92,6 +92,7 @@
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
"dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
"dist/credentials/GoogleDriveOAuth2Api.credentials.js",
+ "dist/credentials/GoogleFirebaseApi.credentials.js",
"dist/credentials/GoogleFirebaseCloudFirestoreOAuth2Api.credentials.js",
"dist/credentials/GoogleFirebaseRealtimeDatabaseOAuth2Api.credentials.js",
"dist/credentials/GoogleOAuth2Api.credentials.js",