Okay I think I understand. You want to make sure that a user can only creat new UserInvitation
Documents for an organization they belong to, is that it?
Initial Notes & things to remember
Important for later: In One-to-one relationships, docs say “In the database, a single collection is predictably chosen to store relational data”. This predictable method is to use the collection with name that comes later in the alphabet. (There are some proposals to change that in the forums… but anyway). So, we should see the Organization
references stored in the UserInvitation
as well as User
Documents.
Also, you can use Get
in permission predicates! This means you can get User data and use that to match information in create and write ops.
Schema Definition
So if your schema is like this:
type Organization {
name: String!
invitations: [UserInvitation!]! @relation
users: [User!]! @relation
}
type User {
email: String
name: String!
organization: Organization! @relation # stored here!
}
type UserInvitation {
email: String!
organization: Organization! @relation # stored here!
}
Permission Required
I can confirm you need predicates for
-
write
and create
permission on UserInviation
-
write
permission on Organization
to perform this mutation:
partialUpdateOrganization(id: $id, data: {
invitations: {
create: [
{ email: $email }
]
}
})
So if you just turn all of those on it will work. But of course you don’t want them all true.
To be honest, I don’t know why you need create and write on the invitations. It must do the create, and then update to add the relation after the fact (an unfortunate doubling of write ops…). It also added to complexity.
Example Role
{
ref: Role("user"),
ts: 1593031454375000,
name: "user",
privileges: [
{
resource: Collection("Organization"),
actions: {
read: true,
// Can only write to Organization if the data doesn't change.
// lets the nested GraphQL query work
write: Query(
Lambda(
["oldData", "newData"],
And(
Equals(
Select("data", Var("oldData")),
Select("data", Var("newData"))
)
)
)
)
}
},
{
resource: Collection("UserInvitation"),
actions: {
read: true,
// Allows any write if there is no Organization provided
// or requires that Organization match the user's
write: Query(
Lambda(
["oldData", "newData"],
Let(
{
inputOrg: Select(
["data", "organization"],
Var("newData"),
null
),
user: Get(Identity()),
userOrg: Select(["data", "organization"], Var("user"), null)
},
If(
IsNull(Var("inputOrg")),
true,
Equals(Var("inputOrg"), Var("userOrg"))
)
)
)
),
// Allows creation with any data if there is no Organization provided
// or requires that Organization match the user's
create: Query(
Lambda(
"values",
Let(
{
inputOrg: Select(["data", "organization"], Var("values"), null),
user: Get(Identity()),
userOrg: Select(["data", "organization"], Var("user"), null)
},
If(
IsNull(Var("inputOrg")),
true,
Equals(Var("inputOrg"), Var("userOrg"))
)
)
)
)
}
},
{
resource: Index("organization_invitations_by_organization"),
actions: {
unrestricted_read: false,
read: true
}
},
{
resource: Index("organization_users_by_organization"),
actions: {
unrestricted_read: false,
read: true
}
}
],
membership: [
{
resource: Collection("User")
}
]
}
Alternative with UDF
If I got your use case right, and a User can only ever create invitations for their organization, then it might be better to only give the user call
permissions for a UDF that takes limited input and creates an invitation by copying the user Organization into the invitation document. The UDF has all the permissions it needs (only create
on UserInvitations).
This would be called from GraphQL by specifiying @resolver
directive.
If you are interested in this idea I can elaborate.