feat: implement authorization policies for Member resource

This commit is contained in:
Moritz 2026-01-08 21:03:27 +01:00
parent 93190d558f
commit 4192922fd3
Signed by: moritz
GPG key ID: 1020A035E5DD0824
5 changed files with 169 additions and 17 deletions

View file

@ -34,7 +34,8 @@ defmodule Mv.Membership.Member do
"""
use Ash.Resource,
domain: Mv.Membership,
data_layer: AshPostgres.DataLayer
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
require Ash.Query
import Ash.Expr
@ -296,6 +297,40 @@ defmodule Mv.Membership.Member do
end
end
# Authorization Policies
# Order matters: Most specific policies first, then general permission check
policies do
# SYSTEM OPERATIONS: Allow operations without actor (seeds, tests, system jobs)
# This must come first to allow database seeding and test fixtures
# IMPORTANT: Use bypass so this short-circuits and doesn't require other policies
bypass action_type([:create, :read, :update, :destroy]) do
description "Allow system operations without actor (seeds, tests)"
authorize_if Mv.Authorization.Checks.NoActor
end
# SPECIAL CASE: Users can always READ their linked member
# This allows users with ANY permission set to read their own linked member
# Check using the inverse relationship: User.member_id → Member.id
bypass action_type(:read) do
description "Users can always read member linked to their account"
authorize_if expr(id == ^actor(:member_id))
end
# GENERAL: Check permissions from user's role
# HasPermission handles update permissions correctly:
# - :own_data → can update linked member (scope :linked)
# - :read_only → cannot update any member (no update permission)
# - :normal_user → can update all members (scope :all)
# - :admin → can update all members (scope :all)
policy action_type([:read, :create, :update, :destroy]) do
description "Check permissions from user's role and permission set"
authorize_if Mv.Authorization.Checks.HasPermission
end
# DEFAULT: Forbid if no policy matched
# Ash implicitly forbids if no policy authorized
end
@doc """
Filters members list based on email match priority.