Remove NoActor bypass from User and Member policies

This removes the NoActor bypass that was masking authorization bugs in tests.
All operations now require an explicit actor for authorization.
This commit is contained in:
Moritz 2026-01-23 20:00:18 +01:00
parent b6992f8488
commit e72b7ab2e8
Signed by: moritz
GPG key ID: 1020A035E5DD0824
2 changed files with 6 additions and 17 deletions

View file

@ -275,12 +275,6 @@ defmodule Mv.Accounts.User do
authorize_if always()
end
# NoActor bypass (test fixtures only, see no_actor.ex)
bypass action_type([:create, :read, :update, :destroy]) do
description "Allow system operations without actor (test environment only)"
authorize_if Mv.Authorization.Checks.NoActor
end
# READ bypass for list queries (scope :own via expr)
bypass action_type(:read) do
description "Users can always read their own account"

View file

@ -303,15 +303,6 @@ 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 (TEST ENVIRONMENT ONLY)
# In test: All operations allowed (for test fixtures)
# 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 (test environment only)"
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
@ -403,8 +394,12 @@ defmodule Mv.Membership.Member do
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)
# Use system_actor as fallback if no actor is present (for systemic operations)
actor =
case Map.get(changeset.context || %{}, :actor) do
nil -> Mv.Helpers.SystemActor.get_system_actor()
actor -> actor
end
# Check the current state of the user in the database
# Check if authorization is disabled in the parent operation's context