Firebase storage url, new file keep same access token - firebase

Duplicate of: Firebase storage URL keeps changing with new token
When a user uploads a profile pic I store this in firebase storage with the file name as the uid.
Lets say the user then goes and makes say 100 posts + 500 comments and then updates their profile image.
Currently I have a trigger which goes and updates the profile image url in all of the post and comment documents. The reason I have to do this is that when the image is changed in storage the access token is changed and this is part of the url so the old url no longer works.
What I want to do is not have the access token change. If I can do this I can avoid the mass updates that will massively increase my firestore writes.
Is there any way to do this? or an alternative?
Edit:
Another solution if you don't mind making the file public.
Add this storage rule and you won't have to use a token to access the file.
This will allow read access to "mydir" globally in any subfolder.
match /{path=**}/mydir/{doc} {
allow read: if true;
}

There are only two options here:
You store the profile image URL only once, probably in the user's profile document, and look it up every time it is needed. In return you only have to write it once.
You store the profile image URL for every post, in which case you only have to load the post documents and not the profile URL for each. In return you'll have to write the profile URL in each post document, and update it though.
For smaller networks the former is more common, since you're more likely to see multiple posts from the same user, so you amortizing the cost of the extra lookup over multiple posts.
The bigger the network of users, the more interesting the second approach becomes, as you'll care about read performance and simplicity more than the writes you're focusing on right now.
In the end, there's no singular right answer here though. You'll have to decide for yourself what performance and cost profile you want your app to have.

Answer provided by #Prodigy here: https://stackoverflow.com/a/64129850/10222449
I tried this and it works well.
This will save millions of writes.
var storage = firebase.storage();
var pathReference = storage.ref('users/' + userId + '/avatar.jpg');
pathReference.getDownloadURL().then(function (url) {
$("#large-avatar").attr('src', url);
}).catch(function (error) {
// Handle any errors
});

Related

How to remove user id on url drupal 9

http://localhost/drupal-9.3.3/user/372#myaccount
How to remove user id on URL . Please suggest how to change these type security isssue.
If you want to change the url structure to remove ids, use pathauto https://www.drupal.org/project/pathauto
You can choose to have an url structure for user profiles that can be generated from any user field.
Someone told me that everything you write on the front end or in your main folder is always just basically a suggestion a.k.a. not secure.
Good ways to secure site:
Authenticate users using backend like firebase read/write permissions
Use randomized urls / keys (which firebase also does)
Also catch exceptions and deal with them, perferably at the the place where that function is created, not when it is executed:
Example
yes:
function() => {
// do something
//catch error
}
function(); // call function;
No:
function().then(error => { //do something})

Updating all the references to an image url in Cloud Firestore when the image is updated using Flutter

In my Flutter app, I have a userData collection on Cloud Firestore where I store user's data including name, image url, etc.. The user can create posts, add comments to post, etc. similar to any other social apps out there and so I have multiple other collections where the user's info is stored including the link to their profile image.
Let's say if the user adds a comment to a post, I save their name, profile image url and comment text as a document inside "postComment" collection and then I display his/her profile image, name and the comment text on the screen by reading this collection and document.
Now, if the user updates their profile image or even their name which will be reflected in the userData collection, I need to make sure that their name and image url are updated in all other collections as well.
What's the easiest and least costly way to do that? Do I need to loop through all my collections and their documents and update the field values, or is there like a simple cloud function that can handle this?
Thanks!
I also store user profile images in Firestore Storage, BUT I use a very consistent schema to make the images easy to "guess":
When I have a document such as "/People/{userID}", and within the document is a field "image" which stores the URL to the image...
...then I store it in Firestore at the path "People/{userID/image/image.jpg" (eg). This way it is trivial to generate a StorageRef to it, and a downloadURL.
All the other uses of it always are to the now-standardized URL. Change the image in Storage; all references update.
For most "user" applications, the only use of the image is to feed it to a web-page, so just the URL is needed, and let the browser do the rest of the work.
As Fattie somewhat more aggressively stated, generally all you need is the URL. But following by itself that means you still would have to find all the references and update them if the user changes the URL. Saving a copy in Firestore Storage, and using that consistent URL, means all references will be "updated" just by changing what is stored at that location. Disadvantage is it will count as a storage read when fetched.
I'm finding duplicating data in NoSQL is great when it's fairly static - created once, and not dynamically changed (which is a LOT of cases). If your application doesn't fit that, it's better to store a reference to the source-of-truth, and incur the cost of the "lookup".
Here's a couple utilities I use to make this easier:
export const makeStorageRefFromRecord = (
record,
key = null,
filename = null
) => {
return FirebaseStorage.ref(
record.ref.path + (key ? "/" + key : "") + (filename ? "/" + filename : "")
);
};
export const makeFileURLFromRecord = (record, key = null, filename = null) => {
return FirebaseStorage.ref(
record.ref.path + (key ? "/" + key : "") + (filename ? "/" + filename : "")
).getDownloadURL();
};
("key" is essentially the fieldname)
remember the refpath is a string of the "/" separated collection/document path to the record, and is completely knowable in a simple situation, such as "People/{userID}". If you keep this internal, you can use "filename" as simple as "image.jpg" so it's always the same - it's unique, because of the path.
Do I need to loop through all my collections and their documents and update the field values
Minimally, yes, that's what you have to do.
or is there like a simple cloud function that can handle this?
You can certainly write your own Cloud Function to do this as well. There is not an existing function that will just do what you want - you have to code it.
Alternatively, you can just store the URL is one document, store the ID of that document in the other documents that need to refer to it, and have the client make an query for the single document with the URL you need.
There are multiple ways to do that.
The best way to do that is instead of storing the profile picture image again and again, you can store document references. If you are storing the images as base64, this would also save a lost of space and is cost efficient.
Another way of doing it is less efficient but you can store the image in firestore and refer it from there.
Both of these are from refereces
The last way of doing it and probably the most inefficient is by querying. You can go to that collection of post (Or if you store each post as a collection, loop through all of them) and then add a where filter and search for the imageURL or more safely a unique ID and then you can change them all one by one
These are the ways that I know

Request.auth.metadata in security rules?

I have a Firebase project where I'd like for users to be able to see when other users created their profiles. My initial hope was that I could use "user.metadata.creationTime" on the frontend to pass the date into the user's extra info document and verify that it is correct by having "request.resource.data.datecreated == request.auth.metadata.creationTime" as a Database Rule, but it looks like it is not possible according to the documentation.
Is there any way I can verify that the creation date is correct on the backend?
More info edit: Below is the code that is being triggered when a user creates a new account on my profile. The three values are displayed publicly. I'm creating a niche gear for sale page so being able to see when a user first created their account could be helpful when deciding if a seller is sketchy. I don't want someone to be able to make it seem like they have been around for longer than they have been.
db.collection('users').doc(user.uid).set({
username: "Username-156135",
bio: "Add a bio",
created: user.metadata.creationTime
});
Firestore rules:
match /users/{id} {
allow get;
allow create, update: if request.resource.data.username is string &&
request.resource.data.bio is string &&
request.resource.data.created == request.auth.metadata.creationTime;
}
user.metadata.creationTime, according to the API documentation is a string with no documented format. I suggest not using it. In fact, what you're trying to do seems impossible since that value isn't available in the API documentation for request.auth.
What I suggest you do instead is use a Firebase Auth onCreate trigger with Cloud Functions to automatically create that document with the current time as a proper timestamp. Then, in security rules, I wouldn't even give the user the ability to change that field, so you can be sure it was only ever set accurately by the trigger. You might be interested in this solution overall.

Firebase/Google Cloud Storage how to store the image

I have a Firestore that has a User Document. When a User uploads a profile image to the bucket I resize it in the Cloud Function and then save the smaller thumbnail of it. Now I am not sure what the best practice is to receive the Download URL for it, there are 2 possibilities:
In the Cloud Function get a Signed URL and store it in the Users Document or
Get the download url on the frontend with the .getDownloadUrl() method.
The problems I have with either solution is
1: The URL is really big, getting multiple Users on a Page this adds up in more size than the actual rest of the User Document
2: In terms of speed im not sure if its the best to loop through a list of Users to get each's thumbnail download URL, but the advantage is I do not have to deal with normalizing the new Profile Pic URL one every occurrence in the database.
The URL is not that that big. Before trying to optimize, first collect some clear benchmarks that suggest the size of the URL is seriously impacting the performance of the page. Don't optimize this if it doesn't need it. I've never heard of anyone complaining that a download URL is bad for performance.
You don't need to loop over all users. You should arrange to have the UID of the user available at the time of the resize, so you can update the correct user. You can put the UID in the path of the file upload and parse it out, or you can put the UID in object metadata at the time of the upload.
Either approach is valid, though. Pick the one that suits you the best. Generating it on the backend is probably more resilient to errors.
As Doug Stevenson mentioned in his answer, I also think you are trying to optimize something that's not even a performance or storage problem. However, if you still want to optimize the size of your URL, I have two solutions for you.
As we already know, the URL of a picture looks similar to this:
https://firebasestorage.googleapis.com/v0/b/project-id/o/images_folder%2vvTMvCsCRsckpR3R5Qg2s.jpg?alt=media&token=2277f575-8ff7-2211-8262-a28ef679d703
So the first solution would be to shorten the links using a service like tiny.cc. There are also other examples but I think you get the idea. So in case of the URL above, after you shorten it, will look like this:
http:// tiny.cc /2r4ucz
The second solution requires the saving two things in your database. It is not about shorten the link, it's about storing less data. So as you can see, the URL above is combined from a "BASE_URL":
https://firebasestorage.googleapis.com/v0/b/project-id/o/images_folder
Which includes the name of your project and the name of the folder where you store the images. It also contains the name of the image, which in my case is an id that is generated by Firestore and it's by definion unique. And the last part is the token id:
2277f575-8ff7-2211-8262-a28ef679d703
So the second solution would be to store in your database only the id of the image and the token and then reconstruct the entire URL client side. So in the example above the only things that you should store are:
Firestore-root
|
--- users (collection)
|
--- uid (document)
|
--- id: vvTMvCsCRsckpR3R5Qg2s
|
--- token: 2277f575-8ff7-2211-8262-a28ef679d703
|
--- //Other user details
If your user will always have a single picture then you can use instead of that random id, the uid that is coming from the authentication processs:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();

SetPermission With sensenet API - version 6.3

I am working on sensenet API. I faced an issue with setPermission on sensenetAPI security.
As per concern, when I create a document I would like to give See, open, Save and RunApplication permission as a default for newly created document to the user(User is taken from the function parameter).
To achieve this I use below code
public static void SetCollabUserSecurity(string myUserEmailId, Node myNodetToSetSecurity)
{
var domainName = "Builtin";
var strUsername = GetNameFromEmail(myUserEmailId);
User user;
using (new SystemAccount())
{
user = User.Load(domainName, strUsername);
if (user != null && user.Enabled)
{
var myUser = user;
myNodetToSetSecurity.Security.SetPermission(myUser, true, PermissionType.See,
PermissionValue.Allow);
myNodetToSetSecurity.Security.SetPermission(myUser, true, PermissionType.Open,
PermissionValue.Allow);
myNodetToSetSecurity.Security.SetPermission(myUser, true, PermissionType.Save,
PermissionValue.Allow);
myNodetToSetSecurity.Security.SetPermission(myUser, true, PermissionType.RunApplication,
PermissionValue.Allow);
}
}
}
While I am using this function, my process for creating document becomes time consuming. It takes around 40 second time for execution.
So in case of, if I would like to share the same newly created document with multiple users, lets say there are 3 user and I want to give the above permission to all of them then my single function call takes 120 second (2 minute) time to simply assign permission.
Is there any Odata REST API call available or any sensenet library call available through which I can assign...
1) multiple permission to multiple user for single document or
2) multiple permission to single user for single document
Can anyone help to come out from this issue?
Thanks!
C# api
On the server there is a c# api for managing permissions, please check this article for details. You may use the AclEditor class for setting multiple permissions in one round. Please note that you have to call the Apply method at the end to actually perform the operation.
// set permissions on folder1, folder2 and file1 for two users and a group
SecurityHandler.CreateAclEditor()
.Allow(folder1.Id, user1.Id, false, PermissionType.Open, PermissionType.Custom01)
.Allow(folder2.Id, user2.Id, false, PermissionType.Open)
.Allow(file1.Id, editorsGroup.Id, false, PermissionType.Save)
.Apply();
As a side note: in most cases it is better to work with groups than users when assigning permissions. So it is advisable to give permissions to a group and put users into the group as members instead of assigning permissions to users directly.
Also: it is easier to maintain a simpler security structure, for example if you assign a permission on the parent container (e.g. a folder) instead of on individual files. Of course if you have to set permission per file, then it is fine.
OData api
The same api is available from the client through the REST api. Please take a look at the SetPermissions action in this article or the similar api in the JavaScript client library of sensenet.

Resources