Serverless Solution for Google Drive watch changes - firebase

I'd like to make a firebase function that is triggered by the Google Drive "Changes: watch":
https://developers.google.com/drive/api/v3/reference/changes/watch
I know I could probably do this with Google cloud storage, but for my use case it would be the best UX for my users to have Google drive watch for and upload file changes.
I'd like this to be a serverless solution to save money on running a server that is waiting/polling all the time. Ideally this can be done in firebase functions with a trigger as I'm parsing the data into Firestore

You can use Google Drive Push Notifications API.
Setup your WebHook to point to your firebase function endpoint:
POST https://www.googleapis.com/drive/v3/changes/watch
Authorization: Bearer auth_token_for_current_user
Content-Type: application/json
{
"id": "4ba78bf0-6a47-11e2-bcfd-0800200c9a77", // Your channel ID.
"type": "web_hook",
"address": "<your-function-endpoint>", // Your receiving URL.
"token": "target=myApp-myChangesChannelDest", // (Optional) Your channel token.
}

Related

Hosting Vector Tiles From Firebase Cloud Storage

I am hosting vector tiles that are being consumed by my javascript web application with mapboxGL.
MapboxGL requests these tile from the bucket in a very specific way. When instantiating the map you provide a url to your bucket like this:
addLayer({
source: {
type: "vector",
tiles: [
`https://storage.googleapis.com/${bucketName}/{z}/{x}/{y}.pbf`,
],
minZoom: 4,
maxZoom: 12,
}
})
And the map will request the proper tiles based on the viewport. You can transform the request and add Authorization: Bearer {{tokens}} to each request for a tile by adding the following property:
transformRequest: (url, resourceType) => {
if (url.startsWith(settings.VECTOR_TILE_BUCKET_URL)) {
return {
url: url,
headers: {
Authorization: `Bearer ${accessToken}`,
'Cache-Control': 'no-cache'
}
};
}
}
Now here is my question for someone familiar with firebase. When setting 'rules' on my cloud storage bucket, can I allow read access for all users in the account and then pass the firebase token that the user receives as a Bearer {{token}} in these tile requests? Is there another method I could protect my vector tiles with firebase's services that I'm not thinking of? I read the documentation where it shows how to access cloud storage bucket using the firebase SDK but it's unclear if I can also auth into their bucket by passing the token in a more traditional format as a query param.
In the Official Cloud Storage Authentication Documentation, you can see many methods of authentication (with gsutil, Client Library, Api), but as you are using Firebase, Firebase SDK comes with Firebase Authentication built-in, so Firebase SDK is the only way to access the bucket, such as the SDK handles all auth related operations and using other methods could expose your apps to risks.
An another way to authenticate could be using Real-time Database Rest API, which I think will not help you for your scenario.

CLOUD_SDK_CREDENTIALS_WARNING We recommend that most server applications use service accounts instead

Context: I have just learn a trick to get (download) data from FireStore Dashboard. Obviouslly, it is much easier just open Google Dashboard on Browser and see with my eyes to own Google Dasboard. Nevertheless, for personal reasons, in my company the operators can't look at a third Dashboard. They only can see internal Dashboards. I am trying some workaround where I can get/download the same data used for fill in Dashboard and imported it to our internal solution based on Dynatrace/ELK.
For learning purposes, in order to download Google Dashboard data I followed:
1 - Get a ACCESS_TOKEN using gcloud
C:\Program Files (x86)\Google\Cloud SDK>gcloud auth application-default print-access-token
C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\bin\..\lib\third_party\google\auth\_default.py:69: UserWarning: Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most server applications use service accounts instead. If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about service accounts, see https://cloud.google.com/docs/authentication/
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
ya29. ... ACCESS-TOKEN ...7hu
2 - using the above ACCESS_TOKEN to get Dashboard data like:
curl --location --request GET 'https://monitoring.googleapis.com/v3/projects/firetestjimis/timeSeries?filter=metric.type%20%3D%20%22firestore.googleapis.com%2Fdocument%2Fread_count%22&interval.endTime=2020-05-07T15:01:23.045123456Z&interval.startTime=2020-05-05T15:01:23.045123456Z' --header 'Authorization: Bearer ya29...ACCESS-TOKEN 7hu'
Obviously this is just an example how to get how many conections satisfied the filter criteria. I can keep searching adjusting the API and filters according to Google Cloud Metrics and Google Cloud API v3
Other example of getting Dashboard metada this time from API version 1 is
curl --location --request GET 'https://monitoring.googleapis.com/v1/projects/firetestjimis/dashboards' --header 'Authorization: Bearer ya29... ACCESS-TOKEN ...7hu'
The warning when getting the ACCESS-TOKEN from gcloud encourage to see Authentication guidance and I did it. Well, it doens't explain how to fix this warning neither why "If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error". I can see my trick to get data from Dashboard is working but it seems I am relying on strange way to get a ACCESS-TOKEN.
So my straight question is: what is the appropriate steps to get manually an ACCESS-TOKEN and use it in curl/postman avoiding such warnning?
It seems to me that, based on this stackoverflow answer the root cause is "... This error message means you're using a User account, and not a service account..." So how can I fix it? Do I have to create a service account? If so, how? At the end of this accepted answer I read "... to use the true application default you can use gcloud auth application-default login..." And it is exactly how I am logging with gcloud: run gcloud auth application-default login, when open Google SingleSignOn I pick my email which is the the same user I registered in Firebase account. The answer also mentioned "... method to associate a specific service account is gcloud auth activate-service-account --key-file ...." I want give a try on it but which key-file is he/she talking about?
In case it is relevant, in my case I am only using FireStore under Firebase project (I am not using anything else other than FireStore).
*** EDITED after John's answer
We are moving soon this project to production.
1 - Our Mobile App will create money transfer by posting it to our internal microserve. Such post request will return a CustomToken generated from our internal NodeJs server.
2 - Our internal microservice will replicate such transfer to Firestore and update its state on Firestore accordingly.
3 - Instead of our Mobilie App poll or listen our internal microservice to get the status it will listen to Firestore for getting the status from respective document. In order to listen, it will use the CustomToken returned from post in step 1. Our company wants just take advantage of Real Time Database feature from Google Firestore for this project (reactive approach).
Do you see any consideration when compared what I am doing with your statement: "Google prefers in most cases that you authorize using a service account"?
The CustomToken is created internally with this NodeJs server and depending on uid extrated from antenticated user authentication/users from Google Firebase:
const admin = require('firebase-admin');
exports.serviceAccount = {
"type": "service_account",
"project_id": "firetestjimis",
"private_key_id": "ecfc6 ... fd05923",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIE .... 5EKvQ==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fg6p9#firetestjimis.iam.gserviceaccount.com",
"client_id": "102422819688924138150",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fg6p9%40firetestjimis.iam.gserviceaccount.com"
}
admin.initializeApp({
credential: admin.credential.cert(exports.serviceAccount)
});
var uid = "NS .... Ro2"; //copied from https://console.firebase.google.com/project/firetestjimis/authentication/users
var claim = {
control: true
};
admin.auth().createCustomToken(uid)
.then(function (customToken) {
console.log(customToken)
})
.catch(function (error) {
console.log("Error creating custom token:", error);
});
Our mobile (example in Angular but same idea for IOS and Android) has the SERVICE_ACCOUNT_JSON_FILE I downloaded like this:
environment.ts:
export const environment = {
production: false,
firebaseConfig: {
apiKey: "AIzaSy ... 3DCGihK3xs",
authDomain: "firetestjimis.firebaseapp.com",
databaseURL: "https://firetestjimis.firebaseio.com",
projectId: "firetestjimis",
storageBucket: "firetestjimis.appspot.com",
messagingSenderId: "795318872350",
appId: "1:7953 ... 32b26fb53dc810f"
}
};
app.component.ts
public transfers: Observable<any[]>;
transferCollectionRef: AngularFirestoreCollection<any>;
constructor(public auth: AngularFireAuth, public db: AngularFirestore) {
this.listenSingleTransferWithToken();
}
async listenSingleTransferWithToken() {
await this.auth.signInWithCustomToken("eyJh ### CUSTOMTOKEN GENERATED FROM INTERNAL NODEJS SERVER ABOVE ### CVg");
this.transferCollectionRef = this.db.collection<any>('transfer', ref => ref.where("id", "==", "1"));
this.transfers = this.transferCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
const data = action.payload.doc.data();
const id = action.payload.doc.id;
return { id, ...data };
});
});
}
}
I understand that both CustomToken creation and its use from our Mobile is relying entirely on Service Account. Am I right? Did I miss some concept and I am using USER CREDENTIAL behind the scene and something that works properly in DEV environment will pop up some surprise when in production? Obviously for this question all comes from my free accoutn but in production it will be paid account but the code and steps will be exactly the same here.
There are two types of credentials used by the CLI:
User Credentials
Service Accounts
Google prefers in most cases that you authorize using a service account. However, some services require user credentials (usually non-Google Cloud Platform services). Consult the documentation for each service that you use.
Execute the following command. This will show the credentials you are using:
gcloud auth list
To configure the CLI to use a service account, execute this command:
gcloud auth activate-service-account <SERVICE_ACCOUNT_EMAIL_ADDRESS> --key-file=<SERVICE_ACCOUNT_JSON_FILE>
I wrote an article that explains in more detail (and several additional articles on services accounts, authorization, etc.):
Google Cloud – Setting up Gcloud with Service Account Credentials
So, the auth token is generated from your gcloud init authorization, which is end-user credentials. That's why you're getting that warning. Because you've used your manually signed in credentials to generate the token.
The preferred way to auth is to use a service account (documentation here: https://cloud.google.com/iam/docs/service-accounts) for authentication. That documentation will also walk you through creating a service account. If you're using it to talk to Firestore, your service account will need appropriate Firestore role permissions. Not to confuse you, but the roles in IAM are for datastore although they apply for Firestore.
This page: https://cloud.google.com/firestore/docs/security/iam lists out which roles/permissions your service account will need in order to do various things with Firestore.
Now, all that being said, the key-file it's talking about is the service account key that you can download when you create the service account. Easiest is to do it via the console in your GCP project, as when you're creating the service account, there's a handy button to create the key, and it downloads it to your local machine.

Difference between Firebase "Realtime Database" and "Cloud Firestore" in terms of REST API and authentication

Maybe a duplicated question, but this time I have tried my best in my explanation.
Being on my Firebase console, when I click on "Database" option on the left side of the screen, it is presented to me that we have two different databases, the "Realtime Database" and the "Cloud Firestore".
This is the current look of my Realtime Database:
This is the current look of my Cloud Firestore:
REALTIME DATABASE TEST
On Postman, I do select PATCH option, and to define what I will put on the address bar, I follow the synthax:
https://PROJECT-NAME.firebaseio.com/PATH.json?auth=YOUR-DATABASE-SECRET-CODE
For me, it becomes:
https://eletronica-ab6b1.firebaseio.com/database333.json?auth=DZSQwLoNWAneWA9BcEfAgnelmY965pq98HF4pIT0
That's not my real secret key, don't worry.
And as content of my request, I go to Body, select 'raw' and 'JSON' on the list, and below is the content:
{"name": "PAUL"}
Exactly as you can see here:
After click SEND on Postman, the write to the REALTIME DATABASE occurs in fact:
So the write to REALTIME DATABASE is working via Postman.
CLOUD FIRESTORE TEST
Here is my problem, I'm not having sucess with Cloud Firestore.
On Postman, I do select GET option, and to define what I will put on the address bar, I follow the synthax:
https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/COLLECTION/DOCUMENT
For me, it becomes:
https://firestore.googleapis.com/v1/projects/eletronica-ab6b1/databases/(default)/documents/colA/docA.json?auth=DZSQwLoNWAneWA9BcEfAgnelmY965pq98HF4pIT0
Exactly as you can see here:
And I do receive this message as response:
{
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"auth\": Cannot bind query parameter. Field 'auth' could not be found in request message.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"auth\": Cannot bind query parameter. Field 'auth' could not be found in request message."
}
]
}
]
}
}
It is strange that if I try to change the project name on the request, for example, from original "eletronica-ab6b1" to "eletronica-999zzz", shomething that should not exist, the response is the same as above, exactly the same.
Why it is so easy to write to REALTIME DATABASE and seems to be not easy to write to CLOUD FIRESTORE DATABASE? Is it not possible to write to Cloud Firestore in a simple way like is done with Realtime Database? Is Cloud Firestore based on tokens? If yes, how do I get a token via Postman, and then use that token to write to Cloud Firestore database?
Regards.
Cloud Firestore does not support access via secret key. To use the REST API you will need a Google OAuth access token (for administrative access) or a Firebase Auth ID Token (normal user access). It will then need to be passed to the server in an Authorization header of the form:
Authorization: Bearer ${insert_token_here}
I'd recommend reading the docs for additional information about how to obtain the appropriate tokens.

How to Connect Google Play Real-Time Developer Notifications to Firebase Cloud Function via Pub/Sub?

I'm in the final stages of development for my new mobile app, but can't seem to find a way to allow Google Play Real-Time Developer Notifications to communicate to a firebase cloud function via the recommended Google Pub/Sub method.
The expected payload flow should go as follows:
User Purchases Subscription via Play Store > Play Store sends R-T Dev Notification to Pub/Sub > Pub/Sub sends message across to firebase cloud function > Cloud function runs with payload.
I currently have an Apple Developer webhook set-up in a similar way, which webhooks a receipt payload to an iOS cloud function I have setup.
During the pub/sub setup phase, the Pub/Sub page asks to verify the cloud function URL, which I cannot do as I am not the true webmaster of cloud function domain, hence halting me about halfway down the 'Add real-time developer notification' docs that google supplies.
Is there a way to get the RT notification to either a Pub/Sub cloud function or a HTTPS cloud function bypassing the google Pub/Sub and going directly to the webhook or another way to complete the flow above?
The ultimate aim is to provide a way to ensure the purchase made is actually a valid purchase, and not a forged request made by someone intercepting the client > server webhook and submitting one of their own accord.
After creating the new topic you DO NOT have to create manually a Pub/Sub subscription as explained in the documentation.
To make it work with firebase you have to deploy a new cloud function like this:
exports.YOUR_FUNCTION_NAME = functions.pubsub.topic('YOUR_TOPIC_NAME').onPublish((message) => {
// Decode the PubSub Message body.
const messageBody = message.data ? Buffer.from(message.data, 'base64').toString() : null;
// Print the message in the logs.
console.log(`Hello ${messageBody || 'World'}!`);
return null;
});
Please note that you need to replace YOUR_FUNCTION_NAME and YOUR_TOPIC_NAME.
When the deploy of this function finish, you will finally find the function in the subscription list.
At this point, you can edit the params of the automatically created subscription, and you will find the url endpoint already filled with an internal url.
Here you can find an example: how to setup a PubSub triggered Cloud Function using the Firebase SDK for Cloud Functions.

Slack API Event Subscription to trigger Firebase Cloud Functions

Goal
I would like Slack to trigger a Firebase Cloud Function.
Example: A user sends a Slack message, and Firebase Cloud Functions writes part of the message to the Firebase Database.
Tools: Slack API \ Event Subscription, googleapis, nodejs, etc.
Issue
The Slack documentation here describes the challenge response requirement.
Once you receive the event, respond in plaintext with the challenge
attribute value.
However, I'm not sure how to let Firebase know the Slack request is authorized. An HTTP request to Firebase Cloud Functions must include a Firebase ID. I've let googleapis do the work of setting up the Firebase ID, and I don't see a way to alter Slack's initial verification request (if I had an ID to provide)
What's the best way to trigger Firebase with the Slack API?
Getting Slack to verify the Firebase URL is pretty easy.
Solution
Google Firebase Cloud Function
import * as functions from "firebase-functions";
export const helloSlack = functions.https.onRequest((request, response) => {
if (request) {
response.status(200).send(request.body);
} else {
console.log("Request Error...");
throw response.status(500);
}
});
Steps
Deploy your Firebase Cloud Function
Goto https://api.slack.com/apps
Your App > Event Subscriptions > Enable Events
Turn on Events
Enter your Firebase Cloud Functions URL
tl;dr
Slack instructions:
We’ll send HTTP POST requests to [your] URL when events occur. As soon
as you enter a URL, we’ll send a request with a challenge parameter,
and your endpoint must respond with the challenge value.
Cloud Function URL:
https://firebase-slack-adaptor.cloudfunctions.net/helloSlack
To meet the verification challenge, enter your Firebase Cloud Functions URL (example above) in Slack's Request URL field.
Your Firebase Cloud Function should return the body of the Slack request. Slack finds what it needs in request.body and should verify your URL.

Resources