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

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");
});
});

Related

Unable to transfer NEAR tokens between accounts using near-api-js

I am trying to transfer NEAR tokens between 2 testnet wallets using the near-api-js library in NextJS
Running send money function of the account, I am getting the following error
import { connect, keyStores } from "near-api-js";
export const NearConfig = async () => {
const config = {
networkId: "testnet",
keyStore: new keyStores.BrowserLocalStorageKeyStore(),
nodeUrl: "https://rpc.testnet.near.org",
walletUrl: "https://wallet.testnet.near.org",
helperUrl: "https://helper.testnet.near.org",
explorerUrl: "https://explorer.testnet.near.org",
};
return await connect(config);
};
setNear(await NearConfig());
const sendTokens = async () => {
try {
const account = await near.account(account_id);
await account.sendMoney(
"itissandeep98.testnet", // receiver account
"100000000" // amount in yoctoNEAR
);
} catch (error) {
console.log(error);
showAlert(error.message, "error");
}
};
On running account.getAccessKeys(); there are full access keys as well as functional access keys available, then why I am not able to send tokens?
Moreover, I don't understand the below screenshot from the docs(https://docs.near.org/docs/develop/front-end/introduction); why isn't it allowed?
Found this after one week of struggle: Connect FullAccess account with near-api-js
const PENDING_ACCESS_KEY_PREFIX = "pending_key";
const loginFullAccess = async (options) => {
const currentUrl = new URL(window.location.href);
const newUrl = new URL(wallet._walletBaseUrl + "/login/");
newUrl.searchParams.set('success_url', options.successUrl || currentUrl.href);
newUrl.searchParams.set('failure_url', options.failureUrl || currentUrl.href);
const accessKey = KeyPair.fromRandom("ed25519");
newUrl.searchParams.set("public_key", accessKey.getPublicKey().toString());
await wallet._keyStore.setKey(
wallet._networkId,
PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(),
accessKey
);
window.location.assign(newUrl.toString());
};
After login you can use the sendMoney function to transfer NEAR tokens between accounts
I wanted to open up near website asking user for permissions required for sending the tokens. Was struggling till I noticed this text in nearjs doc regarding account.sendMoney:
Hint
Use WalletConnection in the browser to redirect to NEAR Wallet for
Account/key management using the BrowserLocalStorageKeyStore.
Basically, instead of nearConnection needed to use walletConnection
// const account = await nearConnection.account(accountId) // Wrong
const account = await walletConnection.account() // Profit

Firebase multi-tenancy has this error There is no user record corresponding to the provided identifier

I have setup my firebase with multiple tenants using Google Identity Platform.
And through Identity Platform, I manually added a user acct to each tenant.
For example, test#abcdemo.com for abcdemo tenant
test#defdemo.com for defdemo tenant
In my Flutter Web client app, I was able to sign in with FirebaseAuth's signInWithEmailAndPassword successfully with user acct and tenantId.
After successful sign in, I want to set a custom claim by passing the idToken that I retrieved from successful sign-in to setCustomClaims cloud function below:
const express = require("express");
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const app = express();
app.post("/setCustomClaims", async (req, res)=>{
// Get the ID token passed.
const idToken = req.body;
functions.logger.log("Here's idToken: ", idToken);
// Verify the ID token and decode its payload.
const claims = await admin.auth().verifyIdToken(idToken);
functions.logger.log("After verify ID Token");
// Verify user is eligible for additional privileges.
if (
typeof claims.email !== "undefined" &&
typeof claims.email_verified !== "undefined"
) {
functions.logger.log("Inside if condition");
//Result of code execution below:
//This shows thee correct project id, etc
functions.logger.log("Project ID is ", process.env.FIREBASE_CONFIG);
//Result of code execution below:
//Rejected: FirebaseAuthError: There is no user record corresponding to the provided identifier.
await admin.auth().getUserByEmail(claims.email).then(
(record) =>
functions.logger.log("Success: ", record)).catch(
(reasonStr) =>
functions.logger.log("Rejected: ",
reasonStr));
//Result of code execution below:
//Users: {"users":[]}
await admin.auth().listUsers().then((users) =>
functions.logger.log("Users: ", users));
//Result of code execution below:
//Error: There is no user record corresponding to the provided identifier.
await admin.auth().setCustomUserClaims(claims.sub, {
youcanaccess: true,
});
//Didn't even go to this line because the above code was erroring out.
functions.logger.log("after setCustomClaims");
// Tell client to refresh token on user.
res.end(JSON.stringify({
status: "success",
}));
functions.logger.log("after success");
} else {
// Return nothing.
res.end(JSON.stringify({status: "ineligible"}));
functions.logger.log("after ineligible");
}
});
exports.api = functions.https.onRequest(app);
The code above has some extra code for debugging purpose.
As you can see the code above, I put some comments till the last line
of executed statement.
It's erroring out in this line:
await admin.auth().setCustomUserClaims.
And the error message again is this:
Error: There is no user record corresponding to the provided identifier.
I don't know exactly why it stated that there's no user record even though I was able to sign in successfully.
My guess is the users in the tenant scope didn't get recognized by the admin.auth()?
By the way, this wasn't done in local emulator.
Looking forward for any advice. Thank you very much for the help
If you are using multi-tenant you must set the tenant id before you access it. if not it will check only outside of the tenant. So you should modify the code following:
first, you've to assign your user's tenant id like the following:
const tenantAuth = admin.auth().tenantManager().authForTenant("TENANT_ID");
Now you can access a particular tenant:
const claims = await tenantAuth.verifyIdToken(idToken);
then,
if (
typeof claims.email !== "undefined" &&
typeof claims.email_verified !== "undefined"
) {
functions.logger.log("Inside if condition");
functions.logger.log("Project ID is ", process.env.FIREBASE_CONFIG);
await tenantAuth.getUserByEmail(claims.email).then(
(record) =>
functions.logger.log("Success: ", record)).catch(
(reasonStr) =>
functions.logger.log("Rejected: ",
reasonStr));
await tenantAuth.listUsers().then((users) =>
functions.logger.log("Users: ", users));
await tenantAuth.setCustomUserClaims(claims.sub, {
youcanaccess: true,
});
functions.logger.log("after setCustomClaims");
// Tell client to refresh token on user.
res.end(JSON.stringify({
status: "success",
}));
functions.logger.log("after success");
} else {
// Return nothing.
res.end(JSON.stringify({status: "ineligible"}));
functions.logger.log("after ineligible");
}
Hope this solution will help you to solve the problem.

How to load 2 different Firestore docs in one 'onUpdate' Cloud Function?

I am trying to make an "onUpdate" function that loads the document that has been updated. Then I want to load another document using the data received by the wildcards. So to summarize I want to access the document that was updated and one more that is in the same collection.
I want : /userProfiles/{doc1}/employees/{doc2} AND /userProfiles/{doc1}.
I can get them both but when I try to use the data from one, it doesn't read the previous data and gives me a ReferenceError.
The end goal is to use both these docs to send an email with nodemailer. Thanks for any help.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require('nodemailer');
admin.initializeApp();
exports.testLog = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
var info = [];
const doc1 = context.params.doc1;
const doc2 = context.params.doc2;
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
var email = doc.data().email;
var phone = doc.data().phone;
info.push(doc.data());
console.log(email, phone); // sees and gets info
return email, phone;
}),
db
.collection("userProfiles")
.doc(`${doc1}`)
.collection(`employees`)
.doc(`${doc2}`)
.get()
.then(doc => {
info.push(doc.data());
var Status = doc.data().Status;
console.log(phone, `${Status}`); //phone is undefined
if (`${Status}` === "Alarm") {
// replace with variables from the users settings page
console.log(`${info.phone}`); // phone is undefined
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: "xxxxxx#gmail.com",
pass: "xxxxxxxxxx"
}
});
// send mail with defined transport object
let mailOptions = {
from: '"Fred Foo 👻" <foo#example.com>',
to: `${info.phone}`, // tried phone as well
subject: "Hello ✔",
text: "216+?",
};
transporter.sendMail(mailOptions, error => {
if (error) {
return console.log(error);
} else {
return console.log("message sent");
}
});
}
console.log(Status);
// return
return console.log("im after the if statement. No alarm triggered");
})
.then(message => console.log(message.sid, "success"))
.catch(err => console.log(err))
);
});
So I want to get the phone number and the Status in these 2 images
The error that is returned:
ReferenceError: phone is not defined
There are two things that aren't quite working the way you expect leading to your problem:
The handling of promises isn't really passing data the way you expect -- in particular, the variables phone and email exist only in one promise handler, they aren't global in scope, so phone and email aren't being passed down the promise chain.
You don't actually need to ever read the second document, as the content is passed to you in the function itself. This actually greatly simplifies the overall thing you are doing, and makes dealing with the first point nearly trivial, since you can skip the second database call.
Look at this code where I have omitted the messaging code for clarity and just left in place most of the log messages:
exports.firestoreOnUpdateTest = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
// var info = []; I have removed this list, it is not necessary
const doc1 = context.params.doc1;
// no need to get the doc2 parameter, as we are handed the doc itself by the function call.
const doc2content = change.after.data();
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
const doc1content = doc.data();
const email = doc1content.email;
const phone = doc1content.phone;
console.log(email, phone); // sees and gets info
console.log(`No need to fetch doc2, as I already have it: ${JSON.stringify(doc2content)}`);
const Status = doc2content.Status;
console.log(`email for user is still: ${email}`); // email is now defined
console.log(phone, `${Status}`); // phone is now defined
if (`${Status}` === "Alarm") {
console.log(`${phone}`); // phone is now defined
return console.log('message would be sent here - code omitted')
}
console.log(Status);
return console.log("im after the if statement. No alarm triggered");
})
.catch(err => console.error(err))
);
});
In the new version, we just store the content from the document that triggered us, including the Status parameter. We then fetch the document with the content we need -- at the higher level in the tree. Once that document is returned, we just process it and combine with the data from doc2. All the fields are now defined (assuming, of course, the database objects are well-formed).
Your messaging code would be re-inserted right were the obvious log message is.
Finally, the info list I don't think is necessary now, so I've removed it. Instead, I recommend you build what you need as you construct the message itself from the data already on hand. That said, your original code wasn't accessing it correctly (that is, as a list) anyway and may have been confusing you further.
Finally, I haven't addressed the use of the Nodemailer module as the question focused primarily on the undefined fields, but I suspect your original code may not be entirely correct either -- as it doesn't either return a promise back from sendMail() or perform an await on that call (and make the entire function async), so you will need to look at that more closely.

Cloud Functions for Firebase Time Out w/ wrong response

Newbie question: Cloud Function times out every single time I run it.
In addition, it only returns ONE value, which is the first userId, in the Functions Log and none of its children. Im assuming this is because it's calling the .once however, it's in a forEach loop, so I'm not sure what it wants.
Firebase database
-items
---- userId0123456789
---- randomKey987654321
-- itemName
-- itemDate
-- itemType
---- userId987654321
---- randomKey012345678
-- itemName
-- itemDate
-- itemType
And here is the function code...
const key = req.query.key;
**let userID = 'xxxxx';
let ikey = 'xxx';**
var dbRef = admin.database().ref('/items/{userID}/{ikey}');
dbRef.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key+": "+child.val());
});
});
UPDATE: here is the entire function and now it's just timing out with no response.
'use strict';
// Firebase Functions
const functions = require('firebase-functions');
// Firebase Admin
const admin = require('firebase-admin');
// Default admin firebase configuration
admin.initializeApp(functions.config().firebase);
const rp = require('request-promise');
const promisePool = require('es6-promise-pool');
const PromisePool = promisePool.PromisePool;
const secureCompare = require('secure-compare');
const MAX_CONCURRENT = 3;
//Initial function call:
exports.CheckItemTypeinFB = functions.https.onRequest((req, res) => {
const key = req.query.key;
// Exit if the keys don't match
if (!secureCompare(key, functions.config().cron.key)) {
console.log('The key provided in the request does not match the key set in the environment. Check that', key,
'matches the cron.key attribute in `firebase env:get`');
res.status(403).send('Security key does not match. Make sure your "key" URL query parameter matches the ' +
'cron.key environment variable.');
return;
}
// Try the database here...
let userID = 'xxx';
let ikey = 'xxxxx
//create database ref
let ref = admin.database().ref(`/items/${userID}/${ikey}`);
//do a bunch of stuff
ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
res.send(200, {/* response data */});
});
//send back response
// res.redirect(200);
}) // END THE MAJJOR CONTAINER THINGS
// Returns an access token using the Google Cloud metadata server. */
function getAccessToken(accessToken) {
// If we have an accessToken in cache to re-use we pass it directly.
if (accessToken) {
return Promise.resolve(accessToken);
}
const options = {
uri: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token',
headers: {'Metadata-Flavor': 'Google'},
json: true
};
return rp(options).then(resp => resp.access_token);
}
Help is much appreciated.
Update:. Timeout is fixed and it returns the userId's that are in the database under "/items". HOWEVER, if I use ${userId}/${key} I get nothing. I'm still not able to tell how to get the children under random userId's in the database and none of the other posts I read explain it. Firebase's docs state to use {userId} to get all under that wildcard but its not working. What am I missing?
You're not returning the result of the once function or returning at all, so the function doesn't know when to finish hence the timeout.
let userID = 'xxxxxxxxx';
let key = 'xxxxxxxx';
let ref = admin.database().ref(`/items/${userID}/${key}`);
return ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
});
Also please be aware that the reference you are observing will give you the children of a particular user item (itemName, itemDate, itemType). If you want the items belonging to a particular user, adjust your reference path to be /items/${userID}.
When inside a HTTP trigger, you can send a response after observing the value.
exports.CheckItemTypeinFB = functions.https.onRequest((req, res) => {
...
ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
res.send(200, {/* response data */});
});
});

dialogflow to interact with firebase realtime database

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);
});

Resources