# `PhoenixGenApi.Security`
[🔗](https://github.com/ohhi-vn/phoenix_gen_api/blob/main/lib/phoenix_gen_api/security.ex#L1)

Provides security utilities for PhoenixGenApi.

## Features

1. **Admin gate** — fail-closed authorization for dangerous runtime operations
   (toggling `detail_error`, updating rate limit config, pushing configs).

2. **Push token validation** — constant-time comparison to authenticate
   push requests from remote nodes.

3. **MFA allowlist validation** — restricts which `{module, function, args}`
   tuples can be registered as function configurations, preventing a
   compromised node from registering dangerous MFAs (e.g. `:os.cmd`).

## Configuration

All checks are **opt-in and backward compatible**. If the relevant application
environment variables are not set, the checks are skipped:

    config :phoenix_gen_api,
      # Admin actions allowlist (fail-closed: default denies everything)
      admin_actions: [:push_config],

      # Push token — when set, push requests must include a matching token
      push_token: "my-secret-token",

      # MFA allowlist — when set, only listed {module, function} pairs are allowed.
      # Module-level entries (just an atom) allow all functions in that module.
      mfa_allowlist: [
        MyApp.UserService,
        {MyApp.OrderService, :create_order}
      ]

## Hardcoded Denylist

The following modules are **always blocked** unless explicitly allowed:
`:os`, `:file`, `:code`, `:erlang`, `:net`, `:rpc`, `:global`, `:inet`.

## Environment Recommendations

- **Development**: You may enable all admin actions for convenience.
- **Production**: Enable only what's needed. Configure `push_token` and
  `mfa_allowlist` to restrict push sources and allowed function targets.

# `admin_action`

```elixir
@type admin_action() ::
  :change_detail_error
  | :update_rate_limit_config
  | :push_config
  | :enable_tracing
  | :disable_tracing
```

# `admin_action_allowed?`

```elixir
@spec admin_action_allowed?(admin_action()) :: boolean()
```

Checks whether a given admin action is currently permitted.

Returns `true` if the action is in the configured allowlist, `false` otherwise.
When denied, a warning is logged.

## Examples

    iex> PhoenixGenApi.Security.admin_action_allowed?(:update_rate_limit_config)
    false

    iex> PhoenixGenApi.Security.admin_action_allowed?(:change_detail_error)
    false

# `valid_push_token?`

```elixir
@spec valid_push_token?(nil | String.t() | binary()) :: boolean()
```

Validates a push token using constant-time comparison.

Returns `true` if:
  - No `:push_token` is configured (backward compatible — push allowed without token)
  - The provided token matches the configured token

Returns `false` if a token is configured but the provided token doesn't match
or is missing.

Uses constant-time comparison to prevent timing attacks.

# `validate_mfa`

```elixir
@spec validate_mfa({module(), atom(), list()}) ::
  :ok | {:error, {:mfa_not_allowed, term()}}
```

Validates that an MFA tuple is allowed by the configured allowlist and not
in the hardcoded denylist.

## Parameters

  - `mfa` - A `{module, function, args}` tuple

## Returns

  - `:ok` if the MFA is allowed
  - `{:error, {:mfa_not_allowed, mfa}}` if the MFA is not allowed

## Behavior

  - If no `:mfa_allowlist` is configured, all MFAs pass the allowlist check
    (backward compatible) — but the hardcoded denylist is still enforced.
  - If `:mfa_allowlist` IS configured, the `{module, function}` pair must match
    an entry. Entries can be:
    - A module atom (e.g. `MyApp.UserService`) — allows all functions in that module
    - A `{module, function}` tuple (e.g. `{MyApp.OrderService, :create_order}`)
  - Modules in the hardcoded denylist (`:os`, `:file`, `:code`, `:erlang`,
    `:net`, `:rpc`, `:global`, `:inet`) are ALWAYS blocked unless the
    allowlist explicitly includes them.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
