defmodule Mv.Authorization.Checks.NoActor do @moduledoc """ Custom Ash Policy Check that allows actions when no actor is present. **IMPORTANT:** This check ONLY works in test environment for security reasons. In production/dev, ALL operations without an actor are denied. ## Security Note This check uses compile-time environment detection to prevent accidental security issues in production. In production, ALL operations (including :create and :read) will be denied if no actor is present. For seeds and system operations in production, use an admin actor instead: admin_user = get_admin_user() Ash.create!(resource, attrs, actor: admin_user) ## Usage in Policies policies do # Allow system operations without actor (TEST ENVIRONMENT ONLY) # In test: All operations allowed # In production: ALL operations denied (fail-closed) bypass action_type([:create, :read, :update, :destroy]) do authorize_if NoActor end # Check permissions when actor is present policy action_type([:read, :create, :update, :destroy]) do authorize_if HasPermission end end ## Behavior - In test environment: Returns `true` when actor is nil (allows all operations) - In production/dev: Returns `false` when actor is nil (denies all operations - fail-closed) - Returns `false` when actor is present (delegates to other policies) """ use Ash.Policy.SimpleCheck # Compile-time check: Only allow no-actor bypass in test environment # SECURITY: This must ONLY be true in test.exs, never in prod/dev # Using compile_env instead of Mix.env() for release-safety @allow_no_actor_bypass Application.compile_env(:mv, :allow_no_actor_bypass, false) @impl true def describe(_opts) do if @allow_no_actor_bypass do "allows actions when no actor is present (test environment only)" else "denies all actions when no actor is present (production/dev - fail-closed)" end end @impl true def match?(nil, _context, _opts) do # Actor is nil # SECURITY: Only allow if compile_env flag is set (test.exs only) # No runtime Mix.env() check - fail-closed by default (false) @allow_no_actor_bypass end def match?(_actor, _context, _opts) do # Actor is present - don't match (let other policies decide) false end end