Open bytewiz opened 8 months ago
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
This is the emulator code that's returning this particular error message: https://github.com/firebase/firebase-tools/blob/b7eea76c22816a0caf4e45e6bd0f072c066c5d44/src/emulator/storage/apis/firebase.ts#L111
Unfortunately, our admin credential vetting in the emulator is in a pretty poor state. We don't have full support of OAuth access tokens, which is what the admin SDK is sending along with its requests.
In short the validation flow for getDownloadUrl in the emulator is this:
Since there's no way to get the admin SDK to pass along the header "Authorization: Bearer owner", this will likely remain broken until we have a full OAuth validation, which we haven't needed until the introduction of the admin getDownloadURL
method.
See this comment at https://github.com/firebase/firebase-tools/blob/master/src/emulator/storage/rules/utils.ts#L81.
This is a true bug and we will get around to fixing this eventually but it's hard to say when the team will have the time to tackle this.
This is the emulator code that's returning this particular error message: https://github.com/firebase/firebase-tools/blob/b7eea76c22816a0caf4e45e6bd0f072c066c5d44/src/emulator/storage/apis/firebase.ts#L111
Unfortunately, our admin credential vetting in the emulator is in a pretty poor state. We don't have full support of OAuth access tokens, which is what the admin SDK is sending along with its requests.
In short the validation flow for getDownloadUrl in the emulator is this:
- Does the Authorization header value equal the string literal, "owner"?
- If not, validate security rules.
Since there's no way to get the admin SDK to pass along the header "Authorization: Bearer owner", this will likely remain broken until we have a full OAuth validation, which we haven't needed until the introduction of the admin
getDownloadURL
method.See this comment at https://github.com/firebase/firebase-tools/blob/master/src/emulator/storage/rules/utils.ts#L81.
This is a true bug and we will get around to fixing this eventually but it's hard to say when the team will have the time to tackle this.
@tonyjhuang thanks for taking the time to review it!
But can I rely on that it only persists for the emulator and not in the "real" / production environment when deploying the function? Would the simplest initializeApp();
work or does it require the credentials as of one of the other examples?
Appreciate the support on this!
Or is there any other workaround to get the download url using the firebase-admin sdk in case the above is not working?
So does it actually work in production?? @maneesht @tonyjhuang
No one here? 😅
Same issue with the Firebase emulator, even with custom rules. Only working after deploying to Firebase
"storage": {
"port": 9199,
"rules": "storage-emulator.rules"
},
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
I have the same issue here and am bypassing the getDownloadUrl in the emulator for now. @tonyjhuang Please notify us if this is fixed 👍
Here's my workaround:
Obtain a download token from the emulator's REST API and manually construct an emulator-compatible download URL.
import fetch from 'cross-fetch';
import { FullMetadata } from '@firebase/storage-types';
/**
* Asynchronously generates and returns the download URL for a file in a specified Firebase Storage emulator bucket.
* The generated URL can be used to download the file.
*
* @param {string} bucket - The name of the Firebase Storage emulator bucket.
* @param {string} filePath - The path to the file inside the bucket.
* @returns {Promise<string>} - A promise that resolves to the download URL as a string.
*/
export const getEmulatorDownloadURL = async (bucket: string, filePath: string) => {
// fetch a new download token
const tokenGenerationFetch = await fetch(
`http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}/v0/b/${bucket}/o/${encodeURIComponent(
filePath,
)}?create_token=true`,
{
method: 'POST',
headers: {
Authorization: 'Bearer owner',
},
},
);
const tokenGenerationResponse: FullMetadata & { downloadTokens: string } = await tokenGenerationFetch.json();
const downloadToken = tokenGenerationResponse.downloadTokens.split(',')[0];
// manually construct the emulator download url
return `http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}/v0/b/${bucket}/o/${encodeURIComponent(
filePath,
)}?alt=media&token=${downloadToken}`;
};
Is there any debug info we can provide to help make a fix? This is very unfortunate DX.
You can use this function to conditionally run getDownloadUrl()
or getEmulatorDownloadURL()
based on whether you are running using the firebase emulators or in production.
exports.getFileDownloadUrl = async (filePath) => {
// Use 'process.env.FUNCTIONS_EMULATOR === "true"' to check your environment.
// Make sure that "true" is surrounded by quotes because it is a string, not a boolean.
if (process.env.FUNCTIONS_EMULATOR === "true") {
// Running using emulators.
// You can find the bucket in the storage emulator suite.
// Your bucket name should look something like this: <gs://your-app-name.appspot.com/>.
return await getEmulatorDownloadURL(bucket, filePath);
} else {
// Running in production.
const fileRef = getStorage().bucket().file(filePath);
const fileUri = await getDownloadURL(fileRef);
return fileUri;
}
};
Here's my workaround: Obtain a download token from the emulator's REST API and manually construct an emulator-compatible download URL.
import fetch from 'cross-fetch'; import { FullMetadata } from '@firebase/storage-types'; /** * Asynchronously generates and returns the download URL for a file in a specified Firebase Storage emulator bucket. * The generated URL can be used to download the file. * * @param {string} bucket - The name of the Firebase Storage emulator bucket. * @param {string} filePath - The path to the file inside the bucket. * @returns {Promise<string>} - A promise that resolves to the download URL as a string. */ export const getEmulatorDownloadURL = async (bucket: string, filePath: string) => { // fetch a new download token const tokenGenerationFetch = await fetch( `http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}/v0/b/${bucket}/o/${encodeURIComponent( filePath, )}?create_token=true`, { method: 'POST', headers: { Authorization: 'Bearer owner', }, }, ); const tokenGenerationResponse: FullMetadata & { downloadTokens: string } = await tokenGenerationFetch.json(); const downloadToken = tokenGenerationResponse.downloadTokens.split(',')[0]; // manually construct the emulator download url return `http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}/v0/b/${bucket}/o/${encodeURIComponent( filePath, )}?alt=media&token=${downloadToken}`; };
Is there a way to set a custom storage.rules
just for the storage emulator? e.g.
{
...
"emulators": {
"storage": {
"port": 9199,
"rules": "storage.rules.emulator"
},
}
}
This could be an easy workaround to use open rules for local and then use the regular rules for deployment as a workaround for now.
@kdawgwilk I haven't tried myself, but you should be able to use a different Firebase config file (ala firebase --config firebase.emulator.json
) which references a different storage rules file
This solution actually works, is the only way I found out there thanks!
Btw (for my use case), I found that the download URL was accessible via .publicUrl()
const uploadRef = storage.bucket().file('assets/' + filename)
await uploadRef.save(buffer, {
metadata: { cacheControl: 'public,max-age=86400' },
public: true,
})
return uploadRef.publicUrl()
or via .metadata.mediaLink
const uploadRef = storage.bucket().file('assets/' + filename)
await uploadRef.save(file.buffer, {
metadata: { cacheControl: 'public,max-age=86400' },
public: true,
})
const [metadata] = await uploadRef.getMetadata()
return metadata.mediaLink
Describe your environment
Describe the problem:
I have now tried for a very long time to follow these docs in order to get
getDownloadURL
to work. https://firebase.google.com/docs/storage/admin/start#use_a_default_bucket https://firebase.google.com/docs/storage/admin/start#shareable_urlsRegardless of how I initialize my app, when trying to use
getDownloadURL
I getError: Permission denied. No READ permission.
Here is how different ways I tried initializing:
Furthermore, I have tried adding IAM roles to the service account:![Screenshot 2023-10-19 at 16 54 45](https://github.com/firebase/firebase-admin-node/assets/16811871/8da9e338-4d60-4744-adf7-8ae933bc2f58)
What I am trying to accomplish is simply what is done in the before-mentioned docs:
What is going wrong here, as the docs states clearly I firebase admin sdk should have access by default?
Stacktrace: (from emulator)
My service account file: