Using Firebase from AWS Lambda results in Task timed out - node.js

I'm using a Lambda function to create users with Firebase Authentication and then save them at my own database.
var firebase = require('firebase-admin')
const serviceAccount = require('./firebase.json')
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: 'https://*****.firebaseio.com'
})
exports.handler = function(event, context, callback) {
const {email, password, name, ...} = event
firebase.auth().createUser({
email,
emailVerified: false,
password,
displayName: name,
disabled: false
})
.then(firebaseResult => {
const {uid} = firebaseResult
return saveUserAtDatabase({email, name, ...})
})
.then(result => {
callback(null, result)
})
}
The user is created at Firebase and at my database as well, but when I run it at AWS Lambda, it throws this error:
{"errorMessage":"2019-01-07T21:25:49.095Z c...e9 Task timed out after 6.01 seconds"}
Doesn't matter how much time I increase the timeout from the function or set higher memory, it still throws the same error.

I solved the problem setting context.callbackWaitsForEmptyEventLoop=false.
This is because callback waits for the event loop to be empty, which doesn't happen using firebase.auth().createUser(). There is the context.callbackWaitsForEmptyEventLoop option documented here http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
When this property is set to false, Lambda freezes the container but does not clear the event loop when you call the callback.

Related

cloud functions: trying to create user on firestore from cloud functions "failed to deploy"

I am trying to access my db upon http request.
in the api builder from google i use node.js 16 as a runtime.
I tried running this code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firestore);
const firestoreDB = admin.firestore()
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase Cloud Functions!");
console.log("function triggered")
});
exports.createUser = functions.firestore.document('Users/asddsa')
.onCreate((snap, context) => {
const newValue = snap.data();
if (snap.data() === null) return null;
const uid = context.params.userId
let notificationCollectionRef = firestoreDB.collection('Users').doc(uid).collection('Notifications')
return notificationCollectionRef.add({
notification: 'Hello Notification',
notificationType: 'Welcome'
}).then(ref => {
return console.log('notification successful', ref.id)
})
});
But I cant even deploy it, it just states that "deployment failed".
Now this is usually when there is a typo in the code. But I am guessing that I didnt set up the connection to the firestore properley. (I never gave it a password or anything)
I assumed that as it is inside the same project, the connection would work either way, but maybe I am wrong?
How do I set up the connection to create the user and not have the deployment fail?
The instructions for Initial setup to configure and set up your Cloud Functions for Firebase project. you can check the Firebase documentation.
You can check the details in Cloud firestore trigger. Which describes Event triggers where you can trigger a function to fire any time a new document is created in a collection by using an onCreate(). This function calls createUser every time a new user profile is added.
Also You can have a look at Github link to create the user.

how to Invoke a firebase callable function from firebase https function with authentication?

I am trying to understand how we can securely call a firebase callable function from firebase https function, Here auth is required so that callable function is not public, it should be accessible only by that https function.
Note: I am new to gcloud and firebase :(
Https Function:
import * as functions from "firebase-functions";
import * as app from "firebase/app";
//import * as auth from "firebase/auth"
import { getFunctions, httpsCallable } from "firebase/functions";
const firebaseConfig = {
apiKey: "WEBAPIKEY",
authDomain: "project.firebaseapp.com",
databaseURL: "https://project.firebaseio.com", // not required though
projectId: "project-id",
storageBucket: "project.appspot.com", // not required
//appId: process.env.APP_ID, // not sure what to provide
messagingSenderId: "1234324" // default service account id
};
const firebaseApp = app.initializeApp(firebaseConfig);
export const caller = functions.https.onRequest((request, response) => {
let messageText = "hi";
const gfunctions = getFunctions(firebaseApp);
const funtionB = httpsCallable(gfunctions, 'funtionB');
funtionB({ text: messageText })
.then((result: any) => {
// Read result of the Cloud Function.
console.log(result);
response.send(result);
});
});
Callable Function:
import * as functions from "firebase-functions";
export const funtionB = functions.https.onCall((data, context) => {
console.log(context.auth); // not getting anything
/* if (!context.auth) { //trying to include this.
return {status: "error", code: 401, message: "Not signed in"};
} */
return new Promise((resolve, reject) => {
resolve({data: "YO", input: data});
});
});
Some logs, which make me to feel bad,
Callable request verification passed {"verifications":{"app":"MISSING","auth":"MISSING"}}
I am not going to user browser to consume this https function, not sure whether we can use auth check without browser. Any way to secure this callable function ? I want to remove alluser access from principal for both the functions to make it private.
I would say this isn't possible because, as you mentioned, the auth checks cannot be done without the browser, also the httpsCallable interface does not allow the context to be forced by passing as a parameter.
I would say that the best option would be to convert your Callable Function into an Http Function where you can implement your own authentication checks, this documentation may be useful for that.

Using AWS Lambda to delete a Cognito User

I want to give user's the ability to delete their account in my android app. I already set up a login/sig up functionality with AWS Amplify and a AWS Cognito User Pool. But Amplify doesn't provide a "delete User" functionality, so I wanted to use a lambda function to delete a user from my cognito user pool.
The function will be called when the user clicks on "delete my account" in the app. To test the function, I use a hard coded username in the Lambda function, instead of passing one into the function. But even that doesn't work. After deploying the Lambda function, I run the function by clicking on "Test" in the console. The console then shows Execution result: succeeded but the response is null. I would either epect a Status 200 or 400 as response. And in the CloudWatch logs of the Execution I can only see my first log statement ("I was here"), the other two don't show up. And in the Cognito Console the user is still there.
This is my Lambda Code (Node.js):
const AWS = require('aws-sdk');
console.log("I was here");
var params = {
UserPoolId: 'syz****f-dev',
Username: '5b53****138'
};
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({
"region": 'eu-central-1',
});
exports.handler = async (event) => {
cognitoidentityserviceprovider.adminDeleteUser(params, function(err, data) {
if (err) {
var response = {
statusCode: 400,
body: JSON.stringify('Didnt work!'),
};
console.log(err, err.stack);
return response;
}
else {
response = {
statusCode: 200,
body: JSON.stringify('yeah!'),
};
console.log(data);
return response;
}
});
};
The user "5b53....138" is still there in my Cognito User Pool "syz....f-dev" after I test this function:
This is the log file that I found in Cloudwatch:
My Lambda Function has a role with these 3 policies and I used the IAM Policy Simulator and the action AdminDeleteUser is allowed with AmazonCognitoAuthenticatedIdentities, so this shouldn`t be the problem:
AmazonCognitoAuthenticatedIdentities
AmazonCognitoPowerUser
AWSLambdaBasicExecutionRole
In CloudWatch I can see that the function got invoked.
First of all, your user pool id is wrong, find the correct on by opening your Cognito user pool: The first thing you see when opening your user pool in the console is the id:
It starts with your region followed by a _, in your case eu-central-1_.
Then try using this code instead of your adminDeleteUser function. Then it should work:
try {
const data = await cognitoidentityserviceprovider.adminDeleteUser(params).promise();
} catch (error) {
console.log(error);
}

Timeout error awaiting promise in Lambda?

I am testing a Serverless lambda function and get a timeout error which I believe is due to an await promise().
module.exports.create = async (event) => {
const provider = event.requestContext.identity.cognitoAuthenticationProvider
....//stuff here where I split auth token to get ids...
const cognito = new AWS.CognitoIdentityServiceProvider({
apiVersion: "2016-04-18"
});
const getUserParams = {
UserPoolId: userPoolId,
Username: userPoolUserId
};
const data =JSON.parse(event.body)
const getUser = await cognito.adminGetUser(getUserParams).promise()
const params = {
Item:{
userId: event.requestContext.identity.cognitoIdentityId,
email: getUser, //!timeout issue probably here!
content: data
}
};
try {
const { Listing } = await connectToDatabase()
const listing = await Listing.create({userId: params.Item.userId, email: params.Item.email
In researching a solution, I have come across people splitting up the lambda into two functions so that they collectively pass the timeout. I do not know how to reference a lambda within a lambda, nor am I sure this is the correct approach.
You change the timeout for lambda function
default timeout for lambda function is 3 sec you can override in below the function code basic settings
For anyone googling this: turns out adminGetUser needs a NAT Gateway configured in order for it to be able to retrieve data from Cognito. I was getting a timeout error because it was not executing, period. Read here: https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/.

What incorrect Metadata-Flavor header at GoogleAuth means?

I´m trying to write to firestore from a onCall firebase function
functions.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
console.log('initialing functions at ' , new Date().toString())
exports.getLinks = functions.runWith({ timeoutSeconds: 540 }).https.onCall( (data,context) => {
console.log('starting to get links ' , new Date().toString())
console.log('data' , data.query, data.limit, data.country, data.uid)
console.log('context auth', context.auth, 'context.auth.uid', context.auth.uid)
// is there anything like admin.setCredentials(context.auth) necessary here?
const queries = admin.firestore().collection('queries');
let uid = data.uid
console.log('uid', uid);
console.log('queries ref', queries)
//probably when trying to write here is not being allowed
queries.doc(uid).set({LinksArrayLength: 'starting'})
.then( r => console.log('writing to firestore 1 result', r))
.catch( err => console.error('writing to firestore 2 error', err))
The console output is like this
starting to get links Fri May 31 2019 19:01:10 GMT-0300 (GMT-03:00)
data sells anywhere 2 com fwfwqe
context auth {
uid: 'f23oij2ioafOIJOeofiwjOIJ',
token: {
iss: 'https://securetoken.google.com/was98oinr-fa4c9',
aud: 'was234r-f32c9',
auth_time: 1559327744,
user_id: 'f23oij2ioafOIJOeofiwjOIJ',
sub: 'f23oij2ioafOIJOeofiwjOIJ',
iat: 1559338208,
exp: 1559341808,
email: 'awef3h#gmail.com',
email_verified: false,
firebase: { identities: [Object], sign_in_provider: 'password' },
uid: 'f23oij2ioafOIJOeofiwjOIJ'
}
} context.auth.uid f23oij2ioafOIJOeofiwjOIJ
uid f1EMxzwjJlTaH3u7RAYsySx0MZV2
queries ref CollectionReference {
_firestore: Firestore {
_settings: {
projectId: 'xxx',
firebaseVersion: '7.0.0',
libName: 'gccl',
libVersion: '1.3.0 fire/7.0.0'
},
and then the not allowed write request to firestore ?
writing to firestore 2 error Error: Unexpected error determining execution environment: Invalid response from metadata service: incorrect Metadata-Flavor header.
> at GoogleAuth.<anonymous> (H:\nprojetos\whats_app_sender\firebase_sender\vue_sender\wa_sender\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:164:23)
> at Generator.throw (<anonymous>)
> at rejected (H:\nprojetos\whats_app_sender\firebase_sender\vue_sender\wa_sender\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:20:65)
> at processTicksAndRejections (internal/process/task_queues.js:89:5)
How could I ensure that the request.auth.uid is being sent to the firestore write request?
Firestore rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;//: if request.auth.uid != null;
// even when commented and allowing all requests still giving the error //message
}
}
}
Even when fully allowed is continues to give the error.
I´m trying to write to firestore to update the client side when something is being written to the the collections('queries') ... so that the client gets notified of the function progress...
Is there is a better way of doing that also?
On the client side the code goes like this
client-side
fireApp.firestore().collection('queries').doc(this.getUser.uid).onSnapshot(snap => {
debugger
console.log('snap', snap)
snap.exists ?
snap.docChanges().forEach(async change => {
if (change.type === "modified") {
_vue.updating = true // the function is in progress
Solved
It just required propper initialization
const credential = require('./xxxxx.json')
admin.initializeApp({credential: admin.credential.cert(credential),
databaseURL: "https://xxx.xxx.firebaseio.com"
});
this link (https://www.youtube.com/watch?v=Z87OZtIYC_0)
explains how to initialize it properly

Resources