I have set up a read: predicate and I am seeing different behavior when I run in a UDF that has membership: versus running the query outright. I have read the docs, and do not understand the different behavior: User-defined roles - Fauna Documentation
First a minimal repro: GitHub - h-unterp/predicate_read
Follow the instructions in the readme, it should run with minimal setup.
I’ve done some more experimenting with unrestricted_read on a UDF and it too does not work.
What I don’t understand is that as the system is currently designed, we have to choose between encapsulating code as UDF which allows for some security, versus the ability to lock down things to a finer-grain with predicates however UDF’s don’t work in that case.
So either UDF’s or predicates, unless I’ve overlooked something?
Feels a bit inconsistent.
Being able to combine UDF’s with the predicate system would be extremely powerful, and allow for much more secure and consistently designed applications.
Would appreciate some perspective both about a potential feature request, but also how best to proceed designing the most secure app possible given current API. Thank you
This expression does not evaluate to false. It errors because there is no identity, so the predicate fails.
When you provide the role field to a UDF, the UDF runs as though it has its own Key with the Role provided, and Keys (as opposed to Tokens) do not have an Identity. Calling CurrentIdentity() with a Key returns an error, which you can test in the shell. When the predicate runs, it will encounter the same error, but the predicate fails as unauthorized rather than propagate the error to your users.
Since you are using the same Roles to call directly and assign to the functions, I will highlight that the membership field is only used to assign the Role to tokens as they are used; when you assign a Role to a Function, the membership is irrelevant (Has no effect on identity). But otherwise, there is no inherent problem with using a Role in both circumstances.
So is there a way to still use a UDF and restrict the reads? Yes!
We will need a different Role for the UDF than for a user calling the Index directly. I see that the UDF already has a "ref" parameter, but it is not used. What you need to do is require the caller to provide the user ref, and restrict the caller from only providing CurrentIdentity to the UDF.
CreateRole({
name: "fn_role_let_it_be",
// membership not required
privileges: [
// UDF is simply free to read the Collection and Index, because we are restricting how we call it
{
resource: Index(TestIndexes.InfoByUserRef),
actions: {
read: true,
},
},
{
resource: Collection(TestCollections.Info),
actions: {
read: true,
write: true,
},
},
],
})
CreateFunction({
name: TestFunctions.LetItBe,
body: Query(Lambda("user_ref",
Get(Match(
Index(TestIndexes.InfoByUserRef),
Var("user_ref")) // use the argument, don't call CurrentIdentity Directly
),
)),
})
CreateRole({
name: "user_can_call_UDFs",
membership: [{ resource: Collection(TestCollections.Users) }],
privileges: [
{
resource: Function(TestFunctions.LetItBe),
actions: {
call: Query(Lambda(["user_ref"],
// User can ONLY call when providing their identity,
Equals(Var("user_ref"), CurrentIdentity())
)),
},
},
]
})
And now you should be able to use the UDF
// with a logged in user
client.query(
Call(TestFunctions.LetItBe, CurrentIdentity())
)
side note
Your minimal repro is not very minimal. It was nontrivial to dig through the bespoke code you have and provide code that makes sense in response. The forums are a place for everyone to follow along with the discussion, and the way you’ve shared your code and schema makes that difficult.
I think that I have handle on what you are trying though, and I hope that my response helps. My advice (and request), though, is that for future questions you consider supplying the final definition of the schema involved rather than requiring readers to piece it together like this.
All that said, what you’ve done with CreateOrUpdateX helpers, breaking UDF bodies out into separate JS functions, etc. – I think that’s a great way to stay organized. Also it’s similar to things I’ve done before, which I think did help me get up to speed relatively quickly for your question