I want to display an image saved in google storage with a URL or PATH saved in my database.
I create a file in my storage bucket with a cloud function
bucket.upload(thumbPath, {
destination: join(bucketDir, thumbName),
});
then i create a singnedUrl
const config = {
action: 'read',
expires: '03-01-2500',
};
const result = await thumbFile.getSignedUrl(config);
and save it in my database
I want to be able to always load the image with the saved URL.
This works for about a week. But then I get 403 errors.
My storage Rules are:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
Is there another way without using signed URLs because I think this is the source of the error
Just a note, if your signed URLs are expiring after rhoughly 7 days, it may be because you are using the V4 signing API (or the method getSignedUrl() is calling this API). From the documentation:
X-Goog-Expires: The length of time the signed URL remained valid,
measured in seconds from the value in X-Goog-Date. In this example the
Signed URL expires in 15 minutes. The longest expiration value is
604800 seconds (7days).
On the other hand, V2 signing (you can check this documentation) has no restrictions on the expiration date of the Signed URLS that generates.
I'm not sure which is the library that you are using to invoke the getSignedUrl() function, but I suspect that it was updated and changed the signed method from V2 to V4, and now there is the limit of 7 days for the generated Signed URLs. If you want to keep using them, maybe rolling back to a previous version of the library could help, or you could directly call the V2 API to generate the urls.
Anyhow, if you would like an alternative method, probably the most straight forward one is to just make the files in your bucket public, see documentation here. You could even make the whole bucket public so all of its contents are freely readable by users.
Related
I upload all my users profile photos in this path:
const userUploadRef = ref(
storage,
`profilePhotos/${user.data?.uid}`
);
I currently access the url like this:
getDownloadURL(uploadTask.snapshot.ref).then(async (downloadUrl) => {
if (user.data?.uid) {
setDownloadURL(downloadUrl);
However, I just realized that even though the path is always going to be the same, each picture has a different url?
This causes massive issues - if I want to attach this user photo to something like comments, posts or any other content, I will have to make sure that the user profile image url is updated.
Surely there must be a way that I can reuse the same url since the path to the user profile photo is always going to be the same regardless?
Edit:
I have tried following this guide - https://www.sentinelstand.com/article/guide-to-firebase-storage-download-urls-tokens on how to remove the token and make my file public, but firebase keeps adding the token regardless.
if (folder == "profilePhotos" && isThumbnail) {
const [newFile]: any = file.makePublic();
const [metadata] = newFile.getMetadata();
const url = metadata.mediaLink;
const userPublicRef = db.collection("usersPublic").doc(userId);
const usersPrivateRef = db.collection("users").doc(userId);
If the photos are public, once you have uploaded them to Firebase storage, just do not pass the download token and that will achieve what you want (this is not a Firebase supported use).
The download of the URL will have the following format firebasestorage.googleapis.com/XXX?alt=media&token=YYY and the part that's changing is the token generated, you can remove this if the security rules allow anonymous access.
Here is a good website that can help you.
1st answer edition:
These are the rules that I used in Firebase storage:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if false;
}
}
}
By using these rules, the token is no longer needed and can be accessed by anyone, but you still need to remove the token from the URL manually
For example, here is the complete URL with the token:
https://firebasestorage.googleapis.com/v0/b/my-firebase-application-92719.appspot.com/o/istockphoto-496152924-612x612.jpg?alt=media&token=960c993e-dc2b-483f-ae30-5c4eeb8c1e73
You’ll need to delete after &token and you’ll get:
https://firebasestorage.googleapis.com/v0/b/my-firebase-application-92719.appspot.com/o/istockphoto-496152924-612x612.jpg?alt=media
Now I can access the picture without the need of the token of the test picture I created:
https://firebasestorage.googleapis.com/v0/b/my-firebase-application-92719.appspot.com/o/istockphoto-496152924-612x612.jpg?alt=media
I would like to highlight that this is not a recommended security setting because now everything in the URL can be accessed by anyone.
This question already has an answer here:
Creating Firebase Storage Security Rules Based on Firebase Database Conditions [duplicate]
(1 answer)
Closed 1 year ago.
I want to allow read permission on a file in Firebase Storage only if the value of a certain node in the Firebase Realtime Database is true. Is it possible to do so? If yes, then how?
That is not possible at this time. You would have to use Cloud functions or your own servers using the Admin SDK to do that.
The Admin SDK essentially has full access to your Firebase project's resources so you can check if the value you are looking for in the Admin SDK exists. If yes, proceed with getting the signed URLs (or the data you are looking for from storage) else return an error.
A simple cloud function for that would be something like:
exports.getStorageData = functions.https.onCall((data, context) => {
const {uid} = context.auth;
const {fileName} = data;
if (!uid) return {error: "User not authorized!"}
// Read for data in realtime database
const dbRef = admin.database().ref("/path/to/data");
if ((await dbRef.once("value")).val()) {
return {error: "Required Value is not true"}
}
//Get signed URL or any data from storage
const storageRef = admin.storage().bucket()
//.....
return {data: "..."}
});
You need to use such secure env such as server or functions as only client side validation is not secure. But is the true value in your database something like a role or anything? You can try adding those in Custom Claims. I'm not totally sure about your use case but if it something like allowing access to specific folders or something then you can add a claim the_folder_id: true. This is not the best solution if a user can have access to a lot of folders. In that case you can assign groups as mentioned in the documentation. But satisfies your needs then you can try the following security rules along with this.
// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
allow read: if resource.metadata.owner == request.auth.token.groupId;
allow write: if request.auth.token.groupId == groupId;
}
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.
I'm sure I'm missing something wrt Firebase Storage rules, but I've done the following:
STEP 1
Firstly I set the following Firebase Storage rule:
service firebase.storage {
match /b/{bucket}/o {
match /items/{dev_key}/{perm_id}/{file_name} {
allow write: if request.auth.uid == dev_id;
allow read: if request.auth.token.permId == perm_id;
}
}
}
I expected only signed in users with a custom claim permId matching the relevant location to be able to download the file, allow read: if request.auth.token.permId == perm_id;.
So, I then set a custom claim in Cloud Functions on a user as follows:
STEP 2
admin.auth().setCustomUserClaims(uid, {permId: '1'}).then(() => {
// send off some triggers to let user know the download is coming
admin.database().ref(`collection/${uid}/${itemId}`).update({
downloadReady: true
});
});
Then I signed the user out and signed back in again... which set the custom claims.
I checked that they were set in Cloud Functions as follows:
STEP 3
admin.auth().verifyIdToken(idToken).then((claims) => {
console.log("--------------claims -------------");
console.log(JSON.stringify(claims));
});
And I saw in the claims string... permID: "1"
On the client side I then requested a downloadURL (here is hopefully where I'm going wrong)... I expected this to not be the public download url but rather the download url that the Firebase Storage security rules will check:
STEP 4
var pathReference = storage.ref('items/<some-key>/1/Item-1');
pathReference.getDownloadURL()
.then((url)=>{
console.log("url: ", url);
})
The url I received from this call gave me this link
https://firebasestorage.googleapis.com/v0/b/emiru84-games.appspot.com/o/games%2FcfaoVuEdJqOWDi9oeaLLphXl0E82%2F1%2FGame-1?alt=media&token=45653143-924a-4a7e-b51d-00774d8986a0
(a tiny little image I use for testing)
So far so good, the user with the correct claim was able to view this image
I then repeated step 2, logout/login again, except this time with a permId of "0". I expected the url generated previously to no longer work since my user no longer had the correct custom claim... and the bucket location was still at the same location (bucket/dev_key/1/filename) but it still worked.
If I repeated step 4 I got a new url, which then gave the appropriate 403 error response. However the old url still worked (I guess as long as the token parameter is tacked on). Is this expected, if so, I'm not sure I understand how the Storage security rules make a difference if the download url is public anyway?
Any help clearing my foggy brain would be appreciated.
The download URL in Cloud Storage for Firebase is always publicly readable. It is not affected by security rules.
If you don't want to allow public access to a file, you can revoke its download URL.