Search code examples
typescriptfirebasegoogle-cloud-storage

Firebase Cloud Storage download failing with "no such object"


I'm trying to download a file from Firebase Storage from within a Firebase Cloud function with this typescript code:

exports.getProfileImage = functions.https.onRequest(async (req, resp) => {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    resp.status(401).send("Unauthorized");
    return;
  }
  let detail = "start";
  let decodedToken: DecodedIdToken;
  try {
    const idToken = authHeader.split("Bearer ")[1];
    try {
      decodedToken = await admin.auth().verifyIdToken(idToken);
    } catch (e) {
      resp.status(401).send("Unauthorized");
      return;
    }
    const match = RegExp("^\\/(\\w+)$").exec(req.path);
    const uid = match && match[1];
    if (!uid) {
      resp.status(400).send(`Invalid profile image request: ${req.path}`);
      return;
    }
    const filePath = `user/${uid}/profileImage`;
    detail = `locating file ${filePath}`;
    const ref = admin.storage().bucket().file(filePath);
    detail = "Checking file exists";
    if (!await ref.exists()) {
      resp.status(404).send(`Requested profile image not found: ${req.path}`);
      return;
    }
    detail = `Downloading profile image for ${decodedToken.email} ` +
      `from ${ref.cloudStorageURI.href}`;
    const [fileContent] = await ref.download();
    resp.type("jpeg");
    detail = "Sending result";
    resp.send(fileContent);
    return;
  } catch (e) {
    if (e instanceof Error) {
      resp.status(500).send(`Error while retrieving image: ${e.message}.` +
        `Occurred during ${detail}`);
    } else {
      resp.status(500).send("Unknown error retrieving image");
    }
  }
});

I don't understand why exists would apparently return true, but download would then fail with the message "no such object". An exception is thrown on the "download" line resulting in output like "Error while retrieving image: No such object [...]"

I get a similar error replacing the download code with createReadStream:

detail = `Downloading profile image for ${decodedToken.email} ` +
  `from ${ref.cloudStorageURI.href}`;
const fileReader = ref.createReadStream();
detail = "Setting type to jpeg";
resp.type("jpeg");
detail = "Piping file content to response";
fileReader.on("error", (err) => resp.status(500).send(err.message))
  .pipe(resp);
detail = "Sending result";
return;

Solution

  • The fact that the object was appearing as present, but not available when attempting to download may have been a clue that the issue was security related. I made a number of changes, but I think the key was when I changed my Firebase storage rules to allow read unconditionally. After this I am able to download, and I removed the code attempting to propagate the authorization token to firebase storage (keeping only the portion that validates its use within the Firebase function). Perhaps the tokens are not as portable as I suspected, but apparently the authorization token was not getting me the access I needed.

    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /user/{uid}/{allPaths=**} {
          allow read;
          allow write: if request.auth != null && request.auth.uid == uid;
        }
      }
    }
    

    Even if this isn't a final solution, it's a step along the path to identifying the problem.