Cloud Functions for Firebase HTTP timeout - javascript

I'm so close with this one.
I have written a Cloud Function that takes information sent from an Azure token to custom mint a Firebase token and send this token back to the client.
The token is created correctly, but isn't returned on my HTTP-request.
Unfortunately my Firebase app causes a timeout.
Function execution took 60002 ms, finished with status: 'timeout'
I can't really wrap my head around why that is, hence this post. Is there something wrong with my code, or is it me that's calling the HTTP-request wrong?
Here is the log I get from the Firebase Functions console.
Here's my code
// Create a Firebase token from any UID
exports.createFirebaseToken = functions.https.onRequest((req, res) => {
// The UID and other things we'll assign to the user.
const uid = req.body.uid;
const additionalClaims = {
name: req.body.name,
email: req.body.email
};
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, additionalClaims).catch(error => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
console.log(`Created user with UID:${uid}, Name: ${additionalClaims.name} and e-mail: ${additionalClaims.email}`);
return admin.auth().createUser({
uid: uid,
displayName: displayName,
email: email,
});
}
throw error;
console.log('Error!');
});
// Wait for all async tasks to complete, then generate and return a custom auth token.
return Promise.all([userCreationTask]).then(() => {
console.log('Function create token triggered');
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
});
});
});
When I'm making this HTTP-request, all i'm sending in is a JSON that looks like this:
parameters = [
"uid" : id,
"email" : mail,
"name" : name
]

Cloud Functions triggered by HTTP requests need to be terminated by ending them with a send(), redirect(), or end(), otherwise they will continue running and reach the timeout.
From the terminate HTTP functions section of the documentation on HTTP triggers:
Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.
After retrieving and formatting the server time using the Node.js moment module, the date() function concludes by sending the result in the HTTP response:
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
So, within your code, you could send the token back in the response with send(), for example:
// ...
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
res.status(200).send(token);
return token;
});
// ...

Related

How can I locally test cloud function invocation from third-party in Firebase?

I am developing an API for a third-party application not related to Firebase. This API consist of cloud functions to create and add users to database, retrieve user information and so on. These functions are created using the admin SDK. Example of a function that adds a user looks like this:
export const getUser = functions.https.onRequest(async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
const utils = ethers.utils;
const method = req.method;
const body = req.body;
const address = body.address;
const userAddress = utils.getAddress(address);
let logging = "received address: " + address + " checksum address: " + userAddress;
let success = false;
const db = admin.firestore();
const collectionRef = db.collection('users');
// Count all matching documents
const query = collectionRef.where("userAddress", "==", userAddress);
const snapshot = await query.get();
// If no documents match, there is no matching user
console.log(snapshot.docs.length);
if (snapshot.docs.length != 1) {
logging += "User does not exist in database.";
res.send({success: success, logging: logging});
return;
}
const data = snapshot.docs[0].data();
if (data != undefined) {
const createdAt = data.createdAt;
const emailAddress = data.emailAddress;
const userAddress = data.userAddress;
const updatedAt = data.updatedAt;
const userName = data.userName;
success = true;
res.send({success: success, createdAt: createdAt, emailAddress: emailAddress, userAddress: userAddress, updatedAt: updatedAt, userName: userName, logging: logging});
}
}
});
NOTE: These functions are NOT going to be called by the third-party application users, only by the third-party application itself.
I am pretty new at programming so I understand that this may not be the best way to code this functionality and I'm greatful for any tips you might have here as well. Anyway, back to my question. I'm trying to mimic the way that my customer is going to invoke these functions. So to test it, I'm using the following code:
function runGetUser() {
// test values
const address = 'myMetaMaskWalletAddress';
axios({
method: 'POST',
url: 'http://127.0.0.1:5001/cloud-functions/us-central1/user-getUser',
data: { "address": address },
}).then((response) => {
console.log(response.data);
}).catch((error) => {
console.log(error);
});
};
This works fine. However, I do not want anyone to be able to invoke these functions when I actually deploy them later. So I have been reading Firebase docs and googling on how to setup proper authentication and authorization measures. What I have found is setting up a service account and using gcloud CLI to download credentials and then invoke the functions with these credentials set. Is there not a way that I could configure this so that I query my API for an authorization token (from the file where the axios request is) that I then put in the axios request and then invoke the function with this? How do I do this in that case? Right now also, since I'm testing locally, on the "cloud function server-side" as you can see in my cloud function example, I'm allowing all requests. How do I filter here so that only the axios request with the proper authorization token/(header?) is authorized to invoke this function?
Thank you for taking the time to read this. Best regards,
Aliz
I tried following the instructions on this page: https://cloud.google.com/functions/docs/securing/authenticating#gcloud where I tried to just invoke the functions from the Gcloud CLI. I followed the instructions and ran the command "gcloud auth login --update-adc", and got the response: "Application default credentials (ADC) were updated." Then I tried to invoke a function I have "helloWorld" to just see that it works with the following command: curl -H "Authorization: bearer $(gcloud auth print-identity-token)" \http://127.0.0.1:5001/cloud-functions/us-central1/helloWorld", and I got the following response: "curl: (3) URL using bad/illegal format or missing URL". So I don't know what to do more.

Are there any public API (s) that I could use to verify an activation code?

I am trying to create a product verification system, and I have the login part down. My question is how are there any API (s) that can verify something like an activation code and return if it succeeded or not.
Btw, you might have to scroll horizontally to see all of the code
//How would I add a verification system
document.getElementById('redeemButton').addEventListener('click', () => {
var code = document.getElementById('redeemCodeBox').value;
var product = document.getElementById('productCode').value;
const fetchPromise = fetch(`https://www.mywebsite.com/api/redeem?product=${product}&code=${code}`);
fetchPromise.then( response => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then( json => {
console.log(json[0].name);
})
.catch( error => {
console.error(`Could not get products: ${error}`);
throw `Please Enter a Valid Activation Code`;
});
});
I don't think you should be dependent on a third party to verify an Activation Code.
You should use a combination of JWT token + activation code that you store in the database.
Generate an activation code.
Generate JWT token.
const token = await this.jwtService.sign(
{
usetId: userId
},
{ expiresIn: "1d" }
);
Save the activation code and the JWT token to the database.
Send an E-mail or SMS with an activation code to the user. + include URL where user can insert the activation code.
URL can look something like this:
https://www.mywebsite.com/api/activation?token=eyJhbGciOiJIfasdfas
(use query parameter for JWT token =>> ?token=JWT_token
Verify JWT token from URL (value from query parameter "token") and validate if Token is not expired.
Control if user input matches an Activation code stored in your database.
Activated

Angular2: PUT request to Node-Server doesn't work - authentication lost

I'm using Angular2 on the client side and a node-express server as my backend. The node-server works as an API-middleware and also as my authentication service. The user-requests must contain a valid JWT token to perform requests on the node-server.
All of my GET functions and other PUT functions are working properly. I wrote a new one, which just should delete an ID on a third-party API, doesn't.
Furthermore, my node-express server sends custom error messages at some points to the client. This comes to my problem, whenever I run my latest PUT-function, my server responds with "No token provided". This happens when the user isn't logged in on the client side.
As I said, all my other functions working. this.createAuthenticationHeaders(); is necessary to perform valid request on the server side. But it's implemented.
In other words, the authentication gets lost between client and server and I get my own error message: "No token provided".
Appointment-Detail.Component.ts
cancelAppointment() {
this.authService.getProfile().subscribe(profile => {
this.username = profile.user.username; // Set username
this.email = profile.user.email; // Set e-mail
if (profile.user.email) {
this.apiService.cancelUserAppointment(this.id).subscribe(data => {
console.log(this.id);
if (!data.success) {
this.messageClass = 'alert alert-danger'; // Set error bootstrap class
this.message = data.message; // Set error message
} else {
this.messageClass = 'alert alert-success'; // Set success bootstrap class
this.message = data.message; // Set success message
// After two seconds, navigate back to blog page
}
});
}
});
}
API Service
cancelUserAppointment(id) {
this.createAuthenticationHeaders();
console.log('API SERVICE ' + id);
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', this.options)
.map(res => res.json());
}
An API Service functions that works
getCertificatesByUser(email) {
this.createAuthenticationHeaders();
return this.http
.get(this.domain + 'api/user/' + email + '/certificates', this.options)
.map(res => res.json());
}
Server route to the third party API
router.put('/appointments/:id/cancel', (req, res) => {
console.log('hi');
var id = req.params.id;
const url = process.env.acuityUri + '/appointments/' + id + '/cancel';
console.log(id);
});
Authentication middleware
router.use((req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});
Without doing too much of a deep dive into your auth headers, I see a pretty glaring issue that I think may be the cause of your troubles.
HTTP REST verbs carry different "intents", the intent we specifically care about in this case is wether or not your request should have a body.
GET requests do not carry a body with them.
PUT requests do carry a body.
Because of this, angular's HttpClient request methods (http.get, http.post, etc.) have different method signatures.
To cut to the chase, http.put's method signature accepts 3 parameters: url, body, and options, whereas http.get's method signature only accepts 2: url and options.
If you look at your example, for http.put you are providing this.httpOptions as the second parameter instead of the third, so Angular is packaging up your options object as the PUT request body. This is why you have a working example and a non-working example; the working example is a GET!
The solution? Simply put something else as the request body in the second parameter and shift this.options down to the third parameter slot. If you don't care what it is, just use the empty object: {}.
So your request should look like this:
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', {}, this.options)
At the very least, this should send whatever is in this.options to the server correctly. Now wether what's in this.options is correct or not is another story.
Example PUT call from Angular's docs: https://angular.io/guide/http#making-a-put-request

How to just create an user in Firebase 3 and do not authenticate it?

I am working on a angularfire project and I would like to know how can I create an user in Firebase 3 and once done, do not authenticate the specified user. In the previous Firebase version we had the method called createUser(email, password). Now we have the method createUserWithEmailAndPassword(email, password) only, it creates and authenticates the specified user.
The answer to the question is: you can't.
We have similar situation where we have 'admin' users that can create other users. With 2.x this was a snap. With 3.x it's a fail as that capability was completely removed.
If you create a user in 3.x you authenticate as that user, and unauthenticate the account that's logged in.
This goes deeper as you would then need to re-authenticate to create another user; so the admin either does that manually or (cringe) stores the authentication data locally so it could be an automated process (cringe cringe, please don't do this)
Firebase has publicly stressed that 2.x will continue to be supported so you may just want to avoid 3.x.
Update:
one of the Firebaser's actually came up with a workaround on this. Conceptually you had an admin user logged in. You then create a second connection to firebase and authenticate with another user, that connection then creates the new user. Rinse - repeat.
Update again
See this question and answer
Firebase kicks out current user
You can do this using cloud function and firebase admin SDK.
Create HTTP function like below.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
exports.createUser = functions.https.onRequest((request, response) => {
if (request.method !== "POST") {
response.status(405).send("Method Not Allowed");
} else {
let body = request.body;
const email = body.email;
const password = body.password;
const displayName = body.displayName;
admin.auth().createUser({
email: email,
emailVerified: false,
password: password,
displayName: displayName,
disabled: false
})
.then((userRecord) => {
return response.status(200).send("Successfully created new user: " +userRecord.uid);
})
.catch((error) => {
return response.status(400).send("Failed to create user: " + error);
});
}
});
In your client app, call this function using Http request, for example using ajax
$.ajax({
url: "the url generated by cloud function",
type: "POST",
data: {
email: email,
password: password,
displayName: name
},
success: function(response) {
console.log(response);
},
error: function(xhr, status, error) {
let err = JSON.parse(xhr.responseText);
console.log(err.Message);
}
});

Send POST parameters to Voice URL for incoming calls with Twilio

I'm allowing my site to accept incoming calls with python and javascript. I want to make sure that whoever answers the call has their username logged correctly by Twilio. As posted in their docs you need to create a capability token to accept incoming calls:
capability.allow_client_outgoing(application_sid)
capability.allow_client_incoming("jenny")
There could be up to 20 different users on the site at once that could answer the call so I would want the "jenny" string to be replaced to allow a dynamic username based upon who is logged in.
I'm using Django as my framework and generate the token through a view that request the username from request.user.username
def token(request):
capability = TwilioCapability(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
capability.allow_client_outgoing(APP_ID)
capability.allow_client_incoming(request.user.username)
token = capability.generate()
data = json.dumps({'token': token})
return HttpResponse(data, content_type='application/json')
My Twilio Voice URL gets directed to this function. If it does not see a phone_number parameter then it can assume it is a incoming call. I somehow want to send the 'call_rep' parameter through this post which will match up the usernames to allow the incoming call with the correct user information:
#twilio_view
def make_call(request):
resp = Response()
if 'phone_number' in request.POST:
phone_number = request.POST['phone_number']
resp.say("Making the call now")
resp.dial(number=phone_number, callerId=TWILIO_PHONE_NUMBER)
else:
resp.say("Incoming call")
with resp.dial(callerId=TWILIO_PHONE_NUMBER) as r:
r.client(request.POST['call_rep'])
return resp
Is there somewhere in my JavaScript I need to put the call_rep's username in?
Twilio.Device.setup(token);
Twilio.Device.ready(function (device) {
});
Twilio.Device.error(function (error) {
console.log(error);
$("#log").text("Error: " + error.message);
});
Twilio.Device.offline(function(device) {
// Called on network connection lost.
});
Twilio.Device.connect(function (conn) {
console.log("Successfully established call");
});
Twilio.Device.disconnect(function (conn) {
// Called for all disconnections
console.log('DISCONNECT: ' + conn.status);
});
/* Listen for incoming connections */
Twilio.Device.incoming(function (conn) {
connection = conn
connection.accept()
});
For outgoing calls I can pass extra parameters no problems. How do I do this with incoming?
Twilio.Device.connect({
call_rep: '{{ request.user.username }}',
phone_number: phone_number
});

Categories

Resources