Open jsakas opened 3 years ago
This issue does not seem to follow the issue template. Make sure you provide all the required information.
We currently only have minimal API support for the Cloud API interface, which sadly is completely different and substantially larger than Firebase's, so we're not aiming for 100% compatibility because of this.
Signed URLs are not currently planned to be supported, but we can leave this open for a bit and if you're interested in signed URLs just hit this message with an emoji reaction and we'll keep an eye on it and prioritize accordingly.
I was thinking... When using the emulator, we don't really need an actually signed url, just a url that works with the given request. For example, emulator auth jwt tokens don't have the signature after the payload, but it works.
So, in the storage emulator, perhaps the 'signed url' could simply be the object's url, plus a uuid token (similar to getDownloadUrl
), with the difference that, this uuid token is not long-lived and adheres to the permissions and settings provided while calling getSignedUrl()
So, while in production, getSignedUrl() uses the gcloud storage library, but if it detects the emulator - it just uses a stubbed method instead.
Imagine something like online education - course videos are uploaded and only people who have bought a course are able to view them.
getSignedUrl()
. First, we prevent all direct reads from firebase storage using storage rules. And in a cloud-function or app-engine, we could check firestore or wherever to see if our user has access to the course, and then make a signed url for the video being requested, if access is allowed, and send that off to the client so that they can stream it on their device. Thus, it's extremely scalable and secure. The only drawback? You will not be able to use storage emulator for this method, having to use production services instead. If there are other parts of your app which are more simple and straight while using storage, then using production cloud storage will also force you to use production auth service (emulator auth tokens won't work). And if you make a mistake, you can't "go back" to a previous state by restarting the emulator.In my experience, the same hurdle also exists for something like an e-commerce app where I want content to be moderated before being published. And I'm sure there will be others. Using getSignedUrl
simplifies a lot of things.
It's not like the current emulator doesn't work - it just messes with the dev experience for advanced cases involving storage.
I filed an internal issue b/197475725 to track this feature request. Please remember to add an emoji reaction to the post by @abeisgoat above if you are interested in this feature and we will prioritize accordingly.
Any updates on this?
Waiting...
Since it doesn't seem this will get attention soon, you can use the public url when the function is running in the emulator for now. Here's how to do that for a list of files:
const [images] = await bucket.getFiles({
prefix: `profile/${profile.id}`,
})
const urls = await Promise.all(
images.map(async (image) =>
process.env.FUNCTIONS_EMULATOR
? image.publicUrl()
: (
await image.getSignedUrl({
version: 'v4', // Allow to set long expire timestamps
action: 'read', // Read Only
expires: new Date().getTime() + 24 * 60000, // 24 hours from now
})
)[0]
)
)
@jsakas You are missing Authentication info. I think you should not use emulator for this case of use. You should process it directly inside Google Cloud Platform. It is such a waste of time to rely on emulator. I prefer using real-time updates from GCP itself, compared from this emulator.
I think it's important to have some sort of implementation for this method in the emulators suite. I prefer not to develop/test in production. In my view, the reason for the emulators is to enable implementing and testing features before making any changes to production configuration and to use the same code paths in dev, test, and prod, to the extent possible.
First off -- thank you so much to @nicolls1 for posting a workaround! Very helpful. I wasn't sure how to detect whether I was executing in an emulator environment or not, and this makes it crystal clear.
Second -- if the Firebase emulator is not planning on supporting this in the short term, would it be possible to change the error message to be more helpful, so that users don't have to spend too much time puzzling over what is wrong (like I did)? It would be great if the error message was something simple like "getSignedUrl() not fully supported in the emulated environment, see https://github.com/firebase/firebase-tools/issues/3400"
Following up on this. This is still a very much needed feature. Could we get a status update?
@taeold is implementing publicUrl
for write
in storage emulator still open for contribution?
I saw 2 open PRs regarding getSignedUrl
support for emulator, are they abandoned?
https://github.com/firebase/firebase-tools/pull/6142
https://github.com/firebase/firebase-tools/pull/6068
by the way, a temporary solution that works for me is to create a local-only firebase function to receive the upload request and do the uploading, so something like this.
Inside API to generate the pre-signed URL
const filename = randomUUID();
const ref = firebase.storage().bucket().file(filename);
const uri = ref.cloudStorageURI.href;
if (configs.USE_EMULATOR) {
return {
uri,
signedUrl:
"http://192.168.1.4:5001/demo/asia-southeast1/upload/" +
filename,
};
}
const [signedUrl] = await ref.getSignedUrl({
version: "v4",
action: "write",
expires: Date.now() + 60 * 10000,
contentType: "application/octet-stream",
});
return { uri, signedUrl };
Firebase function to intercept the upload request
export const upload = functions.https.onRequest(
{ omit: configs.NODE_ENV !== "development" },
async (req, res) => {
const filename = req.params[0];
const file = req.rawBody;
await firebase.storage().bucket().file(filename).save(file);
res.status(200).send({});
},
);
Environment info
Running inside firebase functions (also emulated)
Platform:
macOS Big Sur 11.3.1 (20E241)
Steps to reproduce
Inside any cloud function which processes files and needs to sign a URL:
Expected behavior
I receive a signed URL.
Actual behavior
I receive this error:
Alternatively, following the documentation that says a project ID is required in the
initializeApp
I receive a different error: