Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FirebaseError: Missing or insufficient permissions. #8201

Closed
realexp3rt opened this issue Apr 23, 2024 · 9 comments
Closed

FirebaseError: Missing or insufficient permissions. #8201

realexp3rt opened this issue Apr 23, 2024 · 9 comments

Comments

@realexp3rt
Copy link

Operating System

Linux

Browser Version

Firebase SDK Version

9.22.0

Firebase SDK Product:

Database, Firestore

Describe your project's tooling

Next.js app

Describe the problem

Lately i've been facing issues when I switched the project to another server. Sometimes when a user reads the database through this function:

import {firestore} from "./firebase";

async function readUser(uid) {
  try {
    const userDocRef = doc(firestore, "users", uid);

    const userDocSnap = await getDoc(userDocRef);

    if (userDocSnap.exists()) {
      const {sub} = userDocSnap.data();
      const {subDate} = userDocSnap.data();

      if (sub === "rec" || sub === "special") {
        const {subType} = userDocSnap.data();
        return [sub, subDate, subType, null, null];
      }

      if (sub === "pro") {
        const {pros} = userDocSnap.data();
        const {usage} = userDocSnap.data();
        return [sub, subDate, null, pros, usage];
      }

      if (sub === "basic") {
        return [sub, null, null, null, null];
      }
    }
    return null;
  } catch (e) {
    console.error(e);
    return null;
  }
}

I get these errors occasionally but not every time.

@firebase/firestore: Firestore (9.22.0): GrpcConnection RPC 'Listen' stream 0x367a9933 error. Code: 1 Message: 1 CANCELLED: Disconnecting idle stream. Timed out waiting for new targets.
[FirebaseError: Missing or insufficient permissions.] { code: 'permission-denied', customData: undefined, toString: [Function (anonymous)] }

Everything was working before i'm not sure what the issue is.

Here is my firebase init code:

import { initializeApp, getApps } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { config } from "dotenv";

config();

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
  databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
};


let firebaseApp;

if (getApps().length === 0) {
  firebaseApp = initializeApp(firebaseConfig);
} else {
  firebaseApp = getApps()[0];
}

const firestore = getFirestore(firebaseApp);
const auth = getAuth(firebaseApp);

export { firestore, auth, firebaseApp}

And here are my security rules

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Steps and code to reproduce issue

I'm not sure

@realexp3rt realexp3rt added new A new issue that hasn't be categoirzed as question, bug or feature request question labels Apr 23, 2024
@jbalidiong jbalidiong added api: firestore needs-attention and removed api: database new A new issue that hasn't be categoirzed as question, bug or feature request labels Apr 24, 2024
@jbalidiong
Copy link
Contributor

Hi @realexp3rt, thanks for the report. I tried replicating, but I wasn't able to reproduce the error. If I can replicate the issue, I can have a better look into it. Please share a minimal, but complete sample of a project that I can run locally.

@realexp3rt
Copy link
Author

I don't know but it seems like the issue occurs whenever someone signs in. The already signed in user will have this issue for a couple of seconds then it will go away after a couple re-logins.

Here is my /api/user.js which fetchs the user information from the database whenever the user refreshes the page:

import {parse} from "cookie";
import admin from "firebase-admin";
import { readUser }  from "../../services/read";
import {config} from "dotenv";
const serviceAccount = require("../../config/firebase.json");

config();


try {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://**.firebasedatabase.app",
  });
} catch (error) {
  if (!/already exists/u.test(error.message)) {
    console.error("Firebase admin initialization error", error.stack);
  }
}

function getMinutesLeftUntilExpiry(expirationTimestamp) {
  // Get the current UTC Unix timestamp in seconds
  const currentTimestamp = Math.floor(Date.now() / 1000);

  // Convert the expiration timestamp to minutes and subtract the current timestamp
  const minutesLeft = Math.floor((expirationTimestamp - currentTimestamp) / 60);

  return minutesLeft;
}

// Example usage:

export default async function handler(req, res) {

  try {
    const cookies = parse(req.headers.cookie || '');
    const accessToken = cookies.accessToken;
  
    const decodedToken = await admin.auth().verifyIdToken(accessToken);
    // const decodedToken = jwt.decode(accessToken);

    if (decodedToken) {
      const { exp } = decodedToken;
      const { user_id } = decodedToken;
      let user = await readUser(user_id)
      console.log(user);
      let currentTier = user.tier
      let pros;
      let usage;
      let currentSubDate;
      let currentSubType;

      if (currentTier === "pro")
      {
        pros = user.pros
        usage = user.usage
        decodedToken.pros = pros;
        decodedToken.usage = usage;
  
      }

      if (currentTier === "rec" || currentTier === "special") {
        currentSubDate = user.subDate;
        currentSubType = user.subType;
      }
      decodedToken.tier = currentTier;
  
      const remainingMinutes = getMinutesLeftUntilExpiry(exp);

      if (remainingMinutes < 1) {
        res.status(401).json({ error: 'Token has expired' });
        return;
      }
      decodedToken.accessToken = accessToken;
      res.status(200).json(decodedToken);
    } else {
      res.status(200).json(null);
    }
  } catch (error) {
    // console.error('Error retrieving user:', error);
    res.status(500).json({ error: 'Failed to retrieve user' });
  }
}

It's linked with an AuthContext to be synced all over the app.

And for /api/signin.js

export default async function handler(req, res) {

  if (req.method === "POST") {
    const { email, password } = req.body;
    try {
      const result = await signInWithEmailAndPassword(auth, email, password);

      let idToken = await result.user.getIdToken();

      res.setHeader('Set-Cookie', `accessToken=${idToken}; Path=/; Secure; SameSite=Lax`);


      res.status(200).json({ success: true });
    } catch (e) {
      console.error(e);
      res.status(500).json({ success: false, error: "Sign-in failed" });
    }
  } else {
    res.status(405).json({ success: false, error: "Method not allowed" });
  }
}

Notice that i'm taking the approach of server-side with sign in and database reads. I don't want anything exposed to the client.

@jbalidiong
Copy link
Contributor

Thanks for the additional code snippet, @realexp3rt. I just want to clarify my investigation regarding this issue since we have two error messages here: @firebase/firestore: Firestore (9.22.0): GrpcConnection RPC 'Listen' stream 0x367a9933 error. Code: 1 Message: 1 CANCELLED: Disconnecting idle stream. Timed out waiting for new targets. [FirebaseError: Missing or insufficient permissions.] { code: 'permission-denied', customData: undefined, toString: [Function (anonymous)] }

For the permission error, could you verify that the user is currently signed-in before performing the read/write in the database. There shouldn't be any issue if the user is signed-in before doing any activity on the database.

For the grpc issue, I noticed that there are similar reported connectivity issue in version 9.22.0. Could you try the recommended steps and additional info asked by one of our engineer on this similar issue:

Could you please try set FirestoreSettings.experimentalAutoDetectLongPolling to false to explicitly disable long polling?

Also it would be especially helpful if you could gather logs from https://debug-my.firebaseapp.com/ when you experience this issue. Thanks!

Lastly, there are a lot of fixes on Firebase Firestore, would you mind trying to the update the SDK version that you're using and see if the issue persists?

@realexp3rt
Copy link
Author

It looks like.. when a user signs in, every signed in user session changes to this new user thus rendering insufficient permissions since the uid won't match! how is this happening I don't understand! can you figure it out from these code snippets?

@realexp3rt
Copy link
Author

Seems like an unsynchronized auth users. I did await auth.currentUser.email; in my user.js and saw the output. Whenever a new user signs in the whole app auth changes to that user and to be honest.. i'm not sure why

@jbalidiong
Copy link
Contributor

Hi @realexp3rt, I used the given code snippet you've provided, unfortunately, I'm not encountering the error. I'm not totally clear how you are using the signInWithEmailAndPassword on the server side as it is not available in firebase-admin. It's possible that we're missing something from the given code snippet. I think the difference in our configuration setup may also be the reason why I'm unable to reproduce the behavior. That said, I believe in order to move this forward, a minimal reproducible example will allow us to look at this issue on every angle carefully, resulting to a more speedy resolution. By the way, could you try to update the SDK version that you're using and see if the issue persists? We usually advise this to our developers in order to avoid issues that may have been resolved in the most recent versions. Thanks!

@realexp3rt
Copy link
Author

Well I fixed it. TL;DR since i'm using server-side auth and auth isn't synced in client-side whenever a user signs in it changes the whole app currentUser to the last signed in. And since i'm using a request.auth.uid == userId; it wouldn't give them permession. So I deleted that and everything worked well. Since i'm verifying the idToken in the back-end everything should be good. Sorry for the trouble. But the timeout issue still exists. I updated my SDK but how exactly can I set experimentalAutoDetectLongPolling to false ? I couldn't figure it out with my setup. Please refrence my init setup above.

Thanks!

@jbalidiong
Copy link
Contributor

Glad to know that syncing authentication between client and server fixed the issue. To enable experimentalAutoDetectLongPolling, you have to use the initializeFirestore() rather than getFirestore() then set the setting as below:

const firestore = initializeFirestore(app, {
  experimentalAutoDetectLongPolling: false,
})

@realexp3rt
Copy link
Author

Thanks for your efforts. <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants