Even if the user is logged in, I get an error - javascript

I have a problem:
I want a logged in user to be able to use my getRealtimeUsers function. But I get the following error: FirebaseError: Missing or insufficient permissions.
Below you will find the code. Some explanations:
On the React side, I created a firebase, registered a getRealtimeUsers function there, and tried to use it.
firebase.js file
import firebase from "firebase/app";
import "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};
firebase.initializeApp(firebaseConfig);
export default firebase;
A function that uses the firebase I created:
import firebase from '../../firebase';
export const getRealtimeUsers = () => {
return async () => {
const db = firebase.firestore();
const unsubscribe = db.collection("users")
return unsubscribe;
}
}
Testing of the function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getRealtimeUsers } from '../redux/actions/chatActions';
import firebase from '../firebase';
let unsubscribe;
class chat extends Component {
componentDidMount() {
unsubscribe = this.props.getRealtimeUsers()
.then(unsubscribe => {
return unsubscribe;
}).catch(error => {
console.log(error);
});
}
componentWillUnmount() {
return () => {
unsubscribe.then(f => f()).catch(error => console.log(error));
}
}
render() {
const ref = firebase.firestore().collection("users");
return (
<div>
check if works
</div>
);
}
}
const mapStateToProps = (state) => ({
});
export default connect(
mapStateToProps,
{
getRealtimeUsers
}
);
Here I let the user do the login
login.js action
export const loginUser = (userData) => (dispatch) => {
axios
.post('/login', userData)
.then((res) => {
console.log(res)
setAuthorizationHeader(res.data.token);
})
.catch((err) => {
console.log(err)
});
};
The login is done on the nodejs side, which is also where I set it to firebase:
const config = {
apiKey: "WqcVU",
authDomain: "c468.firebaseapp.com",
projectId: "c468",
storageBucket: "c468.appspot.com",
messagingSenderId: "087",
appId: "1:087:web:c912",
measurementId: "G-SQX1"
};
;
const firebase = require("firebase");
firebase.initializeApp(config);
// Log user in
exports.login = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password,
};
firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(JSON.stringify(data));
return data.user.getIdToken();
})
.catch((err) => {
console.error(err);
});
};
app.post('/login', login);
the rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
I want only a logged in user to use the getRealtimeUsers function, but even if the user is logged in it doesnt work. That's my problem.
I do not know at all how to solve it

Your case is really problematic, the only way to do security in rules is to access your correntUser or auth. Because you connect through the nodejs, you only return json object and there will be no access to these objects.
For you to have access to these objects what you need to do is connect once more, in react itself, with the same firebase and that's how you enjoy all the worlds that firebase has to offer.
When you log in again, the user does not need to know about it, since when you log in you will reset the password, and choose a random password, it will solve all your problems.
I think this is the most likely solution, but it is not for production
Please update me.

Is there a reason why you're not authenticating directly from the client? The thing is you have to pass in some way the authenticated state from the server to the front...
You have signed in with firebase on your server but you did not sign in with firebase on the front end. Or maybe you did in setAuthorizationHeader(res.data.token); but I'd be curious to know how you did that. It would have been interesting to be able to authenticate with nodejs and then send the tokenId which has been generated, that is data.user.getIdToken(), to the client which the client would then pass to firebase.auth().signInWithToken(token) but this is not possible. If you want to know more about it, I invite you to read this issue (a feature request).
The way to solve this problem is to use Admin SDK and create a custom token.
Firebase gives you complete control over authentication by allowing you to authenticate users or devices using secure JSON Web Tokens (JWTs). You generate these tokens on your server, pass them back to a client device, and then use them to authenticate via the signInWithCustomToken() method.
To achieve this, you must create a server endpoint that accepts sign-in credentials—such as a username and password—and, if the credentials are valid, returns a custom JWT. The custom JWT returned from your server can then be used by a client device to authenticate with Firebase (iOS, Android, web). Once authenticated, this identity will be used when accessing other Firebase services, such as the Firebase Realtime Database and Cloud Storage. Furthermore, the contents of the JWT will be available in the auth object in your Realtime Database Rules and the request.auth object in your Cloud Storage Security Rules.
Here are the steps:
First, you will add the SDK to your server:
yarn add firebase-admin --save
and import it
const admin = require("firebase-admin")
Go over to your Project settings under the "Service accounts" tab.
and click on "Generate a new private key"
a file has been downloaded and saved on your machine. From there, you will either import the file and initialize admin like this:
var serviceAccount = require("/path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
or better yet, in the console
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
and in your file
const credential = admin.credential.applicationDefault() // this will automatically find your env variable
admin.initializeApp({
credential
})
Note: don't forget to set the proper permissions to the private key file with chmod (I had to).
nodejs
firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
admin
.auth()
.createCustomToken(data.user.uid)
.then((customToken) => {
console.log('customToken', customToken)
// send token back to client here
res.send(customToken)
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
})
.catch((err) => {
console.error(err);
});
and then on the client:
// get the token and then
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
});
Another solution would be not using firebase on the client at all and use the backend as some sort of proxy between firebase and the client. The client would make a request to the backend which in turn would interrogate firebase.

in my case, faced same problem with rule's.
please give a try, change your rue and check whether its working or not

Related

How can I get user info from auth using NextJS api server side and Firebase?

I am doing a project using NextJS and Firebase.
I need to verify the user auth in the server side to be able to call the API from twitter in my project.
From now I added the firebase-admin.js
import * as admin from 'firebase-admin';
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
}),
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL
});
}
const firestore = admin.firestore();
const auth = admin.auth();
export { firestore, auth };
Then I tried in my API page to create a file to check if I am getting the info right from the user:
import { auth } from '../../utils/firebase-admin';
export default async (req, res) => {
if (!req.headers.token) {
return res.status(401).json({ error: 'Please include id token' });
}
try {
const idToken = req.headers.token.toString();
const decodedToken = await auth.verifyIdToken(idToken);
const uid = decodedToken.uid;
// You can check for the uid here and do any additional authorization checks.
// Then, you can proceed with your code to fetch the follower count
} catch (error) {
console.error(error);
res.status(401).json({ error: 'Unauthorized request' });
}
};
But I always get an error 401.
{"error":"Please include id token"}
GET http://localhost:3000/api/twitterfollowers 401 (Unauthorized)
Has anyone been able to solve this auth problem before using Firebase and NextJS?

TypeError: _firebaseConfig.default.database is not a function

I am trying to use firebase for my chat application developed using React Native and I am getting following error while pushing records into firebase.
[TypeError: _firebaseConfig.default.database is not a function. (In '_firebaseConfig.default.database()', '_firebaseConfig.default.database' is undefined)]
I have created a firebase configuration file as below.
firebase-config.js
import Firebase from 'firebase/compat/app';
const firebaseConfig = {
apiKey: '',
databaseURL: '',
projectId: '',
appId: '',
};
export default Firebase.initializeApp(firebaseConfig);
Messages.js
import Firebase from './firebase-config';
export const sendMessage = async (from, to, message) => {
try {
return await Firebase.database()
.ref('messages/' + from)
.child(to)
.set({
from: from,
to: to,
message: message,
});
} catch (error) {
console.log(error);
}
};
export const receiveMessage = async (from, to, message) => {
try {
return await getDatabase(Firebase)
.ref('messages/' + to)
.child(from)
.push({
from: from,
to: to,
message: message,
});
} catch (error) {
console.log(error);
}
};
You're not importing the Realtime Database SDK anywhere, so when you try to then access firebase.database() you get an error saying that it can't be found.
To fix this, import the Realtime Database SDK after you import Firebase from 'firebase/compat/app'.
import firebase from 'firebase/compat/app';
import 'firebase/database';
...
I recommend also checking out the example in the upgrade guide on updating imports to v9 compat.

Can't get data from firebase in react chart

I'm using ApexChartJs for my react project, but when I tried to fetch dynamic data from my firebase database, then show me undefined.
here is my part of code from my project:
import React, { useEffect, useState } from "react";
import Chart from "react-apexcharts";
import { auth, db } from "./firebase";
import { useCollection } from "react-firebase-hooks/firestore";
const [dataPoint, setDataPoint] = useState([]);
const query = db.collection("data");
const [snapshot, loading, error] = useCollection(query);
const [series, setSeries] = useState([]);
useEffect(() => {
setSeries([
{
name: "Desktops",
data: snapshot?.docs.map((doc) => doc.data().value),
},
]);
}, [snapshot]);
console.log(series);
data in the firebase look like:
firebase configure file:
import firebase from "firebase";
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyC2mfjPdIdXxGgJFqqVviw32xoaEMxxxxxx",
authDomain: "chart-app-4591b.firebaseapp.com",
projectId: "chart-app-4591b",
storageBucket: "chart-app-4591b.appspot.com",
messagingSenderId: "995691438244",
appId: "1:995691438244:web:6d945f72ff51d444664631",
measurementId: "G-2B9ZFG6E33",
};
const app = firebase.initializeApp(firebaseConfig);
const db = app.firestore();
const provider = new firebase.auth.GoogleAuthProvider();
const auth = firebase.auth;
export { db, provider, auth };
If your firebase app is well set-up and configured appropriately, the main issue that may lead an undefined query result is that the result are still being loaded.
To overcome this, you should update your series state whenever the loading property changes (along with query result snapshot changes):
const query = db.collection("data");
const [snapshot, loading, error] = useCollection(query);
useEffect(() => {
setSeries([
{
name: "Desktops",
data: snapshot?.docs.map((doc) => doc.data().value),
},
]);
}, [loading, snapshot]);
You should provide alternative error handling as well for state update.
Update (per the permission error)
A common error source would be the lack of privileges to read and write from the configured Firestore DB highlighted by below error message:
FirebaseError: Missing or insufficient permissions
To allow all reads and writes for test only mode (should never be the case for a production system), you can set below Security Rules for the database:
// Allow read/write access to all users under any conditions
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
You can read more on Cloud Firestore Security Rules in the official documentation.

How to connect an expo app to google firestore?

I am trying to authenticate a user and also organize the users in my database by their uid and then have the data field be by email. The users are successfully being authenticated however nothing appears in my firestore database.
My firestore security rules are in test mode so there should be no issue with permissions. In my action dispatch, I am successfully getting the user from firebase with their uid, access token, and everything else. I believe the error must be some way I am trying to connect to the firestore.
In my app.js file I initialize firebase like this-
import firebase from 'firebase';
import '#firebase/firestore';
componentDidMount() {
const firebaseConfig = {
apiKey: 'xxxxxxxx',
authDomain: 'xxxxxxxxx.firebaseapp.com',
databaseURL: 'xxxxxxxx.firebaseio.com',
projectId: 'xxxxxxxx',
storageBucket: 'xxxxxxx.appspot.com',
messagingSenderId: 'xxxxxxxxxxx',
appId: 'xxxxxxxxxxxxxxxxxxxx'
};
firebase.initializeApp(firebaseConfig);
}
Then this is how I am trying to make the call to put the user into the database-
firebase.auth().createUserWithEmailAndPassword(email, password)
.then((user) =>
dispatch({ type: USER_LOGIN_SUCCESS, payload: user });
const dbRoot = firebase.firestore();
dbRoot.collection('users').doc(user.user.uid).set({ email });
});
Also at the top of that file, I am importing as follows
import firebase from 'firebase';
import '#firebase/firestore';
Again, these users are being successfully authenticated but they are not appearing in the firestore database.
You have to add a collection to the database and add data.
const dbRoot = firebase.firestore().collection('users');
...
addData() {
dbRoot.add({
user: user.user.uid,
email : email,
complete: true,
});
OR
const dbRoot = firebase.firestore().collection('users');
...
const defaultDoc = {
email: email
};
async addData() {
await dbRoot.doc(user.user.uid).set(defaultDoc);
}

How to use Admin SDK with limited privileges on Firestore?

I have some trouble with Cloud function and firestore rules.
I would like use cloud function with limited privilèges on Firestore and give
only has access as defined in the Security Rules
It's working without problem on RTDB but not on Firestore.
I have try with this rules
service cloud.firestore {
match /databases/{database}/documents {
match /init/{ID=**} {
allow read, write: if true;
}
match /test/{ID=**} {
allow read, write: if false;
}
}
}
And this
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const FieldValue = require('firebase-admin').firestore.FieldValue;
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://******.firebaseio.com',
databaseAuthVariableOverride: {
uid: 'my-worker',
},
});
const db = admin.firestore();
exports.onTestRights = functions.firestore
.document('init/{initID}')
.onCreate((event) => {
const initID = event.params.initID;
return db.collection('test').doc(initID).set({'random key': 'random value'}).then(()=>{
console.log('working');
return;
}).catch((err) =>{
console.log('error: ', err);
return;
});
});
But it's still writing so whereas it should be "permission denied"
Anyone know if it's normal(or not yet implanted) on firestore or I have misunderstood something ?
Edit:
Of course my final goal is not with this rules, but only give write/read access on some documents/collections using (allow read, write: if request.auth.uid == 'my-worker';)
Edit2:
I would like use the security rules for checking like a transaction if no change during process using this model
As you've noticed databaseAuthVariableOverride only works for the Realtime Database. There is nothing right now that allows you to do the same for Firestore in the Admin SDK.
One workaround you could use if you want to limit the access rights on your server code is to use the Client JS SDK rather than Firebase Admin and sign the user-in using a custom token. Here is a sample code to do this:
// Configure Firebase Client SDK.
const firebase = require('firebase/app');
require('firebase/auth');
require('firebase/firestore');
firebase.initializeApp({
// ... Initialization settings for web apps. You get this from your Firebase console > Add Firebase to your web app
});
// Configure Firebase Admin SDK.
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
// Create Custom Auth token for 'my-worker'.
const firebaseReady = admin.auth().createCustomToken('my-worker').then(token => {
// Sign in the Client SDK as 'my-worker'
return firebase.auth().signInWithCustomToken(token).then(user => {
console.log('User now signed-in! uid:', user.uid);
return firebase.firestore();
});
});
// Now firebaseReady gives you a Promise that completes with a authorized firestore instance. Use it like this:
exports.onTestRights = functions.firestore
.document('init/{initID}')
.onCreate(event => {
const initID = event.params.initID;
return firebaseReady.then(db => db.collection('test').doc(initID).set({'random key': 'random value'}).then(() => {
console.log('working');
return;
}).catch((err) =>{
console.log('error: ', err);
return;
});
);
});

Categories