Firebase callable + express? - firebase

With functions.https.onRequest(app); it was possible to use express right away.
I'm wondering if it's possible to use functions.https.onCall(...) together with express in the same way?
onCall(...) seem to have a different signature but maybe there is still a way to keep using express while working with onCall(...) functions?

No, it's not possible. Callable functions force your endpoint to use a certain path, a certain type of input (JSON via POST) and a certain type of output (also JSON). Express wouldn't really help you out, given the constraints of how callables work. You can read about all the callable protocol details in the documentation. You can see that callables abstract away all of the details of the request and response, which you would normally work with when using Express.

What does work however is using onRequest and calling that... then you can use express like normal and have the simplicity of firebase callable on the client side...
then you can do your authorization like normal. For example with the following middleware:
export const createFirebaseAuth = () => (req: express.Request, res: express.Response, next: express.NextFunction) => {
console.log('Time: ', Date.now());
const token = req.header('Authorization');
if (!token) {
res.status(400);
res.send('Authtoken must be sent with a request');
return;
}
admin
.auth()
.verifyIdToken(token.replace('Bearer ', ''))
.then((v) => {
req.user = v;
next();
})
.catch((error) => {
res.status(401);
res.send(error.message);
res.end();
});
}

Related

Secure firebase webhook

I have to create a webhook from typeform to firebase. I will create a cloud function listening to events sent from typeform. The typeform is managed by a third party.
The only issue I have, is the authorization part for the webhook. I understood (from reading different post) that anyone can "talk" to the cloud function URL. But I would like to have a secure and exclusive communication between typeform and firebase.
Any hints ?
Thank for your time.
You can definitively connect a Typeform webhook to a Cloud function and push data to Firebase storage.
In addition to authentication pointed by Frank, Typeform also provides a signature mechanism to ensure that the request comes from Typeform webhook.
Typeform lets you define a secret to sign the webhook payload.
When you receive the payload on your end, in the cloud function, you verify first if it's signed correctly, if it's not it means it's not coming from Typeform, therefore, you should not deal with it.
Here is an example to verify the webhook signature:
app.post('/typeform/webhook', async (request, response) => {
console.log('~> webhook received');
// security check, let's make sure request comes from typeform
const signature = request.headers['typeform-signature']
const isValid = verifySignature(signature, request.body.toString())
if (!isValid) {
throw new Error('Webhook signature is not valid, someone is faking this!');
}
//valid signature let's do something with data received
})
And here is the verifySignature function
const crypto = require('crypto')
const verifySignature = function(receivedSignature, payload){
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('base64')
return receivedSignature === `sha256=${hash}`
}
There are more details on Typeform documentation.
Hope it helps :)
Calling request.body.toString() does not work the way it is described in #Nicolas GreniƩs answer. The result will always be the string "[Object object]", as it only utilizes the default prototype as described here (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString).
A valid approach to stringify req.body would be to use JSON.stringify() which would still not deliver the expected result as you need to hash the original binary data (https://developer.typeform.com/webhooks/secure-your-webhooks/).
The Solution (without Firebase)
Use app.use(bodyParser.raw({ type: 'application/json' })) as specified here (Validate TypeForm Webhook payload in Node) to get the raw binary data and pass the request body directly into the hashing function.
const bodyParser = require("body-parser");
app.use(bodyParser.raw({ type: "application/json" })); // Notice .raw !
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.body) // Pass the raw body after getting it using bodyParser
.digest('base64')
})
The solution using Firebase
If you are using a Firebase Cloud Function to handle the request, you can't use bodyParser this way as Firebase already takes care of the parsing (https://firebase.google.com/docs/functions/http-events#read_values_from_the_request). Instead, use req.rawBody to access the raw body and pass it to the hash function.
// No need for bodyParser
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.rawBody) // Notice .rawBody instead of just .body
.digest('base64')
})
Remark for TypeScript users
The default Express Request object does not contain a rawBody property. Be aware that TypeScript therefore might throw an error of no overload matches this call or Property 'rawBody' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. The actual Request object will however be provided by Firebase and will contain said properties. You can access the actual Request object type using functions.https.Request.

How to use Telegraf (Telegram) in Firebase?

I'm trying use Telegraf library with Firebase Functions but it's not working as I expected.
I follow these this article and instructions as appear in webhooks (as appears for express example) and webhookcallback as appear in telegraf docs.
const Telegraf = require('telegraf')
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions')
// The Firebase Admin SDK to access the Firebase Realtime or Firestore Database.
const admin = require('firebase-admin')
// set telegraf and responses.
const BOT_TOKEN = 'my-telegram-bot-token'
const bot = new Telegraf(BOT_TOKEN)
bot.start((ctx) => ctx.reply("Start instructions"))
bot.help((ctx) => ctx.reply("This is help"))
bot.hears('hi', (ctx) => ctx.reply('Hola'))
bot.on('text', (ctx) => ctx.reply('Response to any text'))
bot.catch((err, ctx) => {
console.log(`Ooops, ecountered an error for ${ctx.updateType}`, err)
})
// initialize bot
bot.launch() // <-- (2)
//appends middleware
exports.ideas2coolBot = functions.https.onRequest(bot.webhookCallback(`/my-path`));
In firebase server I need add bot.launch() (2) to get worked, but it works just for max timeout set in Firebase Function. I need to recall Telegram "setWebhook" API to get work again and it works for the same time. It's like it's generate one function instance and shut down when time is over.
I noted the telegraf.launch() have options to start in poll or webhook mode but its not pretty clear for me how to use this options.
How should I use telegram.launch() to get worked in webhook mode in Firebase?
Edit:
When I used getWebhookInfo I get this result:
{
"ok": true,
"result": {
"url": "https://0dbee201.ngrok.io/test-app-project/us-central1/testAppFunction/bot",
"has_custom_certificate": false,
"pending_update_count": 7,
"last_error_date": 1573053003,
"last_error_message": "Read timeout expired",
"max_connections": 40
}
}
and console shows incoming conection but do nothing...
i functions: Beginning execution of "ideas2coolBot"
i functions: Finished "ideas2coolBot" in ~1s
Edit2:
I've been trying adding Express too...
app.use(bot.webhookCallback('/bot'))
app.get('/', (req, res) => {
res.send('Hello World from Firebase!')
})
exports.ideas2coolBot = functions.https.onRequest(app);
it's works '/' path but got nothing with '/bot'. POST to '/bot' not response.
By the way, I tried a express standalone version and works prefect, but using it with firebase doesn't respond ("Read timeout expired").
delete
bot.launch()
try add this
exports.YOURFUNCTIONNAME = functions.https.onRequest(
(req, res) => bot.handleUpdate(req.body, res)
)
then set ur webhook manually
https://api.telegram.org/bot{BOTTOKEN}/setWebhook?url={FIREBASE FUNCTION URL}'

Request has incorrect content type when using Twilio and Google Cloud Functions

I am following this Twilio tutorial on how to reply to SMS messages with my app:
https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-node-js
The tutorial assumes you're using Express, but I am doing this with a Cloud Function, so my code looks a bit different:
exports.sms = functions.https.onCall((req: any, res: any) => {
const twiml = new MessagingResponse();
if (req.body.Body === 'hello') {
twiml.message('Hi!');
} else if (req.body.Body === 'bye') {
twiml.message('Goodbye');
} else {
twiml.message(
'No Body param match, Twilio sends this in the request to your server.',
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
});
When I text my Twilio #, it hits that endpoint, but I get the following error:
Request has incorrect Content-Type. application/x-www-form-urlencoded
How do I get around this?
It looks like you're mixing up callable type functions and normal HTTP type functions. Please read the documentation to understand the difference. Callable functions are intended to be invoked directly from your mobile app using the provided client SDK. They provide two arguments: an input data object, and a context. Callables do NOT provide "req" and "res". If you want control over the the response, you should be using a normal HTTP function with "onRequest" instead of "onCall".

http post request firebase cloud function

I want to make post request and send data into body in firebase cloud function.
as default, it is get request or post request?
var functions = require('firebase-functions');
exports.tryfunction= functions.https.onRequest((req, res) => {
console.log(req.body) // or it should be req.query
});
how do I know and decide what the method it is?
I just sharing simple code part. I'm using like this.
const functions = require('firebase-functions');
exports.postmethod = functions.https.onRequest((request, response) => {
if(request.method !== "POST"){
response.send(405, 'HTTP Method ' +request.method+' not allowed');
}
response.send(request.body);
});
I hope it will be helpful.
There is no default. The request method is whatever the client chose to send.
The req object in your callback is an express.js Request object. Use the linked documentation, you can see that the request method can be found by using req.method.
To specify the HTTP method that your Firebase Function accepts you need to use Express API as you'd normally do in a standard Express app.
You can pass a full Express app to an HTTP function as the argument for onRequest(). This way you can use Express' own API to restrict your Firebase function to a specific method. Here's an example:
const express = require('express');
const app = express();
// Add middleware to authenticate requests or whatever you want here
app.use(myMiddleware);
// build multiple CRUD interfaces:
app.get('/:id', (req, res) => res.send(Widgets.getById(req.params.id)));
app.post('/', (req, res) => res.send(Widgets.create()));
app.put('/:id', (req, res) => res.send(Widgets.update(req.params.id, req.body)));
app.delete('/:id', (req, res) => res.send(Widgets.delete(req.params.id)));
app.get('/', (req, res) => res.send(Widgets.list()));
// Expose Express API as a single Cloud Function:
exports.widgets = functions.https.onRequest(app);
Explanation of the above code:
We expose a Firebase function with endpoint /widgets with different handlers for different HTTP methods using Express' own API, e.g. app.post(..), app.get(..), etc. We then pass app as an argument to functions.https.onRequest(app);. That's it, you're done!
You can even add more paths if you wish, E.g. if we want an endpoint that accepts GET requests to an endpoint that looks like: /widgets/foo/bar, we simply add app.get('/foo/bar', (req, res => { ... });.
It's all taken directly from the official Firebase docs.
I'm surprised #Doug Stevenson didn't mention this in his answer.

How Firebase Cloud functions handle HTTP post method?

I have created Firebase Cloud Functions app,
I created function with https.onRequest.
and get data with req.body but there is not data there.
Can Firebase Cloud Functions can handle HTTP POST method?
This is my sample code:-
var functions = require('firebase-functions');
exports.testPost = functions.https.onRequest((req, res) => {
console.log(req.body);
});
I tested by postman with POST method but didn't show result in Firebase log.
Functions built on Firebase can also use Express.js routers for handling GET/POST/PUT/DELETE, etc... is fully supported by Google, and is the recommended way to implement these types of functions.
More documentation can be found here:
https://firebase.google.com/docs/functions/http-events
Here's a working example built on Node.js
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const app = express();
// Automatically allow cross-origin requests
app.use(cors({ origin: true }));
app.get('/hello', (req, res) => {
res.end("Received GET request!");
});
app.post('/hello', (req, res) => {
res.end("Received POST request!");
});
// Expose Express API as a single Cloud Function:
exports.widgets = functions.https.onRequest(app);
Then, run firebase deploy, and that should compile your code and create the new "widgets" function. Note: You can rename widgets to anything you want. Ultimately, it will generate a URL for calling the function.
I am planning to do the same thing. What I reckon the approach should be is to check the request.method in the function body. A probable approach can be:
if (request.method != "POST") {
respond.status(400).send("I am not happy");
return;
}
// handle the post request
Here's some reference to the details regarding what the request object holds: https://firebase.google.com/docs/functions/http-events
Firebase functions support GET, POST, PUT, DELETE, and OPTIONS method, and you can check what kind of methods that trigger your function.
// Check for POST request
if(request.method !== "POST"){
res.status(400).send('Please send a POST request');
return;
}
Then to get data from POST request (for example JSON type) will be in the header of your request.
const postData = request.body;
// for instance
const format = req.body.format;
// query string params
let format = req.query.format;
Maybe your project hasn't been setup to communicate with your firebase database. Try the following from your terminal:
npm install -g firebase-tools
Then inside your project folder, run the following and login using your credentials
firebase login
Then
firebase init functions
This will create a folder with index.js, package.json and node_modules
If you are using Postman correctly the rest of your code should work.

Resources