dialogflow to interact with firebase realtime database - firebase

Is it possible to get some data from firebase database by using dialogflow? I'm new to dialogflow so I'm still doing some research about.
For example, I want to ask my chatbot if a doctor is available then chatbot will access the firebase db to check if that specific doctor is available or lets say schedule me an appoint with doc X so dialogflow will do a function that allow will enter a schedule object to the database
thanks.

You can use Firebase function to fulfill your Dialogflow agent and the Firestore database to store data. An example of how to do so with Dialogflow's Google Assistant integration is below:
const functions = require('firebase-functions');
const firebaseAdmin = require('firebase-admin');
const DialogflowApp = require('actions-on-google').DialogflowApp;
// Initialize Firebase Admin SDK.
firebaseAdmin.initializeApp(functions.config().firebase);
exports.dialogflowFulfillment = functions.https.onRequest((req, res) => {
// Log headers and body
console.log('Request headers: ' + JSON.stringify(req.headers));
console.log('Request body: ' + JSON.stringify(req.body));
// Create a new Dialgoflow app request handler
let app = new DialogflowApp({request: req, response: res});
// welcome function handler
function start(app) {
// Get user ID from the Google Assistant through Action on Google
let userId = app.getUser().userId;
// Check if the user is in our DB
admin.firestore().collection('users').where('userId', '==', userId).limit(1).get()
.then(snapshot => {
let user = snapshot.docs[0]
if (!user) {
// If user is not in DB, its their first time, Welcome them!
app.ask('Welcome to my app for the first time!');
// Add the user to DB
firebaseAdmin.firestore().collection('users').add({
userId: userId
}).then(ref => {
console.log('Added document with ID: ', ref.id);
});
} else {
// User in DB
app.ask('Welcome back!')
}
});
}
// Map function hanlder to Dialogflow's welcome intent action 'input.welcome'
const actionMap = new Map('input.welcome', start)
app.handleRequest(actionMap);
});

Related

How to connect Realtime Database firebase with dialogflow chatbot

I want to connect Realtime database firebase with Dialogflow chatbot and how to get some data from Realtime database firebase using Dialogflow. I want when the user ask my chatbot to show the machine water quantity then chatbot will ask for the machine id of which you want to know water quantity, then chatbot access the realtime database firebase to check if the machine is present in database then it will show the water quantity of that machine.
I have tried to connect realtime database firebase this way-
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const firebaseAdmin=require("firebase-admin");
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
firebaseAdmin.initializeapp({
credential:firebaseAdmin.credential.applicationDefault(),
databaseURL:"ws://rtdb.asia-southeast1.firebasedatabase.app/",
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function welcome(agent) {
agent.add(`Welcome to Akvo chat!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
//function getFromFirebase(agent){
//return firebaseAdmin.database().ref("board_id").once("value", snapshot=>{
//var boardid=snapshot.val();
//agent.add(boardid);
//});
//}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
//intentMap.set('board id', getFromFirebase);
agent.handleRequest(intentMap);
});
But it shows an error.
Error: " Error happened during Cloud Functions Deployment "
can you please tell me how to do this.

Setting state parameter to a Stripe query to pass a FireStore uuid

I am trying to pass a FirebaseFirestore User Uid to a Stripe / firestore cloud function.
So I would have an https query like following :
https://connect.stripe.com/express/oauth/authorize?response_type=code&client_id={accountid}&scope=read_write to open in a Webview
Here is my function
exports.connectStripeExpressAccount = functions.https.onRequest((req, res) =>{
console.log('query state is ----> ' + req.query.state);
const authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
}).then(async response => {
var connected_account_id = response.stripe_user_id;
const uid = req.query.state
const writeResult = await admin.firestore().collection('Registration').doc(uid)
.set({'customer_id': connected_account_id});
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});
For new integrations with Express Accounts you should ideally be using the Account Links functionality instead of OAuth. That said, if you provide the state value, it should carry through, so I'd make sure you're actually providing it when opening the WebView.
If the User uid is stored in the query parameter state and the URL looks like this:
https://connect.stripe.com/express/oauth/authorize?response_type=code&client_id=ca_JCV8JW9ZIjBaGkwkhbDDDQegceWGidqh&scope=read_write&state=useruidxxx
Your code would look like this:
exports.connectStripeExpressAccount = functions.https.onRequest((req, res) =>{
console.log('query state is ----> ' + req.query.state);
const authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
}).then(async response => {
var connected_account_id = response.stripe_user_id;
const uid = req.query.state
const writeResult = await admin.firestore().collection('Registration').doc(uid)
.set({'customer_id': connected_account_id});
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});

Integrate custom Oauth provider with firebase.auth().signInWithRedirect?

I setup a Twitch OAuth integration using the Instagram example, now I can login into my app by opening the popup.html page that the example gave me.
Here's my adapted code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const { AuthorizationCode } = require('simple-oauth2');
const fetch = require('node-fetch');
// Firebase Setup
const admin = require('firebase-admin');
// #ts-ignore
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
});
const OAUTH_REDIRECT_URI = `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`;;
const OAUTH_SCOPES = 'user:read:email';
/**
* Creates a configured simple-oauth2 client for Twitch.
*/
function twitchOAuth2Client() {
// Twitch OAuth 2 setup
// TODO: Configure the `twitch.client_id` and `twitch.client_secret` Google Cloud environment variables.
const credentials = {
client: {
id: functions.config().twitch.client_id,
secret: functions.config().twitch.client_secret,
},
auth: {
tokenHost: 'https://id.twitch.tv',
tokenPath: '/oauth2/token',
authorizePath: '/oauth2/authorize',
},
options: {
bodyFormat: 'json',
authorizationMethod: 'body',
},
};
return new AuthorizationCode(credentials);
}
/**
* Redirects the User to the Twitch authentication consent screen. Also the 'state' cookie is set for later state
* verification.
*/
exports.redirect = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.__session || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('__session', state.toString(), { maxAge: 3600000, httpOnly: true });
const redirectUri = authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
/**
* Exchanges a given Twitch auth code passed in the 'code' URL query parameter for a Firebase auth token.
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie.
* The Firebase custom auth token, display name, photo URL and Twitch acces token are sent back in a JSONP callback
* function with function name defined by the 'callback' query parameter.
*/
exports.token = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
try {
cookieParser()(req, res, async () => {
try {
console.log('Received verification state:', req.cookies.__session);
console.log('Received state:', req.query.state);
if (!req.cookies.__session) {
throw new Error(
'State cookie not set or expired. Maybe you took too long to authorize. Please try again.'
);
} else if (req.cookies.__session !== req.query.state) {
throw new Error('State validation failed');
}
} catch (error) {
return res.jsonp({ error: error.toString() });
}
let accessToken;
try {
console.log('Received auth code:', req.query.code);
const options = {
client_id: functions.config().twitch.client_id,
client_secret: functions.config().twitch.client_secret,
code: req.query.code,
grant_type: 'authorization_code',
redirect_uri: OAUTH_REDIRECT_URI,
};
console.log('Asking token with options', JSON.stringify(options));
accessToken = await authorizationCode.getToken(options);
console.log('Auth code exchange result received');
const twitchUser = await getTwitchUser(accessToken.toJSON().access_token);
// Create a Firebase account and get the Custom Auth Token.
const firebaseToken = await createFirebaseAccount(twitchUser);
// Serve an HTML page that signs the user in and updates the user profile.
return res.jsonp({ token: firebaseToken });
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
/**
* Creates a Firebase account with the given user profile and returns a custom auth token allowing
* signing-in this account.
*
* #returns {Promise<string>} The Firebase custom auth token in a promise.
*/
async function createFirebaseAccount(twitchUser) {
// The UID we'll assign to the user.
const uid = `twitch:${twitchUser.id}`;
// Save the access token to the Firebase Database.
const db = admin.firestore();
const databaseTask = db.collection('users').doc(uid).set(twitchUser);
// Create or update the user account.
const userCreationTask = admin
.auth()
.updateUser(uid, {
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
})
.catch((error) => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
return admin.auth().createUser({
uid: uid,
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
});
}
throw error;
});
// Wait for all async task to complete then generate and return a custom auth token.
await Promise.all([userCreationTask, databaseTask]);
// Create a Firebase custom auth token.
const token = await admin.auth().createCustomToken(uid);
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
}
async function getTwitchUser(accessToken) {
console.log('Fetching Twitch user with access_token', accessToken);
try {
const response = await fetch('https://api.twitch.tv/helix/users', {
method: 'GET',
headers: {
'Client-Id': functions.config().twitch.client_id,
Authorization: 'Bearer ' + accessToken,
},
});
const data = await response.json();
return { ...data.data[0], access_token: accessToken };
} catch (error) {
console.error(error);
}
}
I'd like, though, to login into Twitch using the firebase.auth().signInWithRedirect() method that I already use for Facebook and Google, unfortunately I can't find any documentation about this, and the Facebook provider source code refers to some externs.* resources so I'm not sure how to adapt it for my own needs.
Right now I have two endpoints/cloud functions: _twitchRedirect and _twitchToken, what should I do to integrate them with signInWithRedirect?
I was similarly curious, so spent a little time playing around with things today.
In short, when using Firebase Auth, I believe the providerId will need to be one of the existing supported providers.
If you upgrade to using the Google Cloud Identity Platform though, I believe you will be able to configure custom providers, and then use this function to authenticate:
https://cloud.google.com/identity-platform
We can see that firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) are used with a number of the providers here, eg.
https://cloud.google.com/identity-platform/docs/web/apple
https://cloud.google.com/identity-platform/docs/web/microsoft
In addition to these provider choices that we get with the standard Firebase Auth, Google Cloud Identity Platform allows us to also add SAML and OpenID Connect (OIDC) integrations:
https://cloud.google.com/identity-platform/docs/web/saml
https://cloud.google.com/identity-platform/docs/web/oidc
When adding a new identity provider using either of these, we are able to specify the 'Provider ID' to use (prefixed with either saml. or oidc.). This custom provider ID is then used with firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) as described above.
For example, if I created a new identity provider with an ID of oidc.foo, my integration code would end up looking like:
const provider = new firebase.auth.OAuthProvider('oidc.foo');
firebase.auth().signInWithPopup(provider)
.then((result) => {
// result.credential is a firebase.auth.OAuthCredential object.
// result.credential.providerId is equal to 'oidc.foo'.
// result.credential.idToken is the OIDC provider's ID token.
})
.catch((error) => {
// Handle error.
});
Based on my understanding of this, I believe we will only currently be able to add custom providers this way if they conform to the OpenID Connect (OIDC) standard (including the OIDC Discovery part, which uses a /.well-known/openid-configuration URL):
Note: If your OIDC provider doesn't comply with the OIDC specification for discovery, it won't work with Identity Platform.
So to my knowledge, the best way to implement 'normal' OAuth2 providers currently is the custom backend function flow you used above (based on the Firebase Auth examples).
As part of figuring this out, I decided to see what would happen if I used a provider ID that didn't match anything configured in my account (this is a fairly verbose step by step, and the main answer is already included above, but this may help provide some more context/help someone out, so including it here)
var provider = new firebase.auth.OAuthProvider("foo.example.com");
firebase
.auth()
.signInWithRedirect(provider)
.then((result) => console.log("OAuthProvider:", result))
.catch((error) => console.log("OAuthProvider::error:", error));
firebase
.auth()
.getRedirectResult()
.then((result) => console.log("RedirectResult:", result))
.catch((error) => console.log("RedirectResult::error:", error));
At first I go this auth/auth-domain-config-required error:
OAuthProvider::error: {
"code": "auth/auth-domain-config-required",
"message": "Be sure to include authDomain when calling firebase.initializeApp(), by following the instructions in the Firebase console."
}
I figured maybe this should be set to the OAuth provider I was wanting to login to, so I set authDomain in my firebase config to foo.myauthprovider.com, but when I called signInWithRedirect, it tried to load the following URL (where the apiKey is the API key of my firebase project), which failed to load:
https://foo.myauthprovider.com/__/auth/handler?apiKey=REDACTED&appName=%5BDEFAULT%5D&authType=signInViaRedirect&providerId=foo.example.com&redirectUrl=http%3A%2F%2Flocalhost%3A3000%2F&v=7.14.5
This /__/auth/handler URL is part of Firebase Auth's reserved URLs, which you can read more about at:
https://firebase.google.com/docs/hosting/reserved-urls#auth_helpers
And is explained a little better in this StackOverflow answer, but is basically what Firebase Auth uses to handle OAuth callbacks to avoid needing to expose sensitive credentials on the frontend, and so users don't need to implement their own handlers all the time):
Why does Firebase auth uses a "middleware" redirect before returning to my app?
Changing authDomain to the actual custom domain of my firebase project fixed that issue, and then resulted in the following auth/operation-not-allowed error when I tried to redirect:
RedirectResult::error: u {code: "auth/operation-not-allowed", message: "The identity provider configuration is not found.", a: null}

accessing realtime DB from firebase functions

We are using firebase functions and firebase real time DB for our mobile app. We do send emails when someone place an order which is implemented using firebase DB triggers like below:
exports.on_order_received = functions.database.ref("/orders/{id}")
.onCreate((change, context) => {
console.log("start of on_order_received")
...
Above trigger works just fine for us. Now, we have some requirement where we do not have a DB trigger in the picture. It's a http request like below
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
The question is how do we access the real time db objects here? or in other words how do i get the access to the /orders node ? I tried like below
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
var ref = functions.database.ref('orders')
ref.orderByValue().limitToLast(3).on("value", function(snapshot) {
snapshot.forEach(function(data) {
console.log("The " + data.key + " dinosaur's score is " + data.val());
});
})
but this does not work. I get error "orderByValue() is not a function"
You should use the Firebase Admin SDK. It has the ability to read and write your database. In fact, when you write a database trigger, the refs it gives you to work with actually come from the Admin SDK, so it's the same API. You just need to initialize it yourself when using HTTP type functions:
// at the top of the file:
const admin = require('firebase-admin');
admin.initializeApp();
// in your function:
const root = admin.database().ref();
// root is now a Reference to the root of your database.
You'll have to use the admin and not the functions to access the database() to read the data.
(Please ensure you've access to firebase-admin sdk, use import or require as appropriate depending on whether you are using TypeScript or JavaScript)
// The Firebase Admin SDK to access the Firebase Realtime Database.
import * as admin from 'firebase-admin';
or
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
Try this:
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
/* Do not use functions */
// var ref = functions.database.ref('orders')
/* Instead use the admin */
var ref = admin.database().ref('orders')
ref.orderByValue().limitToLast(3).on("value", function(snapshot) {
snapshot.forEach(function(data) {
console.log("The " + data.key + " dinosaur's score is " + data.val());
});
})
orderByValue() is not defined in functions.database - but is actually available in admin.database().ref()

Generating a custom auth token with a cloud function for firebase using the new 1.0 SDK

As of firebase-admin#5.11.0 and firebase-functions#1.0.0 firebase-admin no longer takes in an application config when the app initializes.
I had a firestore function that would generate a custom token using firebase-admin’s createCustomToken. Calling that function would generate a credential that I would pass into initializeApp in the credential attribute. How would I go about doing that now?
Do I need to edit process.env.FIREBASE_CONFIG somehow and put the serialized credential there before calling initializeApp?
Based on this issue in Github, it still works.
https://github.com/firebase/firebase-admin-node/issues/224
The following example worked for me:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://yourapplication.firebaseio.com/'
});
exports.createToken = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
return admin.auth()
.createCustomToken(uid)
.then(customToken => {
console.log(`The customToken is: ${customToken}`);
return {status: 'success', customToken: customToken};
})
.catch(error => {
console.error(`Something happened buddy: ${error}`)
return {status: 'error'};
});
});
Michael Chen's cloud function appears to trigger from a HTTP request from somewhere (an external server?). My employee wrote a cloud function that triggers when the user logs in:
// this watches for any updates to the user document in the User's collection (not subcollections)
exports.userLogin = functions.firestore.document('Users/{userID}').onUpdate((change, context) => {
// save the userID ubtained from the wildcard match, which gets put into context.params
let uid = context.params.userID;
// initialize basic values for custom claims
let trusted = false;
let teaches = [];
// check the Trusted_Users doc
admin.firestore().collection('Users').doc('Trusted_Users').get()
.then(function(doc) {
if (doc.data().UIDs.includes(uid)) {
// if the userID is in the UIDs array of the document, set trusted to true.
trusted = true;
}
// Get docs for each language in our dictionary
admin.firestore().collection('Dictionaries').get()
.then(function(docs) {
// for each of those language docs
docs.forEach(function(doc) {
// check if the userID is included in the trustedUIDs array in the doc
if (doc.data().trustedUIDs.includes(uid)) {
// if it is, we push the 2-letter language abbreviation onto the array of what languages this user teaches
teaches.push(doc.data().shortLanguage);
}
});
// finally, set custom claims as we've parsed
admin.auth().setCustomUserClaims(uid, {'trusted': trusted, 'teaches': teaches}).then(() => {
console.log("custom claims set.");
});
});
});
});
First, we put in a lastLogin property on the user object, which runs Date.now when a user logs in and writes the time to the database location, triggering the cloud function.
Next, we get the userID from the cloud function response context.params.userID.
Two variables are then initialized. We assume that the user is not trusted until proven otherwise. The other variable is an array of subjects the user teaches. In a roles-based data security system, these are the collections that the user is allowed to access.
Next, we access a document listing the userIDs of trusted users. We then check if the recently logged in userID is in this array. If so, we set trusted to true.
Next, we go to the database and traverse a collection Dictionaries whose documents include arrays of trusted userIDs (i.e., users allowed to read and write those documents). If the user is in one or more of these arrays, he or she gets that document added to the teaches property on his or her user data, giving the user access to that document.
Finally, we're ready to run setCustomUserClaims to customize the token claims.
Here's a variation for a Callable Cloud Function, thanks to Thomas's answer
Once the custom claim is set, you can access the field in/from .. say, a firebase storage rule.
For example:
allow write: if request.auth.token.isAppAdmin == true;
With a Callable Cloud Function, as long as the admin.auth().setCustomUserClaims(..) function is returned somewhere along the promise chain, the claim field will be added to the request.auth.token object:
const functions = require('firebase-functions');
exports.setIsAdminClaim = functions.https.onCall((data, context) => {
var uid = context.auth.uid;
return admin.auth().setCustomUserClaims(
uid, {
isAppAdmin: true
}
)
.then(() => {
var msg = 'isAppAdmin custom claim set';
console.log(msg);
return new Promise(function (resolve, reject) {
var resolveObject = {
message : msg
};
resolve(resolveObject);
});
});
});

Resources