MailglassAdmin.Auth behaviour (MailglassAdmin v1.0.0)

Copy Markdown View Source

Stack-agnostic authorization seam for production operator access and future destructive actions.

This behaviour is the stable adopter-owned auth seam for mailglass_admin. If your app integrates with the operator surface, this is the module contract to depend on.

Adopters implement this behaviour and pass the module to mailglass_operator_routes/2. MailglassAdmin normalizes the return shape so later operator actions can rely on one server-side contract.

Sensitive operator actions stay adopter-owned. For example, an adopter may choose to require a recent reauthentication check before allowing :destructive_action:

def authorize(:destructive_action, %{actor: %{recent_auth_at: recent_auth_at}})
    when is_struct(recent_auth_at, DateTime) do
  if DateTime.diff(DateTime.utc_now(), recent_auth_at, :second) <= 900 do
    {:ok, %{subject_id: "operator-1", recent_auth_at: recent_auth_at}}
  else
    {:error, :stale_auth, %{message: "Recent authentication is required."}}
  end
end

The 900-second window above is an adopter example, not a library-owned constant or policy.

Summary

Types

action()

(since 0.1.0)
@type action() :: :operator_access | :destructive_action | atom()

actor()

(since 0.1.0)
@type actor() :: %{
  :subject_id => term(),
  optional(:tenant_id) => term() | nil,
  optional(:auth_method) => String.t() | atom() | nil,
  optional(:recent_auth_at) => DateTime.t() | nil
}

failure()

(since 0.1.0)
@type failure() :: {:error, failure_reason(), map()}

failure_reason()

(since 0.1.0)
@type failure_reason() :: :unauthorized | :stale_auth

result()

(since 0.1.0)
@type result() :: success() | failure()

success()

(since 0.1.0)
@type success() ::
  {:ok, actor()} | {:ok, %{:actor => actor(), optional(:assigns) => map()}}

Callbacks

authorize(action, context)

(since 0.1.0)
@callback authorize(action(), context :: map()) :: result()

Functions

authorize(module, action, context)

(since 0.1.0)
@spec authorize(module(), action(), map()) ::
  {:ok, %{actor: actor(), assigns: map()}} | failure()

session_actor(session)

(since 0.1.0)
@spec session_actor(map()) :: actor()