From b3eb6c922333c7dc4ad7a98054f899c39b6606d1 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 8 Jan 2026 22:54:53 +0100 Subject: [PATCH] Docs: Correct :linked scope documentation --- docs/roles-and-permissions-architecture.md | 24 ++++++++++++---------- docs/roles-and-permissions-overview.md | 4 +++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/roles-and-permissions-architecture.md b/docs/roles-and-permissions-architecture.md index b44604b..8c89af7 100644 --- a/docs/roles-and-permissions-architecture.md +++ b/docs/roles-and-permissions-architecture.md @@ -110,8 +110,8 @@ Control access to LiveView pages: Three scope levels for permissions: - **:own** - Only records where `record.id == user.id` (for User resource) - **:linked** - Only records linked to user via relationships - - Member: `member.user_id == user.id` - - CustomFieldValue: `custom_field_value.member.user_id == user.id` + - Member: `id == user.member_id` (User.member_id → Member.id, inverse relationship) + - CustomFieldValue: `member_id == user.member_id` (traverses Member → User relationship) - **:all** - All records, no filtering **6. Special Cases** @@ -714,8 +714,8 @@ defmodule Mv.Authorization.Checks.HasPermission do - **:all** - Authorizes without filtering (returns all records) - **:own** - Filters to records where record.id == actor.id - **:linked** - Filters based on resource type: - - Member: member.user_id == actor.id - - CustomFieldValue: custom_field_value.member.user_id == actor.id (traverses relationship!) + - Member: `id == actor.member_id` (User.member_id → Member.id, inverse relationship) + - CustomFieldValue: `member_id == actor.member_id` (CustomFieldValue.member_id → Member.id → User.member_id) ## Error Handling @@ -799,12 +799,14 @@ defmodule Mv.Authorization.Checks.HasPermission do defp apply_scope(:linked, actor, resource_name) do case resource_name do "Member" -> - # Member.user_id == actor.id (direct relationship) - {:filter, expr(user_id == ^actor.id)} + # User.member_id → Member.id (inverse relationship) + # Filter: member.id == actor.member_id + {:filter, expr(id == ^actor.member_id)} "CustomFieldValue" -> - # CustomFieldValue.member.user_id == actor.id (traverse through member!) - {:filter, expr(member.user_id == ^actor.id)} + # CustomFieldValue.member_id → Member.id → User.member_id + # Filter: custom_field_value.member_id == actor.member_id + {:filter, expr(member_id == ^actor.member_id)} _ -> # Fallback for other resources: try direct user_id @@ -918,7 +920,7 @@ end **Location:** `lib/mv/membership/member.ex` -**Special Case:** Users can always access their linked member (where `member.user_id == user.id`). +**Special Case:** Users can always READ their linked member (where `id == user.member_id`). ```elixir defmodule Mv.Membership.Member do @@ -978,10 +980,10 @@ defmodule Mv.Membership.CustomFieldValue do policies do # SPECIAL CASE: Users can access custom field values of their linked member - # Note: This traverses the member relationship! + # Note: This uses member_id relationship (CustomFieldValue.member_id → Member.id → User.member_id) policy action_type([:read, :update]) do description "Users can access custom field values of their linked member" - authorize_if expr(member.user_id == ^actor(:id)) + authorize_if expr(member_id == ^actor(:member_id)) end # GENERAL: Check permissions from role diff --git a/docs/roles-and-permissions-overview.md b/docs/roles-and-permissions-overview.md index 86e7273..61aef9c 100644 --- a/docs/roles-and-permissions-overview.md +++ b/docs/roles-and-permissions-overview.md @@ -294,7 +294,9 @@ Each Permission Set contains: **:own** - Only records where id == actor.id - Example: User can read their own User record -**:linked** - Only records where user_id == actor.id +**:linked** - Only records linked to actor via relationships +- Member: `id == actor.member_id` (User.member_id → Member.id, inverse relationship) +- CustomFieldValue: `member_id == actor.member_id` (traverses Member → User relationship) - Example: User can read Member linked to their account **:all** - All records without restriction