HasRole function

In order to properly reflect some state in front-end UX, for example, you can actually edit this thing (show an edit button) it’s necessary to know if the user has a specific role. It can of course be built with a table of “roles” for each user reference, but that seems like a lot of overhead. Fauna know this data, so why not a function “HasRole(Role(…), )” which returns true or false? I could write it as UDF, except, I cannot find a way to actually invoke the predicate Query(), call doesn’t work, nor does trying to create/update a function on the fly.

In addition, this would be a great help from the shell, to help track down permission problems.

I would vote this up if I had more votes because it sounds like a good idea.

For a convenient workaround and to keep you from duplicating logic, you can create a UDF and call that in the role predicate, rather than create a role and reference the predicate in a UDF.

CreateFunction({
  name: "isAdmin",
  body: Query(Lambda("ref", Select(["data", "admin"], Get(Var("ref")))))
})

// ...

CreateRole({
  name: "admin",
  membership: [
    {
      resource: Collection("users"),
      predicate: Query(Lambda(ref => Call("isAdmin", ref)))
    }
  ],
  privileges: [
    {
      resource: Collection("users"),
      actions: { read: true, write: true, create: true, delete: true }
    }
  ]
})

EDIT:

I also just checked and you can be more explicit about reusing the Function's body, which probably saves some compute effort for resolving the role.

predicate: Select('body', Get(Function('isAdmin')))

That’s basically my work around, at the moment, I have functions “role.” and I can call them, and share them. It gets complex.

Also, I can’t find a way, and maybe I just missed it, but is there a way to get the collection a reference belongs to?

Refs have an id and a collection property you can Select.

Let(
  {
    thingRef: Ref(Collection('Thing'), '295711981456851459'),
    thingDoc: Get(Var('thingRef')),
    multiCollectionLink: Select(['data', 'link'], Var('thingDoc')),
  },
  {
    id: Select('id', Var('multiCollectionLink')),
    collection: Select('collection', Var('multiCollectionLink'))
  }
)

// returns, for example
{
  id: 295688826915914242,
  collection: Collection('User')
}
1 Like

Awesome. I couldn’t figure out what the first part was called. I likely just missed it in the documentation.

Sorry about that. I’ve filed an issue to add an example to the Select reference page that demonstrates how to extract a Ref’s collection.

1 Like

That would a good thing, I might be there somewhere and I missed it.

However- I did find a bug when I messing with roles. If you specify a predicate, but no resource for the “membership” it crashes the dashboard (Web) and it certainly doesn’t work, even the through the FQL query accepts it, and will display it. A “GetCurrentRoles()” function would be awesome, and make debugging it so much easier.

I haven’t been able to reproduce the Dashboard problem. Do you have any repro steps?

Paginate(Roles()) fetches all existing roles, up to the default page size (64 entries). Is that what you are referring to?

No, I want the roles fauna things the current identity has, so when something goes wrong in the middle of the query, I can determine why.

I’ll try tonight and see if I can reproduce the problem with the roles crashing the the dashboard.

Thanks for clarifying. It sounds like you have two requests:

  • Create a HasRole function that takes a document reference and returns a boolean that indicates whether a role includes the document reference in its membership.
  • Create another function (ShowRoles ?) that take a document reference and returns a set of roles that has the document reference included in its membership.

Does that sound accurate?

Both would be nice, but given the first, I can author the second. However, I think it’s something like: HasRole(roleRef, documentRef).

If that’s the signature that you want to use, then you can write HasRole yourself. All you’d need to do is compare the document ref’s collection with the membership in the role for roleRef. Typically, membership would specify a collection reference, but it could also be a specific document.

You mentioned in the original post that you were having trouble calling your UDF. Can you show us what you tried?

@ewan It is not possible to execute the membership predicate, if provided.

Query(
  Lambda(
    ['instanceRef', 'membership'],
    If(
      // role.resource === instance.collection
      Equals(
        Select('resource', Var('membership')),
        Select('collection', Var('instanceRef'))
      ),
      // then if
      If(
        //  no predicate
        IsNull(Select('predicate', Var('membership'))),
        // hooray!
        true,
        // else compute predicate.
        // *********************************
        // missing higher order functions!!
        // cannot compute
        //**********************************
        Abort('Cannot compute predicate')
      ),
      // else role.resource !== instance.collection
      false
    )
  )
)

For a workaround, I could imagine creating a Function wrapper around CreateRole which creates Functions whose bodies match the Role memberships’ predicates. Using naming conventions, or stashing a reference to the Function in the Role’s data, you could substitute that Function for the appropriate Role predicate when you want it. That’s a lot of work though…

Aha! Thanks for that, not sure why I missed that nuance earlier.

My first thought would be to try defining the membership predicate as a function that calls a named UDF. Then you could call that UDF whenever you like. I have not tested that idea though.