How can I setup rules for firebase realtime database to only allow the user to write only once to the database? [duplicate] - firebase-realtime-database

I'm struggling with understanding how I can allow users to create new records in the list, but only allow creators to update their own posts.
E.g. the following structure:
post {
post1: {
author: "user1"
text: "Some text"
}
post2: {
author: "user2"
text: "Some text 2"
}
}
Here, I want both users to be able to create new posts. But also protect, say, post2 from being edited by user1. Thus, only user1 can edit post1 and only user2 can edit post2.

You'd want to do something like this:
{"rules": {
"post": {
"$id": {
".write": "auth !== null && (!data.exists() || data.child('author').val() === auth.uid)"
}
}
}}
Here you're only allowing write if the user is logged in and a) the node attempting to be written is empty or b) the node attempting to be written was authored by the current user.

Related

firebase security-rules write access to only one specific field

is it possible to give update access to only one specific field ?
users
+ gaglajasdfer32fasfa
- Name: Luke // not allowed to update
- likes: 3 // allowed to update
You will have to specify the rule for each field in that case as shown below:
{
"rules": {
"users": {
"$uid": {
"Name": {
".write": "!data.exists()"
}
}
}
}
}
!data.exists() allows write operation only when it's absent now and hence allowing "Create" operations only and not "update" or "delete".
Checkout the documentation to learn more about security rules.

How to add an object on an array wthout to delete the rest with firestore?

I'm trying to add an object on my array. My model already exist he is like that :
function createUser () {
// User is signed in.
firestore()
.collection('Users')
.doc(user.uid)
.set({
NickName: nickName,
Age: age,
City:city,
Mail:mail,
Picture:'',
MyTeam:[{
Activity:"",
City:"",
adress:"",
text:"",
Members:[{
Picture:"",
Mail:"",
Pseudo:""
}],
Owner:true
}]
On the object "my team" i wanna be able to add few teams (like an array) but when i try to set, all my models is removed.
async function RegisterTeam () {
// User is signed in.
firestore()
.collection('Users')
.doc(await AsyncStorage.getItem ('userID'))
.set({
MyTeam:[{
Activity:activity,
City:city,
Adress:adress,
text: text,
members: members,
Owner:true
}]
})
.then(() => {
console.log('team created !')
})
}
Do you have any idea how i can do that ?? i'm on react native ios
You may use .update() instead of .set(). And to not overwrite the whole object you should use dot notation like this
firestore()
.collection('Users')
.doc(await AsyncStorage.getItem ('userID'))
.update({
"MyTeam.newTeamObj": {newObj}
})
https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects
I think the best way to use sub-collections here and not a arrays. In this case you will have:
users (collection)
|
---------- teams (sub-collection)
|
------------ members (sub-sub-collection)

How to update firebase-query after user login

Using polymer and firebase I have some data that is only accessible by logged users. But, when the user login in the application the data isn't automatically updated.
An easily workaround is force the page reload with location.reload(). But, I'm trying to discover how to update these values without having to reload the entire application. Any ideas?
Expected outcome:
When user logins all fibase-document and firebase-query updates the synced data.
Actual outcome:
These elements doesn't update and the user has to reload the page
Example:
Element: app-auth.html
<firebase-app
id="firebaseApp"
auth-domain="app.firebaseapp.com"
database-url="https://app.firebaseio.com"
storage-bucket="app.appspot.com"
api-key="api"
messaging-sender-id="id"
></firebase-app>
<firebase-auth id="auth"
signed-in="{{isUserLogged}}"
user="{{user}}"
on-error="_onAuthError"
log
></firebase-auth>
login(credentials) {
if (credentials.provider === 'email') {
this.$.auth.signInWithEmailAndPassword(credentials.params.email, credentials.params.password)
.catch(e => {
if (e.code === 'auth/user-not-found') {
this.$.auth.createUserWithEmailAndPassword(credentials.params.email, credentials.params.password)
.then(this._successfulLogin());
}
})
.then(this._successfulLogin());
this.password = null;
} else {
this.$.auth.signInWithPopup(credentials.provider);
}
}
app-contents.html:
<firebase-query
data="{{contents}}"
path="/contents"
></firebase-query>
<div class="card">
<h1>Contents:</h1>
<template is="dom-repeat" items="[[contents]]" as="content">
<app-content-preview="[[content]]"></app-content-preview>
</template>
</div>
</template>
The contents element shows questionnaires the user has to answer in order to complete the goal of the app. But, the questionnaires can only be accessed by logged users. I would like to, when user logins in the app-auth, process his identity and update the firebase-query that shows the questionnaires he has to answer
Actually should read the data at query (due to your firebase datababase rule. (if you allow read for all, but write on logged in ) something like in database rules;
"rules": {
".read": true,
".write": "auth != null"
}
But in case let says read need also auth !=null than, lets change your code like:
<firebase-query
data="{{contents}}"
path="{{pathContents}}"
></firebase-query>
and define an observer like
static get observers() {
return ['_isUserLogged(user)']
_isUserLogged(user) {
if (user) {
this.set('pathContents', 'contents');
}
}
this observer will check user is exist or not. And if exist than the path will be defined as contents than query element will fire again automatically.
Hope this will work :)
Another js options is;
_isUserLogged(user) {
if (user) {
var db = firebase.database();
db.ref('contents').on('value', snapshot=>{
if (snapshot.exists()) { //To check that you have a data
this.set('contents', snapshot.val() );
}
})
}
}
Please not: if you read database with above code, than you do not need firebase-query element. So, delete it.

Listener at /users failed: permission_denied

I have a Sign Up Flow using Firebase. When I check if an email already exists in the database, like so:
refUsers.queryOrdered(byChild: "email").queryEqual(toValue: emailText).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("Unique email")
// Move to Password View.
let passwordViewController = self.storyboard?.instantiateViewController(withIdentifier: "PasswordViewController") as! PasswordViewController
self.navigationController?.present(passwordViewController, animated: true, completion: nil)
// Pass the emailText to the last View of the flow.
self.singleton.sharedInstance.emailText = emailText!
}
else {
print("Duplicate email")
}
})
The problem is, I don't have the permission to view /users in the database cause my rule is:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
I know I can find if an email is a duplicate using Auth.auth().createUser but it's not just email that I'm checking in the sign up flow. I use the same method for unique username, as well. How can I achieve this?
As you can see this is not the best way to do it. You should not manually check if email already exists - Firebase can do that for you when user signs up and why would you not want to use that?
What you need is a different approach. I can think of two ways right now:
First:
You can add a new rule to Firebase, eg:
{
"rules": {
"usernames": {
".read": true,
".write": "auth != null"
},
"emails": {
".read": true,
".write": "auth != null"
}
}
}
What you do here is create a new node named usernames which every user can access and read.
Here you should hold a copy of all usernames that registered users have and when registering check if users username is already inside this node.
Second way:
You could modify your signup flow a bit and let users register without a username. After account is created you let them set a username. With a nice flow it would all look as the same registration form.
UPDATE
With rules above users should be able to read from emails and usernames without being registered. This way you can fetch data and compare if email or username is already in use.
Just make sure that when user registers you insert his email and username into those two nodes.
Though #ZassX answered helped me, I've learned what a good approach for this would be, for those who are confused like me.
The best approach is to keep users data safe in /users with "auth != null" rule. Only show the user's meta data to everyone that includes just the email and password of each user. For example:
Database
{
“metaData”: {
uid: {
“email”: …,
“password”: …
}
},
“users”: {
uid: {
“email”: …,
“password”: …
// other information
}
}
}
Security
{
"rules": {
“metaData”: {
“.read”: true,
“.write”: “auth !== null”
},
“users”: {
“.read”: “auth !== null”,
“.write”: “auth !== null”
}
}
}
The information in metaData can now be matched without a user being authenticated first. Of course, this can get more complex as you add more security but for easy understanding, this is it.

swift showing posts from all firebase users

I need to create a UISearchController using firebase as the backend. I currently have two users in firebase. One user has made one post and the other has made four posts. I will like to be able to search for the title of all of the books in my database (which is five). However, as of now, I can only search for the books the current signed in user uploaded. Below is a screenshot of what my database looks like and what my code currently looks like.
databaseRef.child("posts").child(userID!).observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedUser?.uid)
{
print("same as logged in user")
}
else
{
self.usersArray.append(snapshot)
self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
}
}) { (error) in
print(error.localizedDescription)
}
}
You will need a different parent node called Books :-
Right now your JSON is something like this :-
posts : {
userID1 : {
BOOK_11_ID : {.....}
};
userID2 : {
BOOK_21_ID : {.....},
BOOK_22_ID : {.....},
BOOK_23_ID : {.....},
BOOK_24_ID : {.....},
}
}
You gotta modify your database JSON structure to :-
posts : {
Users_books :{
userID1 : {
BOOK_11 : true // Value 'true' means that this user
// has subscribed or bought this book or whatever
};
userID2 : {
BOOK_21 : true,
BOOK_22 : true,
BOOK_23 : true,
BOOK_24 : true,
}
},
Books:{
BOOK_11_ID : {/Book details/};
BOOK_21_ID : {/Book details/};
BOOK_22_ID : {/Book details/};
BOOK_23_ID : {/Book details/};
BOOK_24_ID : {/Book details/};
}
}
Modify your security rules for section 'Book' to make is visible to all the authenticated users.
{
"rules" :{
"Users_books" : {
".write" : "auth != null && auth.uid === $uid", // Will allow only the user to make any change within its node and no-one else
".read": "auth != null && auth.uid === $uid"
},
"Books" : {
".write" : "auth != null", // Will allow all the users that are authenticated to your app to access every books detail.
".read" : "auth != null"
}
}
}
Now what you gotta do is :-
1) If its the user that is creating the book ; Then store the book details in the parent node 'Books' under the books uid and set the value of book_ID to true in the users node.
2) If the database is all backend i.e you input it; Whenever the user subscribe's or buys a books just fetch the uid of that book and set it to true in the section of that user.
PS : Always fan out your database ; its much easier to search from it .Although i am pretty sure you are gonna wanna store some personal details of the user in the near future so for that just make another node . Name it 'Users', security rules will be same as 'Users_books' and under 'Users' parent node append details of every which user with their uid as their key.

Resources