Store references by name (users/1/profile.png). Then the URL needs to be generated all the time.
const url = await firebase
.storage()
.ref('users/1/profile.png')
.getDownloadURL()
Store references by URL. The access token could be revoked which would cause issues trying to generate a new one and update it in the database.
const url = await firebase
.storage()
.refFromURL(invalidURL)
.getDownloadURL()
Related to #1. Store by file name only so files can be moved without having to update database references.
const url = await firebase
.storage()
.ref(`users/${user.id}/${user.image}`)
.getDownloadURL()
The download URL and the reference path are two different things and I'd store each of them as appropriate (and sometimes both).
Store the Download URL when you want to directly serve the file from storage (e.g. an <img> tag).
Store the Reference Path when you need to keep a reference to the file to modify it later.
Calling getDownloadURL() does trigger a network request and so it's advisable to cache the result when possible to avoid unnecessary extra work/latency.
Related
If I have a reference for Firebase Storage saved as String, like :
String reference = https://firebasestorage.googleapis.com/v0/b/something/o/bucket%2Fp%2FprofilePicture%2Fimage_picker_26.jpg?alt=media&token=something
And this reference points to a picture, how can I get this image from Storage and show it to the user?
I was thinking something like :
Image.network(reference)
But I read on the internet that this isn't safe and I think that this isn't so easy...I should have something like : FirebaseStorage.instance.refFromURL(url) before calls any widget to show it.
Do you have any suggestion?
I understand that the problem is that the URL returned by the getDownloadURL() method is a long lived one. This method does not offer the possibility to configure an expiration date to the download URL. It is the same with the JS Client SDK.
However, it is possible with the Admin SDK, see for example the getSignedUrl() method of the Node.js Admin SDK.
So you could create a Callable Cloud Function, that you call by passing the file reference and which returns a signedURL with an expiration date you choose.
From your Flutter app, you would call this Cloud Function as explained here.
If you want to get the url from the storage after upload then do:
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child("folder");
UploadTask uploadTask = ref.putFile(image);
uploadTask.then((res) {
res.ref.getDownloadURL();
});
To display it you can use Image.network()
I have a firebase function to upload files to firebase storage, after upload I have to return the url (as Reset response) so that user can view the file
const bucket = admin.storage().bucket();
const [file, meta] = await bucket.upload(tempLocalFile, {
destination: uploadPath,
resumable: false,
public: true,
});
I have two options
1- const signedUrl = await file.getSignedUrl({ action: 'read', expires: '03-09-2491' });
2- meta.mediaLink
SignedUrl will be like https://storage.googleapis.com/web-scanner-dev.appspot.com/pwc%2Fwww.x.com%2F2019-11-17%2Fdesktop%2Fscreenshot-2019-11-17-1125.png?GoogleAccessId=firebase-gcloud%40scanner-dev.iam.gserviceaccount.com&Expires=16447035600&Signature=w49DJpGU9%2BnT7nlpCiJRgfAc98x4i2I%2FiP5UjQipZQGweXmTCl9n%2FnGWmPivkYHJNvkC7Ilgxfxc558%2F%2BuWWJ2pflsDY9HJ%2Bnm6TbwCrsmoVH56nuGZHJ7ggp9c3jSiGmQj3lOvxXfwMHXcWBtvcBaVj%2BH2H8uhxOtJoJOXj%2BOq3EC7XH8hamLY8dUbUkTRtaWPB9mlLUZ78soZ1mwI%2FY8DqLFwb75iob4zwwnDZe16yNnr4nApMDS7BYPxh4cAPSiokq30hPR8RUSNTn2GxpRom5ZiiI8dV4w%2BxYZ0DvdJxn%2FW83kqnjx6RSdZ%2B9S3P9yuND3qieAQ%3D%3D
and mediaLink will be like https://storage.googleapis.com/download/storage/v1/b/web-scanner-dev.appspot.com/o/pwc%2Fwww.x.com%2F2019-11-17%2Fdesktop%2Fscreenshot-2019-11-17-1125.png?generation=1574007908157173&alt=media
What is the pros and cons of each?
The mediaLink does not convey any access permissions on its own -- thus, the object itself will need to be publicly readable in order for end uers to make use of the link (or you will need to be authenticated as an account with read access to that bucket when you execute the link).
On the other hand, a URL returned by getSignedUrl will have a signature that allows access for as long as the URL hasn't expired. Thus, the link alone is sufficient (if temporary) permission to access the blob. Additionally, the URL that is generated maintains the permissions of the user who created it -- if that user loses access to the blob before the link would otherwise expire, the link will no longer function.
I want to get a downloadURL from a file inside my storage from within the onFinalize trigger. In a best case scenario, I want an URL as short as possible (so preferably not a signed one, but just one like the public one like it can be seen in the Firebase Storage UI). Keep in mind, that I am moving the file first, so I cannot access it directly from the onFinalize parameter.
I currently have the following solution:
await imageRef.move(newPath);
const newFile = defaultBucket.file(newPath);
const url = (await newFile.getSignedUrl({
action: 'read',
expires: '03-09-2491'
}))[0];
This approach has two flaws:
Apparently the signed URL is only valid for 3 days. This may be a known issue
The URL is very long and takes much space in my Firestore
I also saw an approach, where the URL is being reproduced from the bucket name and the token, but I did not manage to find the token in the metadata of the file.
Example:
upload file to server and save resulting path to the database, only authenticated users should be able to upload files
How to implement this?
to summarize we have 3 ways:
client uploads to s3 (or similar service), get's file url, then makes insert/update mutation to the right table
custom uploader - write application/server that uploads files and mutates db and use nginx routing to redirect some requests to it
custom resolver using schema stitching (example)
If you are uploading files to AWS S3, there is a simple way that you don't have to launch another server to process file upload or create a handler for hasura action.
Basically, when you upload files to S3, it's better to get signed url from backend and upload to s3 directly. BTW, for multiple image sizes hosting, this approach is easy and painless.
The critical point is how to get s3 signed url to upload.
In node.js, you can do
const AWS = require("aws-sdk");
const s3 = new AWS.S3({ apiVersion: "2006-03-01" });
const signedUrl = s3.getSignedUrl("putObject", {
Bucket: "my-bucket",
Key: "path/to/file.jpg",
Expires: 600,
});
console.log("signedUrl", signedUrl);
A signedUrl example is like https://my-bucket.s3.amazonaws.com/path/to/file.jpg?AWSAccessKeyId=AKISE362FGWH263SG&Expires=1621134177&Signature=oa%2FeRF36DSfgYwFdC%2BRVrs3sAnGA%3D.
Normally, you will put the above code to a handler hosted in AWS Lambda or glitch, and add some logic for authorization and even add a row to table.
You can see that the most important part is Signature=oa%2FeRF36DSfgYwFdC%2BRVrs3sAnGA%3D. How can we make it easier to get Signature?
After digging into AWS JS SDK, we can find signature is computed here.
return util.crypto.lib.createHmac(fn, key).update(string).digest(digest);
fn = 'sha1'
string = 'PUT\n\n\n1621135558\b/my-bucket/path/to/file.jpg'
digest = 'base64'
It's just sha1 a certain format of string. This means we can just use hasura computed fields and Postgres crypto function to achieve the same results.
So if you have a table "files"
CREATE TABLE files (
id SERIAL,
created_at timestamps,
filename text,
user_id integer
);
you can create a SQL function
CREATE OR REPLACE FUNCTION public.file_signed_url(file_row files)
RETURNS text
LANGUAGE sql
STABLE
AS $function$
SELECT ENCODE( HMAC(
'PUT' ||E'\n'||E'\n'||E'\n'||
(cast(extract(epoch from file_row.created_at) as integer) + 600)
||E'\n'|| '/my-bucket/' || file_row.filename
, 'AWS_SECRET', 'SHA1'), 'BASE64')
$function$
Finally, follow this to expose this computed field to Hasura.
This way allows you to be able to not add any backend stuff and handle permission all in Hasura.
When I work with Firebase Storage I use downloadURL file metadata to access the file. But the documentation says it's downloadURLs. Is that a typo?
It says that downloadURLs is an Array of strings.
From experience, Firebase lets you create new Download Urls so that single files can have multiple download URLs.
It is mentioned on the documentation that the downloadURLs variable is:
An array of long-lived download URLs. Always contains at least one
URL.
downloadURL is a different variable. If you get the downloadURL programmatically using getDownloadUrl(), it will generate a new url that will be added to the downloadURLs array. But if you get it from the metadata, it will give you an URL that already exists on that array.