Firebase Database Rules private nodes - firebase

I'm doing a groceryItem app where users can add items and only should see their own items.
I add my items by using childByAutoId and get unique key entries for each item (see picture #1).
Each item hast got 3 properties. addedByUser is the UID of an authenticated user and of course a name and the quantity.
I have problems setting the rules so that only users can see their own items. My current rules look like this (see picture #2).
If I log in with another user he can read all items. So my question is:
How I can make items readable for the users who created them?
I think I have a gap between items and addedByUser because I have the childByAutoId between. If you have any solutions or tips how I can structure this in a better way, pls feel free to help me.
Thanks in advance!

The $addedByUser is probably in the wrong spot in this example, as it is not a "nested child" of the items, but an attribute of a child item. I would suggest (not having tested this) that you would be looking for something along the lines of:
"items": {
"$item": {
".read": "data.child('addedByUser').val() === auth.uid"
}
}
This is lifted from the Firebase docs:
With data:
{
"messages": {
"message0": {
"content": "Hello",
"timestamp": 1405704370369
},
"message1": {
"content": "Goodbye",
"timestamp": 1405704395231
},
...
}
}
The rules could be:
{
"rules": {
"messages": {
"$message": {
// only messages from the last ten minutes can be read
".read": "data.child('timestamp').val() > (now - 600000)",
// new messages must have a string content and a number timestamp
".validate": "newData.hasChildren(['content', 'timestamp']) && newData.child('content').isString() && newData.child('timestamp').isNumber()"
}
}
}
}
This will prevent the incorrect user from reading someone else's (singular) item, and in your example would prevent user SZ825V... from performing a read on "items/-KKTDD...", however if user SZ825V were to perform a read on "items", then the read would fail entirely, because the user is trying to read their own documents and someone else's as well. This is an important concept in firebase with the rules behaving in an atomic way - that is, if one part of the read fails, the entire read does. Another way of putting this is that the rules do not act as a filter.
If this is the functionality you are seeking, it may be required to rearrange your structure. An example could be as follows:
{
"users": {
"abcdef-user-id": {
"items": {
"fjeiwofjewio": {
"name": "apple",
"qty": "23"
}
}
},
"ghijkl-user-id": {
"items": {
"regrewgreh": {
"name": "passionfruit",
"qty": "18"
}
}
}
}
and security rules might be:
.. {
"users": {
"$userId": {
".read": "auth.uid === $userId"
}
}
}
The above approach assumes you only want to allow a user to read the items in their user own data tree, is that no-one else would be able to see any other users' items. If you did want other users to see some of the other users' items, then a different approach again would be required, which would be another SO question.

Related

Saleor Shopping Cart Sometimes Wrong/Older Version - Using GraphQL and Next.js

I am using Saleor shopping cart GraphQL, and actually Stepzen to merge it with a Strapi GraphQL. So all Saleor mutations and queries go through the Stepzen GraphQL.
I have followed Saleors tutorial here:
https://learn.saleor.io/checkout/displaying-cart-content/
This works 90% of the time so generally the code should be OK, but I must be missing something because occasionally when adding to the cart the contents are different. The item is not added. In fact, it's possible the cart seen is an older version but am not sure. I could add to cart 4 times and it updates to the correct products and quantities each time, then the 5th time it shows the cart from the last time or even time before.
After a few seconds, if you refresh the page, the cart displays correct.
EDIT: I have just noticed, if you refresh the page several times, the cart sometimes changes, and a refresh changes back, then a refresh it changes to something else and just keeps changing on refreshes!
When the cart works, there is a little delay adding it, but when it doesn't work the cart page loads much faster, almost instantly. It's as if when it works it waits for Saleors response for the cart items list, but when it doesn't it doesn't wait for the response. Just a hunch.
This is my query code to get cart items from Saleor. It does work, 90% of the time.
const Cart = () => {
const [token] = useCookies('["token"]')
const { data, loading, error } = useCheckoutFetchByTokenQuery({
variables: { checkoutToken: token.token },
skip: !token.token,
});
const [CheckoutremoveProduct] = useCheckoutRemoveProductMutation();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
if (!data || !data.saleorcheckout) return null;
const products = data.saleorcheckout?.lines || [];
return (
<Layout title="Cart">
.....
</Layout>
);
}
The actual saleorcheckout query:
query CheckoutFetchByToken($checkoutToken: saleorUUID!) {
saleorcheckout(token: $checkoutToken) {
id
email
lines {
id
quantity
totalPrice {
gross {
amount
currency
}
}
variant {
id
product {
id
name
slug
thumbnail {
url
alt
}
cmsContent {
SaleorID
Title
Featured_image
{
data
{
attributes
{
url
width
height
alternativeText
}
}
}
}
}
pricing {
price {
gross {
amount
currency
}
}
}
name
}
}
totalPrice {
gross {
amount
currency
}
}
}
}
Interestingly the saleorcheckoutLinesAdd mutation which adds lines to the cart returns with the correct products in the cart, after the new one is added, but on the cart page that uses saleorcheckout to fetch them returns a lower amount, until you refresh later.
The actual saleorcheckoutLinesAdd mutation:
mutation ProductAddVariantToCart($checkoutToken: saleorUUID!, $variantId: ID!) {
saleorcheckoutLinesAdd(
token: $checkoutToken
lines: [{ quantity: 1, variantId: $variantId }]
) {
checkout {
id
lines {
id
quantity
variant {
id
name
product {
name
}
}
}
}
errors {
message
}
}
}
Thanks
UPDATE:
My first post above included this below, but it may be a red herring as I have added the variant ID in the query now, and dont get this error below anymore, but the problem above still exists.
Previously when it doesn't work the following error was in console:
react_devtools_backend.js:4026 Cache data may be lost when replacing the variant field of a saleorCheckoutLine object.
To address this problem (which is not a bug in Apollo Client), either ensure all objects of type saleorProductVariant have an ID or a custom merge function, or define a custom merge function for the saleorCheckoutLine.variant field, so InMemoryCache can safely merge these objects:
existing: {"__typename":"saleorProductVariant","product":{"__ref":"saleorProduct:UHJvZHVjdDoxNjY="},"pricing":{"__typename":"saleorVariantPricingInfo","price":{"__typename":"saleorTaxedMoney","gross":{"__typename":"saleorMoney","amount":100,"currency":"USD"}}},"name":"UHJvZHVjdFZhcmlhbnQ6NDAy"}
incoming: {"__typename":"saleorProductVariant","name":"UHJvZHVjdFZhcmlhbnQ6NDAy","product":{"__typename":"saleorProduct","name":"52-00 Base Plate"}}
I have looked this up, and the solution is to set typePolicies the ApolloClient to state to merge the new and old carts. BUT I cannot get this to work, if it is the solution. Online some examples of typePolicies and merge have console.log() outputs but mine are just not firing. So I am assuming I have not set the typePolicies up correct perhaps.
This is my typePolicies code, although I have tried many variations, and the fact lines is nested may be complicating it.
const client = new ApolloClient({
uri: "https://my-stepzen-graphql-url.com/__graphql",
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
saleorcheckoutLinesAdd: {
lines: {
merge(existing, incoming, { mergeObjects }) {
console.log(existing)
console.log(incoming)
return mergeObjects(existing, incoming);
},
},
},
},
},
},
}),
})
I have tried many versions of the above typePolicies, but can never get console.log to fire, so assume I have fond the correct format.

I'm curious about the method of fire base security rules

This is my firebase rule.
{
"rules":{
"users":{
".write":"auth.provider == 'anonymous' || auth.token.email.endsWith('dev#email.com')",
".read":"auth.token.email.endsWith('dev#email.com')"
}
}
}
It says unsafe rules.
I wonder why, and there's no way to know how to improve it Help me

Filter nested objects in WPGraphQL

I'm using WPGraphQL to interact with a vanilla wordpress backend. gatsby-source-wordpress is used as a client.
What I would like to achieve with my query:
for a list of category ids, give me a list of posts that belong to each category
each list of posts should be filtered (posts not having a certain id, etc), and limited to a certain number. The filter/limit params would be the same for each category.
Now when I'm constructing my query, it seems like I can only filter the outer node (category), but not the attached post node:
// syntax from the gatsby-source-wordpress plugin
// https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-wordpress
query MyQuery {
allWpCategory(filter: {id: {in: ["dGVybTo0Mg=="]}}) {
nodes {
posts { // cannot filter here 🥲
nodes {
title
id
}
}
}
}
}
I tried adding the filter in different locations, without success. Searched for solutions, but didn't find any specific to this GraphQL implementation. Structurally, this looks like what I'm try to achieve, but it's a different platform altogether.
My questions:
is it possible to get this working (it seems like a pretty standard requirement to me)
if yes, how
if no, what's the limiting factor? (GraphQL, WPGraphQL, gatsby-source-wordpress)
🙏
I don't have your data structure and can't test. Does something like this work?
query MyQuery {
allWpCategory(
filter: {
posts: {
elemMatch: {
id: {
ne: "something"
}
}
}, {
id: {
in: ["dGVybTo0Mg=="]
}
}
})
{
nodes {
posts { // cannot filter here 🥲
nodes {
title
id
}
}
}
}
}

How to add a role profile page to meteor user?

I have 2 roles in my app. Users and doctors.
I want that a doctor can edit extra fields like picture and text field on an extra profile page for doctors.
How to add this fields probably not to the profile but to an extra part of the users collection and make it then public availible as a profile page /doctor/_id
I am now experimenting with meteorkitchen, which is great, but when I add a profile.doctor.field1 and show this on an page like user_settings/profile/doctor and update it the other fields of the profile are overwritten with empty values. I assume it assumes that all profile attributes are edited on one form and when one field is empty so it deletes them. But this is not what I want.
(Can someone add a tag meteorkitchen please. I have not enough reputation. thanks)
Edit:
this are the settings for the router
var roleMap = [
...
{ route: "user_settings.doctor", roles: ["doctor","admin"] }
];
and attached this part for the form
"name": "edit_form",
"type": "form",
"mode": "update",
"title": "Edit your doctor profile",
"submit_route": "user_settings.profile.doctor",
"query": {
"name": "current_user_data",
"collection": "users",
"filter": {
"_id": "Meteor.userId()"
},
"find_one": true,
"options": {},
"params": []
},
"fields": [
{
"name": "profile.doctor.quote",
"title": "Favorite quote",
"type": "string",
"required": true
}
]
the problem is that on update some of the profile values the others are deleted.
If you want to stick with Meteor Kitchen, here is how I would proceed.
First, you need to create a "doctor" role. See your application page on kitchen designer, you can add it here.
Second, you need to add your new "doctor" role to the page roles. You can do it directly in the role map located in client/views/router.js or using the designer.
EDIT Following the discussion in the comments here is an update:
You can change the collection name directly in the section "Edit source" of the designer. You go to your page, in the query, you set
"query": {
"name": "doctor_user",
"collection": "users",
"filter": {
"_id": ":userId"
},
"find_one": false,
"options": {},
"params": []
},
The thing is that even if you do that, only admins can update the users collection. So you need to download the user-roles package from perak, put it inside your project (only put the folders) and remove the user-roles pre-installed package (meteor remove perak:user-roles).
Once you have done that, go to server/collections and replace the update part of users.js with that:
update: function (userId, doc, fieldNames, modifier) {
return Users.isInRole ("admin")|| (Users.isInRole ("doctor") &&
!_.contains(fieldNames,'roles')));
It will allow users with the doctor role to update any user field except for the "roles" field.
Last step, you edit the server\publications\users.js file and add this in it:
Meteor.publish("doctor_user", function(_id){
return Users.isInRoles ({"doctor", "admin"}) ? Users.find({_id: _id}) : this.ready();
});

Firebase orderByChild security rules

Given the firebase setup with the dinosaur example I would like to add a security rule for .read. If I add .read:true in the hierarchy like the following the query fails to provide any results.
{
"rules": {
"dinosaurs": {
"$dino": {
".read":true
}
}
}
}
If I use the following the queries work as expected.
{
"rules": {
"dinosaurs": {
".read":true
}
}
}
For my use case I need a setup more like the first example so that I can check each item for a condition before allowing a read to that item. To use the new orderByChild("property") style query however it would seems I have to allow access to the entire subtree. Is this the case?

Resources