In a Firebase Cloud Function running Express, I am attempting to set custom user claims when a client posts a token to a setCustomClaims route. When I call admin.auth().setCustomUserClaims(uid, {admin: true}) within that route, I get an error saying this is "not a function."
My authentication provider is the email/password provider via Firebase authentication (i.e. I am not creating custom tokens).
Do I have to be creating custom tokens to set custom user claims?
Here is my cloud function code:
const functions = require('firebase-functions');
const admin = require("firebase-admin");
import express from "express"
admin.initializeApp(functions.config().firebase);
const app = express()
app.post('/setCustomClaims', (req, res) => {
uid = "some-uid"
admin.auth().setCustomUserClaims(uid, {admin:true}).then(()=> {
res.end(JSON.stringify( { status: 'success' } ) );
})
});
export let api = functions.https.onRequest((request, response) => {
if (!request.path) {
request.url = `/${request.url}` // prepend '/' to keep query params if any
}
return app(request, response)
})
npm install firebase-admin#latest --save
firebase-admin#5.4.3 work, good luck for fun app.
Note: client needs this code
// Force token refresh. The token claims will contain the additional claims.
firebase.auth().currentUser.getIdToken(true);
In the new SDKs, you no longer instantiate a database references via new Firebase. Instead, you will initialize the SDK via firebase.initializeApp():
BEFORE
var ref = new Firebase("https://databaseName.firebaseio.com");
AFTER
// See https://firebase.google.com/docs/web/setup#project_setup for how to
// auto-generate this config
var config = {
apiKey: "apiKey",
authDomain: "projectId.firebaseapp.com",
databaseURL: "https://databaseName.firebaseio.com"
};
firebase.initializeApp(config);
var rootRef = firebase.database().ref();>
I have found same issue on the stackoverflow, check this: firebase.database is not a function
Related
I am using ReactJS with Firebase to make functions. My motive is to create a user signup function. Following is the code for it.
app.post( '/signup', (req, res)=> {
const newUser = {
email : req.body.email,
password : req.body.password,
confirmPassword : req.body.confirmPassword,
handle : req.body.handle,
};
firebase.auth().createUserWithEmailAndPassword('newUser.email', 'newUser.password')
.then((data)=>{
return res.json({message: `user signed up successfully`});
} ).catch( (err) => { console.error(err); return res.json({error : err.code}) } );
} )
It also requires the use of Firebase initialization with proper credentials. The code i am including below :
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const app = require('express')();
const firebase = require('firebase');
const firebaseConfigg = {
apiKey: "A*************************s",
authDomain: "socialape-9ede9.firebaseapp.com",
databaseURL: "https://socialape-9ede9.firebaseio.com",
projectId: "socialape-9ede9",
storageBucket: "socialape-9ede9.appspot.com",
messagingSenderId: "105*****9789",
appId: "1:1054747689789:web:075f037f03b59627edfb54",
measurementId: "G-ZY1LNR052N"
};
admin.initializeApp();
firebase.initializeApp(firebaseConfigg);
The firebase functions log is showing this error when i try to run firebase deploy :
How to get through this error? I have been following this youtube tutorial link :Youtube social networking website tutorial with react and firebase and around 42.55min the guy uses firebase authentication, and copies a code in project settings, which seems to be different for me (obvioously i am not expecting the same api keys etc, but the format, it actually asks me to add a web app, but not in the tutorial) when i go through the exact same steps, snippet for me looks like the const firebaseconfig that i gave in the code snippet above.
My VS Code says:
Error: Functions did not deploy properly.
In a Cloud Function, you need to use the Admin SDK if you want to interact with one of the Firebase services (Auth, Firestore, Cloud storage, etc.).
So, you need to adapt you code as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const app = require('express')();
admin.initializeApp();
//...
app.post( '/signup', (req, res)=> {
const newUser = {
email : req.body.email,
password : req.body.password,
// see https://firebase.google.com/docs/reference/admin/node/admin.auth.UserRecord
};
admin.auth().createUser(newUser)
.then((data)=> {
return res.send({message: `user signed up successfully`});
})
.catch((err) => {
console.error(err);
return res.status(500).send({error : err.code}) });
});
See also https://firebase.google.com/docs/auth/admin/manage-users?authuser=0#create_a_user.
I am logging users in via their domain Google accounts using passport.js. This works great, but now I need to give this application access to a few Google API's (drive, sheets, etc).
When a user logs in, a message appears in the logs, that makes it seem like passport has all the required info:
info: [06/Jun/2019:21:24:37 +0000] "302 GET /auth/callback?code=** USER ACCESS TOKEN HERE **&scope=email%20profile%20https://www.googleapis.com/auth/drive.file%20https://www.googleapis.com/auth/spreadsheets%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/drive HTTP/1.1" [46]
This is achieved by passing the appended scopes via passport.authenticate(), which presents the user with the "Grant access to these things on your Google account to this app?" screen :
//Initial auth call to Google
router.get('/',
passport.authenticate('google', {
hd: 'edmonds.wednet.edu',
scope: [
'email',
'profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/spreadsheets'
],
prompt: 'select_account'
})
);
However, when I go and try to call an API with something like:
const {google} = require('googleapis');
const sheets = google.sheets({version: 'v4', auth});
router.post('/gsCreate', function(req,res,next){
sheets.spreadsheets.create({
// Details here.....
});
});
I get nothing but errors (the current one is debug: authClient.request is not a function)
My question is: Is it possible for me to use a setup like this, asking the user to log in and grant permissions once, and then somehow save that to their user session via passport?
I had the same question, but I was able to access Google Gmail API functionalities along with Passport.js user authentication by specifying 'scopes' using the following process.
First, create a file to setup the passport-google-strategy in nodejs as follows.
passport_setup.js
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20')
const fs = require("fs");
const path = require('path');
//make OAuth2 Credentials file using Google Developer console and download it(credentials.json)
//replace the 'web' using 'installed' in the file downloaded
var pathToJson = path.resolve(__dirname, './credentials.json');
const config = JSON.parse(fs.readFileSync(pathToJson));
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
const query = { _id: id }
Users.findOne(query, (err, user) => {
if (err) {
res.status(500).json(err);
} else {
done(null, user)
}
})
})
//create a google startergy including following details
passport.use(
new GoogleStrategy({
clientID: config.installed.client_id,
clientSecret: config.installed.client_secret,
callbackURL: config.installed.redirect_uris[0]
}, (accessToken, refreshToken,otherTokenDetails, user, done) => {
//in here you can access all token details to given API scope
//and i have created file from that details
let tokens = {
access_token: accessToken,
refresh_token: refreshToken,
scope: otherTokenDetails.scope,
token_type: otherTokenDetails.token_type,
expiry_date:otherTokenDetails.expires_in
}
let data = JSON.stringify(tokens);
fs.writeFileSync('./tokens.json', data);
//you will get a "user" object which will include the google id, name details,
//email etc, using that details you can do persist user data in your DB or can check
//whether the user already exists
//after persisting user data to a DB call done
//better to use your DB user objects in the done method
done(null, user)
})
)
Then create your index.js file in nodejs for API route management and to call send method of Gmail API.
Also, run the following command to install "google-apis"
npm install googleapis#39 --save
index.js
const express = require("express")
//import passport_setup.js
const passportSetup = require('./passport_setup')
const cookieSeesion = require('cookie-session');
const passport = require("passport");
//import google api
const { google } = require('googleapis');
//read credentials file you obtained from google developer console
const fs = require("fs");
const path = require('path');
var pathToJson_1 = path.resolve(__dirname, './credentials.json');
const credentials = JSON.parse(fs.readFileSync(pathToJson_1));
//get Express functionalities to app
const app = express();
// **Middleware Operations**//
//cookie encryption
app.use(cookieSeesion({
name:'Reserve It',
maxAge: 1*60*60*1000,
keys: ['ranmalc6h12o6dewage']
}))
//initialize passort session handling
app.use(passport.initialize())
app.use(passport.session())
app.use(express.json());
//**API urls**//
//route to authenticate users using google by calling google stratergy in passport_setup.js
//mention access levels of API you want in the scope
app.get("/google", passport.authenticate('google', {
scope: ['profile',
'email',
'https://mail.google.com/'
],
accessType: 'offline',
prompt: 'consent'
}))
//redirected route after obtaining 'code' from user authentication with API scopes
app.get("/google/redirect", passport.authenticate('google'), (req, res) => {
try {
//read token file you saved earlier in passport_setup.js
var pathToJson_2 = path.resolve(__dirname, './tokens.json');
//get tokens to details to object
const tokens = JSON.parse(fs.readFileSync(pathToJson_2));
//extract credential details
const { client_secret, client_id, redirect_uris } = credentials.installed
//make OAuth2 object
const oAuth2Client = new google.auth.OAuth2(client_id,
client_secret,
redirect_uris[0])
// set token details to OAuth2 object
oAuth2Client.setCredentials(tokens)
//create gmail object to call APIs
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
//call gmail APIs message send method
gmail.users.messages.send({
userId: 'me',//'me' indicate current logged in user id
resource: {
raw: //<email content>
}
}, (err, res) => {
if (err) {
console.log('The API returned an error: ' + err)
throw err
}
console.log('Email Status : ' + res.status)
console.log('Email Status Text : ' + res.statusText)
})
res.status(200).json({ status:true })
} catch (err) {
res.status(500).json(err)
}
})
app.listen(3000, () => { console.log('Server Satrted at port 3000') })
You can separate the routes in the index.js file to different files for clarity using express.Router()
If you want to call another Google API service just change this code segment and code below that;
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
gmail.users.messages.send(....Send Method internal implementation given above....)
For Google Drive:
const drive = google.drive({version: 'v3', auth: oAuth2Client});
drive.files.list(...Refer "Google Drive API" documentation for more details....)
I believe you can't use passport.js for three-legged oauth for APIs like Sheets or Drive.
Have a look at the Using OAuth for web servers documentation instead.
user835611 has the correct answer, as that page explains everything quite nicely. However, if you still need more, the below link really helped me to understand how this works.
https://github.com/googleapis/google-auth-library-nodejs#oauth2
I am building a public shopify app and I want to add a POST route that allows a metafield to be created.
In the shopify-api-node module the following is stated:
accessToken - Required for public apps - A string representing the permanent OAuth 2.0 access token. This option is mutually exclusive with the apiKey and password options. If you are looking for a premade solution to obtain an access token, take a look at the shopify-token module."
Here is the object that needs the shopName and accessToken
const shopify = new Shopify({
shopName: 'your-shop-name',
accessToken: 'your-oauth-token'
});
In the Shopify Node / Express documentation it has you add in /shopify/callback route qwhich includes the the Oauth:
// Shopify Callback Route //
app.get('/shopify/callback', (req, res) => {
const { shop, hmac, code, state } = req.query;
/// ... skipping over code ... ///
request.post(accessTokenRequestUrl, { json: accessTokenPayload })
.then((accessTokenResponse) => {
const accessToken = accessTokenResponse.access_token;
// DONE: Use access token to make API call to 'shop' endpoint
const shopRequestUrl = 'https://' + shop + '/admin/api/2019-04/shop.json';
const shopRequestHeaders = {
'X-Shopify-Access-Token': accessToken,
};
});
/// ... skipping over code ... ///
});
Instead of using the shopify-token module can I access/should I access this information from the /shopify/callback route in the following manner (see below)? Or is there a better way to do this / can you provide examples?
Server.js
// Declare new global variables //
var accessTokenExport;
var shopExport;
// New Function //
function exportTokens(accessToken) {
accessTokenExport = accessToken;
shopExport = shop;
}
// Shopify Callback Route //
app.get('/shopify/callback', (req, res) => {
// Export variables to New Function
exportTokens(shop, accessToken);
});
// New POST route //
app.post("/api/createMetafield", function (req, res) {
const shopify = new Shopify({
shopName: shopExport,
accessToken: accessTokenExport
});
shopify.metafield.create({
key: 'warehouse',
value: 25,
value_type: 'integer',
namespace: 'inventory',
owner_resource: 'metafield',
// owner_id: 632910392
}).then(
metafield => console.log(metafield),
err => console.error(err)
);
})
This is not the right way to use store access token
Because shopify/callback url call once only when store admin install your app but access token is useful for most of the time
To use store access token for your system you can do as below
shopify/callback API call when your app installing by shop admin that time you can store this access token in database and when it require simply getting from your db and this access token is accessible for life time till store admin not uninstall your app
I'm using Firebase to authenticate a user. I have setup Firebase like so:
firebase.js
const firebase = require("firebase-admin")
const serviceAccount = require("./serviceAccountKey.json")
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "https://logbook-96180.firebaseio.com/"
})
module.exports = firebase
I then import firebase in another file to authenticate a user.
auth.js
const firebase = require("./firebase")
...
const credential = firebase.auth.GoogleAuthProvider.credential(
null,
accessToken
)
firebase.auth().signInWithCredential(credential)
When I execute the authentication, I receive an error Cannot read property 'credential' of undefined, showing that firebase.auth.GoogleAuthProvider is undefined.
Are there any reasons as to why this could be the case? Thanks.
I'm trying to understand how to upload files in Firebase Storage, using Node.js. My first try was to use the Firebase library:
"use strict";
var firebase = require('firebase');
var config = {
apiKey: "AIz...kBY",
authDomain: "em....firebaseapp.com",
databaseURL: "https://em....firebaseio.com",
storageBucket: "em....appspot.com",
messagingSenderId: "95...6"
};
firebase.initializeApp(config);
// Error: firebase.storage is undefined, so not a function
var storageRef = firebase.storage().ref();
var uploadTask = storageRef.child('images/octofez.png').put(file);
// Register three observers:
// 1. 'state_changed' observer, called any time the state changes
// 2. Error observer, called on failure
// 3. Completion observer, called on successful completion
uploadTask.on('state_changed', function(snapshot){
...
}, function(error) {
console.error("Something nasty happened", error);
}, function() {
var downloadURL = uploadTask.snapshot.downloadURL;
console.log("Done. Enjoy.", downloadURL);
});
But it turns out that Firebase cannot upload files from the server side, as it clearly states in the docs:
Firebase Storage is not included in the server side Firebase npm module. Instead, you can use the gcloud Node.js client.
$ npm install --save gcloud
In your code, you can access your Storage bucket using:
var gcloud = require('gcloud')({ ... }); var gcs = gcloud.storage();
var bucket = gcs.bucket('<your-firebase-storage-bucket>');
Can we use gcloud without having an account on Google Cloud Platform? How?
If not, how come that uploading files to Firebase Storage from the client side is possible?
Can't we just create a library that makes the same requests from the server side?
How is Firebase Storage connected with Google Cloud Platform at all? Why Firebase allows us to upload images only from the client side?
My second try was to use the gcloud library, like mentioned in the docs:
var gcloud = require("gcloud");
// The following environment variables are set by app.yaml when running on GAE,
// but will need to be manually set when running locally.
// The storage client is used to communicate with Google Cloud Storage
var storage = gcloud.storage({
projectId: "em...",
keyFilename: 'auth.json'
});
storage.createBucket('octocats', function(err, bucket) {
// Error: 403, accountDisabled
// The account for the specified project has been disabled.
// Create a new blob in the bucket and upload the file data.
var blob = bucket.file("octofez.png");
var blobStream = blob.createWriteStream();
blobStream.on('error', function (err) {
console.error(err);
});
blobStream.on('finish', function () {
var publicUrl = `https://storage.googleapis.com/${bucket.name}/${blob.name}`;
console.log(publicUrl);
});
fs.createReadStream("octofez.png").pipe(blobStream);
});
When using the firebase library on a server you would typically authorize using a service account as this will give you admin access to the Realtime database for instance. You can use the same Service Account's credentials file to authorize gcloud.
By the way: A Firebase project is essentially also a Google Cloud Platform project, you can access your Firebase project on both https://console.firebase.google.com and https://console.cloud.google.com and https://console.developers.google.com
You can see your Project ID on the Firebase Console > Project Settings or in the Cloud Console Dashboard
When using the gcloud SDK make sure that you use the (already existing) same bucket that Firebase Storage is using. You can find the bucket name in the Firebase web config object or in the Firebase Storage tab. Basically your code should start like this:
var gcloud = require('gcloud');
var storage = gcloud.storage({
projectId: '<projectID>',
keyFilename: 'service-account-credentials.json'
});
var bucket = storage.bucket('<projectID>.appspot.com');
...
Firebase Storage is now supported by the admin SDK with NodeJS:
https://firebase.google.com/docs/reference/admin/node/admin.storage
// Get the Storage service for the default app
var defaultStorage = firebaseAdmin.storage();
var bucket = defaultStorage.bucket('bucketName');
...
Firebase Admin SDK allows you to directly access your Google Cloud Storage.
For more detail visit Introduction to the Admin Cloud Storage API
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "<BUCKET_NAME>.appspot.com"
});
var bucket = admin.storage().bucket();
bucket.upload('Local file to upload, e.g. ./local/path/to/file.txt')
I hope It will useful for you. I uploaded one file from locally and then I added access Token using UUID after that I uploaded into firebase storage.There after I am generating download url. If we hitting that generate url it will automatically downloaded a file.
const keyFilename="./xxxxx.json"; //replace this with api key file
const projectId = "xxxx" //replace with your project id
const bucketName = "xx.xx.appspot.com"; //Add your bucket name
var mime=require('mime-types');
const { Storage } = require('#google-cloud/storage');
const uuidv1 = require('uuid/v1');//this for unique id generation
const gcs = new Storage({
projectId: projectId,
keyFilename: './xxxx.json'
});
const bucket = gcs.bucket(bucketName);
const filePath = "./sample.odp";
const remotePath = "/test/sample.odp";
const fileMime = mime.lookup(filePath);
//we need to pass those parameters for this function
var upload = (filePath, remoteFile, fileMime) => {
let uuid = uuidv1();
return bucket.upload(filePath, {
destination: remoteFile,
uploadType: "media",
metadata: {
contentType: fileMime,
metadata: {
firebaseStorageDownloadTokens: uuid
}
}
})
.then((data) => {
let file = data[0];
return Promise.resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(file.name) + "?alt=media&token=" + uuid);
});
}
//This function is for generation download url
upload(filePath, remotePath, fileMime).then( downloadURL => {
console.log(downloadURL);
});
Note that gcloud is deprecated, use google-cloud instead.
You can find SERVICE_ACCOUNT_KEY_FILE_PATH at project settings->Service Accounts.
var storage = require('#google-cloud/storage');
var gcs = storage({
projectId: PROJECT_ID,
keyFilename: SERVICE_ACCOUNT_KEY_FILE_PATH
});
// Reference an existing bucket.
var bucket = gcs.bucket(PROJECT_ID + '.appspot.com');
...
Or you could simply polyfill XmlHttpRequest like so -
const XMLHttpRequest = require("xhr2");
global.XMLHttpRequest = XMLHttpRequest
and import
require('firebase/storage');
That's it. All firebase.storage() methods should now work.