FaunaDB custom-role to limit access to own data only

378 views Asked by At

I'm building a sample app to evaluate FaunaDB and Nextjs

My plan is to have the web app authenticate separately, then create the user on FaunaDB Afterwards create a token on FaunaDB, and allow the user to connect through his own secret token

I believe I'm on the right track to get this model to work, but I'm facing an issue with the Custom-role in FaunaDB

The data model is User -> Board -> Tasks, and I will use the access to boards in this question

Here is the code for the custom role

{
  ref: Role("Free_Tier_Role"),
  ts: 1601934616790000,
  name: "Free_Tier_Role",
  membership: [
    {
      resource: Collection("user"),
      predicate: Query(
        Lambda("ref", Select(["data", "isEnabled"], Get(Var("ref"))))
      )
    }
  ],
  privileges: [
    {
      resource: Collection("user"),
      actions: {
        read: true,
        write: false,
        create: false,
        delete: false,
        history_read: false,
        history_write: false,
        unrestricted_read: false
      }
    },
    {
      resource: Collection("board"),
      actions: {
        read: Query(
          Lambda(
            "ref",
            Equals(Identity(), Select(["data", "owner"], Get(Var("ref"))))
          )
        ),
        write: Query(
          Lambda(
            ["oldData", "newData"],
            And(
              Equals(
                Select("id", Identity()),
                Select(["data", "owner"], Var("oldData"))
              ),
              Equals(
                Select(["data", "owner"], Var("oldData")),
                Select(["data", "owner"], Var("newData"))
              )
            )
          )
        ),
        create: Query(
          Lambda(
            "newData",
            And(
              Equals(Identity(), Select(["data", "owner"], Var("newData"))),
              LT(Count(Match(Index("board_by_owner"), Identity())), 3)
            )
          )
        ),
        delete: Query(
          Lambda(
            "ref",
            Equals(Identity(), Select(["data", "owner"], Get(Var("ref"))))
          )
        ),
        history_read: false,
        history_write: false,
        unrestricted_read: false
      }
    },
    {
      resource: Collection("task"),
      actions: {
        read: Query(
          Lambda(
            "ref",
            Equals(Identity(), Select(["data", "owner"], Get(Var("ref"))))
          )
        ),
        write: Query(
          Lambda(
            ["oldData", "newData"],
            And(
              Equals(
                Select("id", Identity()),
                Select(["data", "owner"], Var("oldData"))
              ),
              Equals(
                Select(["data", "owner"], Var("oldData")),
                Select(["data", "owner"], Var("newData"))
              )
            )
          )
        ),
        create: Query(
          Lambda(
            "newData",
            And(
              Equals(Identity(), Select(["data", "owner"], Var("newData"))),
              LT(Count(Match(Index("task_by_owner"), Identity())), 10)
            )
          )
        ),
        delete: Query(
          Lambda(
            "ref",
            Equals(Identity(), Select(["data", "owner"], Get(Var("ref"))))
          )
        ),
        history_read: false,
        history_write: false,
        unrestricted_read: false
      }
    },
    {
      resource: Index("task_by_owner"),
      actions: {
        unrestricted_read: false,
        read: false
      }
    },
    {
      resource: Index("board_by_owner"),
      actions: {
        unrestricted_read: false,
        read: false
      }
    }
  ]
}

The problem I'm facing is When I login through a user token, and that user is the owner for a board, I get an empty list

> Map(Paginate(Documents(Collection('board'))),Lambda('x', Get(Var('x'))))
{ data: [] }

To test they have the same value, I'm running this command on the shell on the dashboard

Select(["data", "owner"], Get(Ref(Collection("board"), "278575744915866117")))

Ref(Collection("user"), "278571699875611143")

>> Time elapsed: 28ms

And run the Identity() on my token-authenticated instance

> Identity()
Ref(Collection("user"), "278571699875611143")
>                                 

P.S. before this approach, I was matching the id number only using Select(['data', 'ownerId'], Ref) but it didn't work, even when I tried converting both ToString or ToNumber

1

There are 1 answers

0
Nabil Alhusail On BEST ANSWER

Wow, took me about 2 days to diagnose what was going on

Basically, the code I've written above is working to only allow an owner of a board or task to see his/her own items, and update them

But, the problem for me was with membership, it was not working as expected

Here's the solution steps you need to follow if you find yourself with non-working permissions

Make sure the role applies to your user Here's how I did it

  1. Create a sample function 'test_newFunc' using the default code Query(Lambda("x", Add(Var("x"), Var("x"))))
  2. Create a new role, and include all 'user', and allow calling the 'test_newFunc'
  3. Create the token, and start the shell using the token's secret
  4. Run Call("test_newFunc", 2)
  5. For me, I added the membership predicate to be

Lambda("docRef", Equals(true, Select(["data", "isEnabled"], Get(Var("docRef")))))

Which means, the user must have a data field "isEnabled", and its value must be true

Then test by switching the value of this field for the impersonated user until you confirm that this role is being applied

Once this step is clear, then you can test the predicates for each permission

Hope this is helpful for future developers who run into this issue