What incorrect Metadata-Flavor header at GoogleAuth means? - node.js

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

Related

Error trying to set auto back up Firestore, cloud function

I am following this tutorial here: Tutorial
everything seems ok and it allows me to do everything in the tutorial, but when I run the function I get this error.
textPayload: "TypeError: Cannot read property 'charCodeAt' of undefined
at peg$parsetemplate (/workspace/node_modules/google-gax/build/src/pathTemplateParser.js:304:17)
at Object.peg$parse [as parse] (/workspace/node_modules/google-gax/build/src/pathTemplateParser.js:633:18)
at new PathTemplate (/workspace/node_modules/google-gax/build/src/pathTemplate.js:55:54)
at segments.forEach.segment (/workspace/node_modules/google-gax/build/src/pathTemplate.js:120:29)
at Array.forEach (<anonymous>)
at PathTemplate.render (/workspace/node_modules/google-gax/build/src/pathTemplate.js:114:23)
at FirestoreAdminClient.databasePath (/workspace/node_modules/#google-cloud/firestore/build/src/v1/firestore_admin_client.js:904:57)
at exports.scheduledFirestoreExport (/workspace/index.js:13:31)
at Promise.resolve.then (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:330:28)
at process._tickCallback (internal/process/next_tick.js:68:7)
insertId: "000000-8410c5c7-8304-42b6-b2b6-dd55a54e8cab"
resource: {2}
timestamp: "2020-07-11T18:14:35.981Z"
severity: "ERROR"
labels: {1}
logName: "projects/b-b-b-app/logs/cloudfunctions.googleapis.com%2Fcloud-functions"
trace: "projects/b-b-b-app/traces/d7c07a715d0106225d9963ce2a046489"
receiveTimestamp: "2020-07-11T18:14:44.813410062Z"
}
I can't see what the problem may be.
I changed the buckets and the app ids like asked in the tutorial.
I am on a Blaze plan and can export the database to the bucket manually by using shell command and using
gcloud firestore export gs://bbbdata-backup
I am using the GCP console on the firebase site and using this code.
const firestore = require('#google-cloud/firestore');
const client = new firestore.v1.FirestoreAdminClient();
const bucket = 'gs://bbbdata-backup'
exports.scheduledFirestoreExport = (event, context) => {
const databaseName = client.databasePath(
process.env.GCLOUD_PROJECT,
'(default)'
);
return client
.exportDocuments({
name: databaseName,
outputUriPrefix: bucket,
// Leave collectionIds empty to export all collections
// or define a list of collection IDs:
// collectionIds: ['users', 'posts']
collectionIds: [],
})
.then(responses => {
const response = responses[0];
console.log(`Operation Name: ${response['name']}`);
return response;
})
.catch(err => {
console.error(err);
});
};
Following the tutorial referred by the OP I run into precisely the same error. Runtime used: Node.js 14.
Root cause of the issue: value of process.env.GCLOUD_PROJECT is undefined.
Workaround: Go to GCP console -> Home. Note your Project ID. Replace process.env.GCLOUD_PROJECT with the 'Project ID' string. The Cloud Function will then work as expected
Note: it appears to be a known issue that GCLOUD_PROJECT environment variable was missing in the Node.js 10 runtime. This bug report contains a lot of additional pointers: https://github.com/firebase/firebase-functions/issues/437
I had a similar issue last year, probably you are missing some permission, I would do it this way, hope this works for you:
import * as functions from 'firebase-functions'
import { auth } from 'google-auth-library'
export const generateBackup = async () => {
const client = await auth.getClient({
scopes: [
'https://www.googleapis.com/auth/datastore',
'https://www.googleapis.com/auth/cloud-platform'
]
})
const path = `YOUR_FOLDER_NAME_FOR_THE_BACKUP`
const BUCKET_NAME = `YOUR_BUCKET_NAME_HERE`
const projectId = await auth.getProjectId()
const url = `https://firestore.googleapis.com/v1beta1/projects/${projectId}/databases/(default):exportDocuments`
const backup_route = `gs://${BUCKET_NAME}/${path}`
return client.request({
url,
method: 'POST',
data: {
outputUriPrefix: backup_route,
// collectionsIds: [] // if you want to specify which collections to export, none means all
}
})
.catch(async (e) => {
return Promise.reject({ message: e.message })
})
}
You can then decide that is your trigger for this function and execute it accordingly.
Note: Go to the IAM section of your project and find the App Engine service account, you will need to add the role Cloud Datastore Import Export Admin, otherwise, It will fail.
You can read more about it here It's very detailed.
Cheers.

Creating user with email and password in admin console results in anonymous user

I'm creating users using the admin SDK and I'm wanting them to be able to login with email and password. For some reason when I create users through the client using only email and password, the user can login using those credentials, but when I create a user using the admin SDK, the user is shown as anonymous in the auth dashboard, and the user can't login using their email and password. No errors are shown client side or Firebase side.
How can I create a Firebase user using the admin SDK and have that user linked to email authentication?
Node:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.https.onRequest(async (req, res) => {
//grab the email and password parameters
await admin.auth().createUser({
email: req.query.email,
password: req.query.password
})
//create the user
.then(function(userRecord) {
const child = userRecord.uid;
console.log('Successfully created new user:', userRecord.uid);
res.json({
status: 201,
data: {
"message": userRecord.uid
}
});
})
//handle errors
.catch(function(error) {
console.log();
res.json({
status: 500,
data: {
"error": 'error creating user: ', error
}
});
});
});
Swift:
func createChild(for parent: Parent,
with firstName: String,
lastName: String,
displayName: String?,
chores: [Chore]?,
username: String,
password: String,
completion: #escaping () -> Void = { }) {
let funcCallDict = [
"email": username,
"password": password
]
functions.httpsCallable(addChildIdentifier).call(funcCallDict) { (result, error) in
if let error = error {
NSLog("error: adding child with firebase function: \(error)")
completion()
return
}
}
completion()
}
Your function is an HTTP type trigger:
exports.createUser = functions.https.onRequest
But you're trying to invoke it as a callable type trigger:
functions.httpsCallable(addChildIdentifier).call(funcCallDict)
(Note that a callable trigger would be defined with onCall, not onRequest.)
As you can see from the documentation links, they are not the same thing. You are probably invoking the HTTP trigger, and it's not actually getting the arguments you expect from the client, since the protocol is different between them. Try logging req.query.email in the function to see what I mean.
You will have to either make your function a proper callable so it can be invoked from the client using the provided library, or change the way you invoke it on the client to use a regular http library instead of the Firebase library.

Firebase fcm sendMulticast Error: "Exactly one of topic, token or condition is required"

I am using version 7.2.0 of firebase admin to send fcm push notification, using sendMutlicast method:
async function sendPushRequest({tokens, title, body, customData}) => {
const message = {
notification: {
title,
body,
},
data: customData,
tokens,
}
return firebase.messaging().sendMulticast(message)
}
This is the error I am getting
Error: Exactly one of topic, token or condition is required
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
...
I tried logging the data and here is the object that sendPushRequest function is called with:
{
tokens: [ null, null, null, 'home-test', null, null ], // this one is a recent sample, I've been getting this error for a while now
title: 'some string',
body: 'some other string',
customData: {
title: 'some string',
body: 'some other string',
bigText: 'again another string',
color: '#9f0e27',
smallIcon: 'notificon',
sound: 'default'
}
}
I'm not sure what is causing the error!
I struggled with this problem too, its quite difficult to configure google admin firebase in nodejs. I find out there is a package that can handle this nicely.
https://www.npmjs.com/package/fcm-notification
but it has some little problem . you can not pass it multiple firebase configuration. here is some example :
const fcm = require('fcm-notification');
const fcm_key = require('../config/customer/fcm.json');
const FcM = new fcm(fcm_key);
module.exports.sendToSingleUser = async (message, token) => {
let message_body = {
notification: {
...message
},
token: token
};
FcM.send(message_body, function (err, response) {
if (err) {
return err
} else {
return response
}
})
}
Facing this error too. Figure out that our tokens array contains null or undefiend value. Resolved by remove that from tokens array and everything works fine.

Lambda function timing out after 10 seconds

Code:
const knex = require('knex')({
client: 'mysql',
connection: {
host: process.env.database_host,
user: process.env.database_user,
password: process.env.database_pass,
database: process.env.database_db,
charset: 'utf8'
}
});
const bcrypt = require('bcrypt');
const bookshelf = require('bookshelf')(knex);
const User = bookshelf.Model.extend({
tableName: 'users'
});
const checkValues = (values) => {
// todo: add data validation
return true;
};
exports.test = (database) => {
// todo: add tests
};
exports.handler = (event, context, callback) => {
let salt = bcrypt.genSaltSync();
let values = {
first_name: event.firstname,
last_name: event.lastname,
username: event.username,
date_of_birth: event.birthday,
password: bcrypt.hashSync(event.password, salt),
password_salt: salt
};
if (!checkValues(values)) {
callback(null, {
success: false,
error: {
id: 2,
details: 'data validation error'
}
});
context.done(null, "User not created");
return;
}
try {
new User({
'first_name': values.first_name,
'last_name': values.last_name,
'username': values.username,
'date_of_birth': values.date_of_birth,
'password': values.password,
'password_salt': values.password_salt
}).save();
callback(null, {
success: true
});
context.done(null, "User created");
} catch (err) {
console.log(err);
callback(null, {
success: false,
error: {
id: 1,
details: 'error inserting user into database'
}
});
context.done(null, "User not created");
}
};
I am trying to make a basic sign up api endpoint using AWS API Gateway and Lambda functions, however every time I post the information to the api gateway I get the error
{
"errorMessage": "2017-09-07T08:38:50.174Z f2368466-93a7-11e7-b4bc-01142a109ede Task timed out after 10.00 seconds"
}
I have tried using different database libraries but I seem to always be hitting the same problem. The database connection works I know this because the user does infact get added to the users table in the database and the password is successfully hashed..
I have also tried using asynchronous bcrypt but it doesn't make any difference to the result, it still does it but says it times out.
Lambda doesn't seem to be terminating properly, something keeps the process still running and I can't figure out what, any ideas?
i had the similar issue using API gateway invoking my lambda.
The default timeout for API gateway is 30 seconds. If your response is not ready within in 30 seconds, you will be timed out though your lambda would still run!
So may be try to get the response back within 30 seconds. If not have one lambda being invoked from the API and give the response back immediately and let the first lambda invoke your second lambda and that will run upto max time which is 5 mins.
Thanks

Send a notification by Lambda function with AWS Pinpoint

I have an iOS app with a backend build by MobileHub with API Gateway, Lambda, and DynamoDB.
I noticed that the SNS function of MobileHub has been replaced by Pinpoint, and I would like to create a notification system using this new service.
When a user creates a new post through API Gateway, a lambda function will be triggered and I suppose I can send a notification to the subscribers by Pinpoint.
But I cannot find any example or reference doc in the official website of Pinpoint.
Do you have any resource for this scenario or any idea? Thank you very much!
Depends what you mean by notification, I assume you would like to send a push notification to a particular user (Pinpoint endpoint).
Pinpoint stores each device associated with a user as an "endpoint", generally created by the AWS client side analytics library (e.g. amplify analytics).
The client
With the amplify analytics library, I call updateEndpoint so I can specify a userId that is available to Lambda, as well as the device token and remove optOut so user can receive the push notification:
Address - The token generated from user accepting push notification permission (iOS)
optOut - NONE so they can receive push notifications
userId - unique id for user (Cognito's sub)
Lambda (node.js)
Now you can send a push notification, using the userId and the Pinpoint SDK.
Example:
const sendMessagesParams = {
ApplicationId: process.env.PINPOINT_APP_ID,
SendUsersMessageRequest: {
Users: {
[receiverUserId]: {}
},
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
Title: 'Message received',
SilentPush: false,
Body: `You have a new message`
},
GCMMessage: {
Action: 'OPEN_APP',
Title: 'Message received',
SilentPush: false,
Body: `You have a new message`
}
}
}
};
console.log('sendMessagesParams', JSON.stringify(sendMessagesParams));
pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => console.log('push sent')
For your particular scenario, I set up a DynamoDB stream and trigger a Lambda when a record changes within the table. You may need to add the IAM permissions manually once the lambda is created.
Sources
Full list of pinpoint methods you can use in lambda (Node.JS)
Update endpoint using Amplify Analytics (JS)
Dynamodb streams - trigger a lambda
I was struggling a lot getting a lambda function working, so please see this answer as an addition to the answer from Dylan w.
Client
import PushNotification from '#aws-amplify/pushnotification';
import Analytics from '#aws-amplify/analytics';
PushNotification.onRegister((token) => {
Analytics.updateEndpoint({
address: token,
channelType: 'APNS',
optOut: 'NONE',
// Customized userId
userId: "e236e3ea-bas9-4eae-967e-0eb9bcaca26d" // Example
})
});
Lambda function
'use strict';
const AWS = require('aws-sdk');
exports.handler = async (event, context) => {
var pinpoint = new AWS.Pinpoint();
const sendMessagesParams = {
ApplicationId: <YOUR_APPLICATION_ID>, // Find it in Pinpoint->All projects
SendUsersMessageRequest: {
Users:{<USER_ID>:{}}, // The same userId as set on the client. This way you can "follow" people if they switch device
MessageConfiguration:{
APNSMessage:{
Action:"OPEN_APP",
Title:"Message received",
Body:"You have a new message"
}
}
}
};
return await new Promise( (resolve, reject) => {
pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => {
if(sendMessagesErr) reject(sendMessagesErr)
if(sendMessagesData) resolve(sendMessagesData)
});
});
};
Note that the call to pinpoint is wrapped in a promise. Because pinpoint.sendUserMessages accepts a callback, the execution is continued (async nature of Node), which will shut down the lambda function and you will get no output from the callback function or receive a notification, without awaiting the function to finish.
Finally, I've got something that perfectly works.
The answer is that you have to use "targetClient" to update endpoint inside "didRegisterForRemoteNotificationsWithDeviceToken" function.
let client = self.pinpoint!.targetingClient
let profile = client.currentEndpointProfile()
print("EndpointId = \(profile.endpointId)")
profile.user?.userId = <YOUR_CUSTOM_ID>
client.update(profile)
Client Side(XCODE)
Here is my [AppDelegate.swift] looks like:
(Important part is inside "didRegisterForRemoteNotificationsWithDeviceToken" function)
import UserNotifications
import AWSPinpoint
class AppDelegate: UIResponder, UIApplicationDelegate {
var pinpoint: AWSPinpoint?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Instantiate Pinpoint
let pinpointConfiguration = AWSPinpointConfiguration.defaultPinpointConfiguration(launchOptions: launchOptions)
// Set debug mode to use APNS sandbox, make sure to toggle for your production app
pinpointConfiguration.debug = true
self.pinpoint = AWSPinpoint(configuration: pinpointConfiguration)
// Present the user with a request to authorize push notifications
self.registerForPushNotifications()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
// Only get the notification settings if user has granted permissions
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
// Register with Apple Push Notification service
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("DidRegisterForRemoteNotificationsWithDeviceToken: Start")
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
// Register the device token with Pinpoint as the endpoint for this user
self.pinpoint!.notificationManager.interceptDidRegisterForRemoteNotifications(withDeviceToken: deviceToken)
//set custom userId and update endpoint
let client = self.pinpoint!.targetingClient
let profile = client.currentEndpointProfile()
print("EndpointId = \(profile.endpointId)")
profile.user?.userId = <YOUR_CUSTOM_ID>
client.update(profile)
}
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("didReceiveRemoteNotification:\(userInfo)")
// Pass this remote notification event to pinpoint SDK to keep track of notifications produced by AWS Pinpoint campaigns.
self.pinpoint!.notificationManager.interceptDidReceiveRemoteNotification(userInfo)
}
}
Backend (Lambda with Nodejs)
And here is a code to send a notification to specific user.
'use strict';
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({region: 'us-west-2'});
exports.handler = (event, context, callback) => {
const done = (err, data) => {
if(err){
console.log('ERROR:', err);
const response = {
statusCode: 400,
body: JSON.stringify(err)
};
callback(response);
}else{
console.log('SUCCESS:', data);
const response = {
statusCode: 200,
body: JSON.stringify(data)
};
callback(null, response);
}
};
let users = {};
users[<YOUR_CUSTOM_ID>] = {};
const params = {
ApplicationId: PINPOINT_PROJECT_ID,
SendUsersMessageRequest: {
Users: users,
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
Title: "Hi, I am AWS Pinpoint.",
SilentPush: false,
Body: "You've got a nice message."
}
}
}
};
pinpoint.sendUsersMessages(params, (err, data)=>{
if(err){
done(err);
}else{
done(null, data);
}
});
};
Hope those work for you too.
This is certainly possible with Amazon Pinpoint. You can find the Javascript SDK documentation here.
There are 2 modes of sending with Pinpoint.
Direct send - This is effectively the same as what SNS has traditionally been. You need a device token and you can send directly to your push provider using that token.
Segmentation sends - This mode is slightly different and assumes that you have loaded all your devices into Pinpoint via the Mobile SDK as part of your app, or via an S3 Import. The benefit here is that you can segment your devices and send to that segment (e.g. 'Ryans Friends').
So in your Lambda backed API you can choose to either send directly to subscribers (if you have their addresses) or potentially an entire segment of subscribers (if you have loaded your endpoints into Pinpoint).

Resources