My scenario is very simple. Two user roles one admin and one viewer. Viewer can see read-only data. Is it possible to somehow set this without manually specifying select permissions for every single table?
Seems like such a common problem but I can't find anything in documentation.
Unfortunately there's no API method for changing permissions (or other metadata) across every table at once. It requires table name and schema.
I'm not sure what your "read-only" data permission translates into, but you can do this in two ways:
Call the metadata API with create_select_permission for every table name, programmatically
POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type" : "create_select_permission",
"args" : {
"table" : "article",
"role" : "user",
"permission" : {
"columns" : "*",
"filter" : {
"author_id":{ "_eq": "X-Hasura-User-Id" }
},
"limit": 100,
"allow_aggregations": false
}
}
}
Use the Metadata SDK to programmatically generate a tables.yaml or metadata.json which has the updated select permissions on every table, and then apply the metadata
See https://github.com/hasura/graphql-engine/issues/5459
import { Convert } from './customMetadataConverter'
import { TableEntry } from '../generated/HasuraMetadataV2'
// Read "tables.yaml" file as text from filesystem
const tablesMetadataFile = fs.readFileSync('./metadata/tables.yaml', 'utf8')
// Convert it to JSON object with type annotation using loadYAML utility
const tablesMetadata: TableEntry[] = Convert.loadYAML(tablesMetadataFile)
for (let table of tablesMetadata) {
if (table.insert_permissions.length == 0) table.insert_permissions = []
// Put the permission you want applied on every table here
table.insert_permissions.push({
columns : "*",
filter : { user_id: { _eq: "X-Hasura-User-Id" } },
limit: 100,
allow_aggregations: false
})
}
// Ouput the updated "tables.yaml" to filesystem
fs.writeFileSync('./tables-updated.yaml', Convert.metadataToYAML(tablesMetadata))
Related
I'm trying to read an item with ID of X from DynamoDB (Using Appsync graphql) and I want it to create a default item if there is none.
This seems like it should be a normal use case. But the solutions I've tried have all been pretty bad:
I tried to create a Pipeline resolver that would first get the item, then in a second function create an item if there was no item in the result from the previous function. This had with returning the read item.
I tried making a PutAction with the condition that an item with this ID doesn't work. This does what I need it to, but I can't change the response from an error warning, no matter what I do to the response mapping template.
So how does one efficiently create a "read - or create if it does not exist" resolver for DynamoDb?
It turns out that I was close to the solution.
According to this documentation: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#aws-appsync-resolver-mapping-template-reference-dynamodb-condition-handling
Create a putItem resolver that conditionally checks if there is an item with the same unique identifier (in DynamoDB that's usually a primary key and a sort key combination)
If the resolver determines the read object to not be different from the intended new object a warning will not be sent. So we can simply remove ALL fields from the comparison.
Example:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id" : { "S" : "${ctx.args.id}" }
},
"condition" : {
"expression" : "attribute_not_exists(id)",
"equalsIgnore": [ "__typename", "_version", "_lastChangedAt", "_createdAt", "name", "owner"]
},
"attributeValues": {
"name": { "S" : "User Username" }
}
}
What are the firebase rules required to avoid duplicate entries in below users array at sList collection level
"sList" : {
"-KZawgegLrIyq9h6GSf8" : {
"name" : "Test",
"users" : [ "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV" ]
}
}
What you're trying to model is a set: a collection of unique entries.
What you've modeled is an array: a sequence of non-unique entries.
The simplest and best solution is to change your data model to actually reflect a set. The closest you can get to that in Firebase is:
"sList" : {
"-KZawgegLrIyq9h6GSf8" : {
"name" : "Test",
"users" : {
"-KZawhnFZLcqFKNwZnSi": true,
"-KZawxBSAwL-lbi7dF-h": true,
"-KZawxBgz8k7v8-fKpDV": true
}
}
}
With such a set-like structure, duplicates are automatically prevented by the data structure itself. You won't need to write security rules for that.
So, let's say I have data like this:
{
"events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
text: "Bob says 'My name is Robert'" },
"kjl34jl234j" :
{ creator: "fred#fred.com",
text: "Fred says 'My name is Fredrick'" }
}
"users" : {
"bob#bob.com" : { "paid": true },
"fred#fred.com" : { "paid": false }
}
}
I'm assuming this is the correct way to structure the data. When the data is created, I use the push() method to create a new key for the data, and then store the creator of the data inside it.
I'd like to make it so that:
I can allow anyone from a group of users to access certain data (and disallow others obviously).
The query is "optimized," meaning if I have thousands of records I am not iterating over all the data.
More concretely, for example, I want lizzie#lizzie.com to be able to see the s0d980983s.
I'm confused how to structure the data, and what my Firebase rules should look like.
Would it be something like this?
{ "events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
viewers: { "bob#bob.com": true,
"lizzie#lizzie.com" : true },
text: "Bob says 'My name is Robert'" },
...
}
I don't understand how I can search for events that are viewable by a group of users. I don't believe Firebase supports some kind of wildcard that would make this code work, right?
var ref = firebase.database().ref( "events/*/viewers/lizzie#lizzie.com" ).on(...);
Do I also want to reference the events inside my users table? I'm not sure I understand how to flatten data (denormalize it) and keep references in both places to support a query like this. Should I expect to make multiple queries where I first retrieve a list of events stored in a user object and then retrieve them one by one using their key? But, how do I put that logic into my firebase rules?
{ "events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
viewers: { "[insert bobs id]": true,
"[insert liz id]" : true
},
text: "Bob says 'My name is Robert'" },
...
}
Based on the above structure as you suggested, and if you are using firebase authentication to authenticate your user, you can add another 'read' or 'write' rule for checking whether that user is in the list of your 'viewers'. something like:
{
"rules": {
"users": {
"$uid": {
".write": "auth != null &&
root.child('users').child(auth.uid).child('viewers').child(auth.uid).val() ==
true"
}
}
}
}
This should help. setting firebase security rules at a location/node
I have a snapshot for my reference in firebase like this:
"friendlist" : {
"user1" : {
"user3" : 1
},
"user2" : {
"user1" : 0
}
"user3" : {
"user1" : 1
}
}
The explanation for the reference:
Every user has an unique id, i'm using user's id for their friendlist unique id. In example above i have 3 users and every user have his own friendlist. Inside their friendlist, there's other user's id that already be friend with him. If the value is 1, the user already be friend. But when the value is 0, the user is requesting to be friend.
My problem is:
How to get all user's friendlist's id which have "user1" with value 0 inside their friendlist? Can i do that in just one query?
I think i need to iterate through all friendlist and orderbykey for every friendlist and looking for "user1". Or there's any good approach to do that?
Any answer would be appreciated, thanks!
It would help if you next time tell a bit more about what you've already tried. Or at the very least specify what language/environment you're targeting.
But in JavaScript, you can get those users with:
var ref = new Firebase('https://yours.firebaseio.com/friendlist');
var query = ref.orderByChild('user1').equalTo(0);
query.once('value', function(usersSnapshot) {
usersSnapshot.forEach(function(userSnapshot) {
console.log(userSnapshot.key());
});
});
With the sample data you specified, this will print:
user2
You should add (and will get a warning about) an index for efficiently performing this query:
{
"rules": {
"friendlist": {
".indexOn": ['user1']
}
}
}
Without this index, the Firebase client will just download all data to the client and do the filtering client-side. With the index, the query will be performed server-side.
A better data model
You'll likely want to search for any friend, which turns the index into:
".indexOn": ['user1', 'user2', 'user3']
But with this structure, you'll need to add an index whenever you add a user. Firebase SDKs don't have an API to add indexes, which is typically a good indication that your data structure is not fitting your needs.
When using a NoSQL database, your data structure should meet the needs of the application you're building. Since you are looking to query the friends of user1, you should store the data in that format too:
"friendlist" : {
"user1" : {
"user3" : 1
},
"user2" : {
"user1" : 0
}
"user3" : {
"user1" : 1
}
},
"friendsOf": {
"user1": {
"user2": 0,
"user3": 1
},
"user3": {
"user1": 1
}
}
As you can see, we now store two lists:
* friendList is your original list
* friendsOf is the inverse of your original list
When you need to know who friended user 1, you can now read that data with:
ref.child('friendsOf').child('user1').on('value'...
Note that we no longer need a query for this, which makes the operation a lot more scalable on the database side.
Atomic updates
With this new data model, you need to write data in two places when adding a friend relation. You can do this with two set()/update() operations. But in recent Firebase SDKs, you can also perform both writes in a single update like this:
function setRelationship(user1, user2, value) {
var updates = {};
updates['friendList/'+user1+'/'+user2] = value;
updates['friendsOf/'+user2+'/'+user1] = value;
ref.update(updates);
}
setRelationship('user3', 'user4', 1);
The above will send a single command to the Firebase server to write the relationship to both friendList and friendsOf nodes.
I have data which looks like this:
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill"
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill"
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John"
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack"
}
When a user is logged in with id simplelogin:5 I want to order the output based on their sort preferences. So say for example user simplelogin:5 previously set his order to Jack,Jill,Joh,Bill and simplelogin:1 set their order to Bill,John,Jack,Jill.
I know I can set priority but that's priority for the data as a whole and it isn't tied to a user, this is shared data which needs custom priority per user.
I was thinking of setting up something like this:
users[
{
"uid":"simplelogin:1",
"nameOrder":[-gn643Jp3rgsIeM2O0EN, -yryexaJp3rgsIeM2O0EN, etc.]
}
];
But it seems like there should be a better way, and even if I was able to generate a list like that, i'm not sure how to sort the output to follow the order in the nameOrder entry.
First you need to modify your main list with a persistent sort id. I used a numeric value but you can use whatever value you prefer.
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill",
nameIndex": 0
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill",
"nameIndex": 1
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John",
"nameIndex": 2
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack",
"nameIndex": 3
}
Then store your user defined sort preference
"simplelogin:1" : {
"nameOrder": "0,2,3,1"
},
"simplelogin:5" : {
"nameOrder": "3,1,2,0"
}
Now when you read the name list, use the array index saved in nameOrder to display results.