See this original community forums post
How can we create functions that determine which Roles a Key or Token has?
- Create a new Collection.
- Create one Document for each Role that uniquely represents each role.
- Create the required Indexes to read the new Collection.
- Update each Role with privileges to read the indexes, but a predicate that lets it only read the one specific Document that represents it.
Then, when you try to read the Indexes only those documents that the roles have access to will be read. You can interpret which documents are read as the roles that you have. And we can create Functions that make it easy to use this stuff.
NOTE: This technique gives you the ability to list all of the roles that a given key has, but it relies on each key being aware of which roles it has. That means that any key can read from this new Collection we are making, so be aware of that.
An easy way to try this out is with the demo database that can be created from the Dashboard. Create a new database and make sure to click Use demo data
We'll call it "_role_checkers"
.
// as admin
CreateCollection({ name: "_role_checkers" })
This query will loop over each Role and create simple Documents with a name
field the same as the Role.
// as admin
Let(
{
role_names: Select("data", Map(Paginate(Roles()), Lambda("ref", Select("id", Var("ref"))))),
},
Map(
Var("role_names"),
Lambda(
"name",
Create(Collection("_role_checkers"), { data: { name: Var("name") } })
)
)
)
We will create an "_all_role_checkers"
Index that we will use for a "CurrentRoles"
Function, and a "_role_checker_by_name"
Index that we will use for a "HasRole"
Function.
// as admin
CreateIndex({
name: "_all_role_checkers",
source: Collection("_role_checkers")
})
CreateIndex({
name: "_role_checker_by_name",
source: Collection("_role_checkers"),
terms: [{ field: ["data", "name"] }]
})
IMPORTANT: These Indexes cannot specify
values
fields. Otherwise, reading the Index entries will count as a read on the Document for permissions. We want to be able to use the read-the-index-but-filter-the-documents trick.
We want two functions
"CurrentRoles"
function that will return an array of Role name, like["customer", "manager"]
"HasRole"
function that will take a Role name as an argument and returntrue
orfalse
.
These Functions will not have a role
field specified. That means they will rely on the Roles of the calling Key/Token.
// as admin
CreateFunction({
name: "CurrentRoles",
body: Query(
Lambda([], Map(
Select("data", Paginate(Match(Index("_all_role_checkers")))),
Lambda("ref", Select(["data", "name"], Get(Var("ref"))))
))
)
})
CreateFunction({
name: "HasRole",
body: Query(
Lambda("name", Let(
{
page: Paginate(Match(Index("_role_checker_by_name"), Var("name")))
},
Not(IsEmpty(Select("data", Var("page"))))
))
)
})
For each Role, we will add the privilege to read the Indexes, read the respective Document, and if we want, call the Functions.
Update(
Role("ROLE_NAME"),
{
Privileges: [
/* all existing privileges */
{
// Can only read the Document if the `name` field matches the Role's name
resource: Collection("_role_checkers"),
actions: { read: Query(Lambda("ref", Equals(
"ROLE_NAME",
Select(["data", "name"], Get(Var("ref")))
))) }
},
{
resource: Index("_all_role_checkers"),
actions: { read: true }
},
{
resource: Index("_role_checker_by_name"),
actions: { read: true }
},
{
resource: Function("HasRole"),
actions: { call: true }
},
{
resource: Function("CurrentRoles"),
actions: { call: true }
}
]
}
After running all of this on the Demo database we can try out our new functions
// as admin
Call("CurrentRoles")
// ["customer", "manager"]
Call("HasRole", "customer")
// true
Call("HasRole", "manager")
// true
// as customer
Call("CurrentRoles")
// ["customer"]
Call("HasRole", "customer")
// true
Call("HasRole", "manager")
// false
// as manager
Call("CurrentRoles")
// ["manager"]
Call("HasRole", "customer")
// false
Call("HasRole", "manager")
// true