Firebase Functions : SignatureDoesNotMatch - node.js

I had an error in deploying Firebase functions after I changed my laptop and also transferred ownership of my Firebase account to another for a 3-month free Blaze plan. While deploying any functions, I am getting this error. All Firebase functions are successfully running locally.
Error code
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message> The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message>
<StringToSign GET 1670330017 /uploads-abc.zip</StringToSign></Error>
index.js
`
const functions = require("firebase-functions");
const admin = require("firebase-admin");
var handlebars = require("handlebars");
var fs = require("fs");
const nodemailer = require("nodemailer");
var serviceAccount = require("../service_account.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "gs://xyz.com",
databaseURL: "https://xyz",
});
transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "username",
pass: "password",
},
});
readHTMLFile = function (path, callback) {
fs.readFile(path, { encoding: "utf-8" }, function (err, html) {
if (err) {
callback(err);
throw err;
} else {
callback(null, html);
}
});
};
exports.sendWelcomeMail = functions.https.onCall(async (data, context) => {
// Grab the text parameter.
const email = data.email;
const name = data.name;
readHTMLFile(`./welcome_page.html`, function (err, html) {
var template = handlebars.compile(html);
var replacements = {
name: name,
};
var htmlToSend = template(replacements);
var mailOptions = {
from: "from-mail",
to: email,
subject: "Welcome to boom boom",
html: htmlToSend,
};
transporter.sendMail(mailOptions, (erro, info) => {
if (erro) {
console.log(erro.toString());
return erro.toString();
}
console.log("Sended");
return "Sended";
});
});
});
`
I had tried different service account private keys which we can get from firebase project settings, with that I had tried deploying functions from different account ex. owners account, other account with service admin access.

Please check that SHA keys (SHA-1 and SHA-256) in your Firebase project (Project Settings -> General -> Your Apps -> Select your android app -> SHA certificate fingerprints) are same as in Google Play Console (Setup -> App integrity -> App Signings -> App Signing Key Certificate).
Specially if you are using Google play signing to release your app.

try in your terminal :
login with your firebase account firebase login:ci you will be redirected to your browser, you have to login
come back to the terminal (now you are logged in) and type firebase projects:list to list all the projects that you can access with your logged in firebase account
choose the project you want to deploy your function to : firebase use
NB: you are assumed to have npm and firebase-tools already installed in your terminal

Related

Error trying to authenticate with FCM servers when sending message using Firebase Admin SDK in node.js

When I try to send to send an FCM notification to a topic I get the following error...
Error: An error occurred when trying to authenticate to the FCM
servers. Make sure the credential used to authenticate this SDK has
the proper permissions.
I am using a brand new generated service account key and pointing to it correctly. I have confirmed that the path to the key is correct. I have also enabled Cloud Messaging for the project.
const { messaging } = require('firebase-admin');
var admin = require('firebase-admin');
console.log(process.cwd());
async function run() {
try {
var serviceAccount = require("/Users/myUser/src/my_project/node_admin/my_project-5617a-firebase-adminsdk-lwpk6-5dad9000e0.json");
const topic = 'all';
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const payload = {
notification: {
title: "Test1234",
body: "body",
sound: 'test-sound.wav'
}
};
var options = {
priority: "high"
}
await admin.messaging().sendToTopic(topic , payload , options);
} catch (e) {
console.log(e);
}
}
run();
Hello i was copy code and run --> it working
so i think you can check path of file auth json
var serviceAccount = require("/Users/myUser/src/my_project/node_admin/my_project-5617a-firebase-adminsdk-lwpk6-5dad9000e0.json");
good luck!

Not Authorized To Access This Resource/API (GCP)

I set up a service account with domain-wide delegation and I passed the client email, private key, scopes, and a user email the JWT method to impersonate a G-Suite user. I then get specific user info from the Admin API and use it to create an email signature and push it to the Gmail API. It works great if the user email I pass to the JWT method is a super admin but if I try to pass any other user I get an error response, "Not Authorized to access this resource/api". Any ideas on how I can get it to work with a regular user account within my domain?
Here is the code. (genSignature.js)
const { google } = require('googleapis');
const privatekey = require('../private-key.json');
const scopes = [
'https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing',
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.user.readonly'
];
const auth = async (user) => {
try {
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
scopes,
user // User who will be impersonated using the JWT client.
);
await jwtClient.authorize();
return jwtClient;
} catch (err) {
console.log(err.message);
};
};
function genSig(e) {
auth(e).then((jwtClient) => {
// Authenticate with the gmail API.
const gmail = google.gmail({
version: 'v1',
auth: jwtClient
});
// Authenticate with the admin API.
const dir = google.admin({
version: 'directory_v1',
auth: jwtClient
});
// Get users contact and job data from the directory. This data will be used as variables in their email signature.
dir.users.get({ userKey: e }, (err, response) => {
if (err) {
console.log(err.message);
} else {
let phones = response.data.phones;
let workPhone = '';
if (phones) {
for (i = 0; i < phones.length; i++) {
if (phones[i].type == 'work') {
workPhone = phones[i].value;
};
};
};
function getUserData() {
let userData = {
name: response.data.name.fullName,
email: response.data.primaryEmail,
phone: workPhone,
avatar: response.data.thumbnailPhotoUrl,
department: response.data.organizations[0].department,
title: response.data.organizations[0].title
};
return userData;
};
let requestBody = {
signature: 'Test'
};
// Update the users email signature for their primary email.
gmail.users.settings.sendAs.update({ userId: e, sendAsEmail: e, requestBody }, (err, response) => {
if (err) {
console.log(err.message);
} else {
console.log(response.data);
};
});
};
});
});
}
module.exports = genSig;
(signatures.js)
const express = require('express');
const router = express.Router();
const genSig = require('../../functions/genSignature');
// Get webhooks from Google.
router.post('/', (req, res) => {
let email = req.body.email;
let emailStr = email.toString();
console.log(emailStr);
genSig(emailStr);
res.status(200).json({
"msg": "data recieved..."
});
});
module.exports = router;
(index.js)
const express = require('express');
const app = express();
app.use(express.json());
app.use('/email-signature', require('./routes/api/signatures'));
const PORT = process.env.PORT || 6000;
app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));
Here are some screenshots.
API configuration on G-Suite
Service Account Setup
Successful request vs unsuccessful request
You need to impersonate an admin:
Only accounts with User Management privileges (like a Super Admin, or a User Management Admin) can access Users: get. You have to take into account that this is part of Admin SDK, which is to be used by admin accounts.
You can also check this is not possible if you try calling this via Try this API on the reference docs (you'll get the same message: Not Authorized to access this resource/api).
It doesn't matter that you're using a Service Account with domain-wide authority: when the service account is impersonating another user, it can only access the resources this user can access.
Solution:
In this case, the impersonated account should have user management privileges if you want to retrieve user data from Admin SDK.
But since these privileges are not necessary for calling the Gmail API method, you could impersonate an admin account when calling Users: get, and a regular one when calling users.settings.sendAs.update.
Reference:
Admin privileges definitions
Pre-built administrator roles
this is not a new post. However, I faced it and found a solution.
You can use a service account by assigning a role. See "Assign a role to a service account" in Assign specific admin role. There are details in updates blog post.
At first, you need to create a custom admin role at Google Workspace Admin Console. And you can assign service accounts to the custom admin role with email address.
It worked on Google Cloud Functions in my environment.

app.auth(...).signInWithCredential is not a function firebase error

I'm trying to add google login to my node js firebase app. I'm getting the following error:
app.auth(...).signInWithCredential is not a function
Here's the code on the server
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
return app.auth().signInWithCredential(credential);
On the client
// We need to register an Observer on Firebase Auth to make sure auth is initialized.
var unsubscribe = app.auth().onAuthStateChanged((firebaseUser) => {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
firebase.auth().signInWithCredential(credential).catch( (error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
console.dir(errorMessage);
}).then( (success) => {
console.dir(success);
var id_token = googleUser.getAuthResponse().id_token;
axios.post('/auth/google-login', {
id_token: id_token
}).then(response => {
var result = response.data;
if (result.status === "ok") {
this.setState({complete: true});
} else if (result.error) {
this.setState({error: result.error});
}
}).catch(error => {
console.dir(error);
this.setState({error: "error"});
});
});
});
What I'm I doing wrong?
At the top of the nodejs file, I have
var app = require('./base');
where base :
var serviceAccount = require("./service-account-file.json");
const app = fadmin.initializeApp({
credential: fadmin.credential.cert(serviceAccount),
databaseURL: "https://test.firebaseio.com"
});
module.exports = app;
Hi there are hints that the problem starts with node version 6.21. that
app.auth().signInWithCredential(credential)
Don't work
It works in v6.2.0.
Try signInWithEmailAndPassword
Use latest version of node
The function signInWithCredential is part of the Firebase JavaScript SDK
The Firebase JavaScript SDK implements the client-side libraries used by applications using Firebase services.
The Firebase Admin SDK is intended to run on a server or other privileged environment. The Admin SDK does not include sign-in functions.

Google Suite - Google API access - Client is unauthorized to retrieve access tokens using this method

I am struggling for days with the set up in trying to access GMail Google API from a node.js script using googleapis lib. I succeeded once but I cannot remember how I did it , I tried to reset a project, service-account and G-Suite Domain wide delegation following the Google doc ..
Here is what I did :
In my GCP Console console,
1. Existing organisation : lechorodescharentes.org
2. In this organisation , I created a project : choro-dev
3. In this project I created a service account : choro-dev-postoffice
with choro-dev-postoffice with role TokenGenerator
and enabled the Google Apps Domain-wid Delegation
downloaded the new private key ( JSON file )
4. I enabled the GMail API ( from Libray menu)
In my G-Suite domain's admin console,
5. I added the following copes for this service account's ClientID
"https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.group"
Node.js client
I am trying to access the GMail API with the following Firebase function code using the node.js googleapis library
with server -server authentication using service account
see node.js client code
In this code, I have 2 authentication functions
connect() : to a JSON Web Token
authorize() : to request an access token from the Google OAuth 2.0 Authorization Server
Deployed the Firebase function
Run the function
Got the JWT client displayed
Function ended with error :
{"infos":"unauthorized_client: Client is unauthorized to retrieve access tokens using this method."}
node.js client code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const nodemailer = require('nodemailer')
const _ = require('lodash');
const KEY = require('./service-key.json');
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
_.values(KEY.scopes), // scopes as authorized in G-Suite admin
KEY.admin_email . // impersonated user
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient); // returns client
}
});
});
}
// Send a message to the contact user
function sendMessage (client, sender, msg) {
return new Promise((resolve, reject) => {
var transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: KEY.admin_email,
serviceClient: KEY.client_id,
privateKey: KEY.private_key,
accessToken: client.access_token,
refreshToken: client.refresh_token,
expires: client.expiry_date
}
});
const mailOptions = {
from: 'SITE CONTACT<' + sender + '>',
to: KEY.contact_email,
subject: 'Message',
text: 'From: ' + sender + '\n\n' + msg,
html: '<h1>Message</h1><p>From: ' + sender + '</p><p>' + msg + '</p>'
};
transporter.sendMail(mailOptions, (err, response) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
}
function newContactMessage (from, msg) {
return connect()
.then(client => {
return sendMessage(client, from, msg);
});
}
exports.sendContactMessage = functions.https.onRequest((req, res) => {
const sender_email = 'dufourisabelle#orange.fr';
const sender_msg = 'just a test message to contact the site owner.'
newContactMessage(sender_email, sender_msg).then(() => {
return {status: 200};
}, error => {
return {status: error.status, infos: error.message};
}).then(response => {
return res.send(response);
}).catch(console.error);
});
What could I add to it ? I'll try to re-initiate the all process and pray ... ??

NodeJS Example - Firebase Cloud Functions - Instantiate an Admin SDK Directory service object

Goal
Use googleapis with Firebase Cloud Functions to get a list of all users in my G Suite domain.
Question
How do I Instantiate an Admin SDK Directory service object. I do not see a NodeJS example, and I'm not clear how to setup and make the request with googleapis.
Context
This code runs from Firebase Cloud Functions, and it seems to authenticate okay. Now, how do I setup the service object at //TODO in the following code:
// Firebase Admin SDK
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
// Google APIs
const googleapis = require('googleapis')
const drive = googleapis.drive('v3')
const gsuiteAdmin = googleapis.admin('directory_v1')
// Service Account Key - JSON
let privatekey = require("./privatekey.json")
let jwtClient = new googleapis.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
['https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/admin.directory.user'])
// Firebase Cloud Functions - REST
exports.authorize = functions.https.onRequest((request, response) => {
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err)
return
} else {
console.log("Successfully connected!")
}
// TODO
// USE SERVICE OBJECT HERE??
// WHAT DOES IT LOOK LIKE?
response.send("Successfully connected!")
})
})
Order of Operations:
Create Service Account Credentials in Google Cloud Console
Add Domain-Wide Delegation to the Service Account
Authorize the API in G Suite - Security - Advanced
Go back to the Service Account and Download the .json key file
I downloaded the .json key file too soon, e.g., before authorizing the APIs in G Suite. The order, Setting up the Service Account with DwD and then authorization the API in G Suite API and then downloading the .json key file is important.
The Example
// Firebase Admin SDK
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
// Google APIs
const googleapis = require('googleapis')
const drive = googleapis.drive('v3')
const directory = googleapis.admin('directory_v1')
// Service Account Key - JSON
let privatekey = require("./privatekey.json")
let impersonator = 'example#example.com'
let jwtClient = new googleapis.auth.JWT(
privatekey.client_email,
null, // not using path option
privatekey.private_key,
['https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.user.readonly'],
impersonator
)
// Firebase Cloud Functions - REST
exports.getUsers = functions.https.onRequest((request, response) => {
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err)
return
} else {
console.log("Successfully connected!")
}
//Google Drive API
directory.users.list ({
auth: jwtClient,
domain: 'example.com',
maxResults: 10,
orderBy: 'email',
viewType: 'domain_public'
}, function(err, res) {
if (err) {
console.log('The API returned an error: ' + err)
return;
}
var users = res.users;
if (users.length == 0) {
console.log('No users in the domain.');
} else {
console.log('Users:');
for (var i = 0; i < users.length; i++) {
var user = users[i];
console.log('%s (%s)', user.primaryEmail, user.name.fullName)
}
response.send(users)
}
})
})
})
UPDATE
The example above is not secure. A Cloud Function, especially with G Suite Domain-wide Delegation, should not respond to http requests unless they come from your application. See in this example that the Cloud Function uses admin.auth().verifyIdToken(idToken)... to validate that the request is authenticated by Firebase.
If you don't properly handle your G Suite DwD Cloud Function, you risk exposing your G Suite API to the public.

Resources