Security: Fix critical deny-filter bug and improve authorization

CRITICAL FIX: Deny-filter was allowing all records instead of denying
Fix: User validation in Member now uses actor from changeset.context
This commit is contained in:
Moritz 2026-01-08 23:12:07 +01:00
parent b3eb6c9223
commit 42a463f422
Signed by: moritz
GPG key ID: 1020A035E5DD0824
4 changed files with 25 additions and 332 deletions

View file

@ -300,12 +300,12 @@ defmodule Mv.Membership.Member do
# Authorization Policies
# Order matters: Most specific policies first, then general permission check
policies do
# SYSTEM OPERATIONS: Allow CRUD operations without actor
# SYSTEM OPERATIONS: Allow CRUD operations without actor (TEST ENVIRONMENT ONLY)
# In test: All operations allowed (for test fixtures)
# In production: Only :create and :read allowed (enforced by NoActor.check)
# :read is needed for internal Ash lookups (e.g., relationship validation during user creation).
# In production/dev: ALL operations denied without actor (fail-closed for security)
# NoActor.check uses compile-time environment detection to prevent security issues
bypass action_type([:create, :read, :update, :destroy]) do
description "Allow system operations without actor (seeds, tests, internal lookups)"
description "Allow system operations without actor (test environment only)"
authorize_if Mv.Authorization.Checks.NoActor
end
@ -399,8 +399,13 @@ defmodule Mv.Membership.Member do
user_id = user_arg[:id]
current_member_id = changeset.data.id
# Get actor from changeset context for authorization
# If no actor is present, this will fail in production (fail-closed)
actor = Map.get(changeset.context || %{}, :actor)
# Check the current state of the user in the database
case Ash.get(Mv.Accounts.User, user_id) do
# Pass actor to ensure proper authorization (User might have policies in future)
case Ash.get(Mv.Accounts.User, user_id, actor: actor) do
# User is free to be linked
{:ok, %{member_id: nil}} ->
:ok