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

Provides permission checking functionality for API requests.

This module implements a flexible permission system that can verify whether a user
has the right to execute a specific request. Permissions can be disabled, or configured
to check that certain request arguments match the requesting user's identity.

## Permission Modes

### Disabled Permissions (`false`)
When `check_permission: false` is set in the FunConfig, all permission checks pass.
This is useful for public endpoints that don't require authentication or authorization.

### Any Authenticated (`:any_authenticated`)
When `check_permission: :any_authenticated` is configured, the system verifies that
the request has a valid `user_id`. Any authenticated user is allowed access.

### Argument-Based Permissions (`{:arg, arg_name}`)
When `check_permission: {:arg, arg_name}` is configured, the system verifies that
the value of the specified argument matches the request's `user_id`. This ensures
users can only access their own data.

For example, with `{:arg, "user_id"}`, a request from user "123" will only be
allowed if `request.args["user_id"] == "123"`.

When `arg_orders: :map`, the argument is first looked up at the top level of
`request.args`, then searched one level deep inside map values.

### Role-Based Permissions (`{:role, allowed_roles}`)
When `check_permission: {:role, allowed_roles}` is configured, the system verifies
that the user has one of the allowed roles. Roles are checked against the
`request.user_roles` field (must be a list of strings or atoms).

For example, with `{:role, ["admin", "moderator"]}`, only users with those roles
are allowed access.

### Custom Callback (`permission_callback`)
When `permission_callback: {module, function, args}` is set,
the callback takes precedence over `check_permission`. Called as
`apply(module, function, [request | args])`. Must return `true` or `false`.
Any other return value is treated as `false`. Exceptions are caught and treated
as `false` for safety.

## Examples

    # Public endpoint - no permission check
    config = %FunConfig{
      request_type: "get_public_data",
      check_permission: false
    }

    request = %Request{user_id: "any_user"}
    Permission.check_permission(request, config)
    # => true

    # Any authenticated user
    config = %FunConfig{
      request_type: "get_profile",
      check_permission: :any_authenticated
    }

    request = %Request{user_id: "user_123"}
    Permission.check_permission(request, config)
    # => true

    request = %Request{user_id: nil}
    Permission.check_permission(request, config)
    # => false

    # User-specific endpoint - must match user_id
    config = %FunConfig{
      request_type: "get_user_profile",
      check_permission: {:arg, "user_id"}
    }

    # This passes - user accessing their own data
    request = %Request{
      user_id: "user_123",
      args: %{"user_id" => "user_123"}
    }
    Permission.check_permission(request, config)
    # => true

    # This fails - user trying to access another user's data
    request = %Request{
      user_id: "user_123",
      args: %{"user_id" => "user_999"}
    }
    Permission.check_permission(request, config)
    # => false

    # Role-based access
    config = %FunConfig{
      request_type: "delete_user",
      check_permission: {:role, ["admin"]}
    }

    request = %Request{
      user_id: "user_123",
      user_roles: ["admin"]
    }
    Permission.check_permission(request, config)
    # => true

## Security Considerations

- Always use `check_permission!/2` in the execution path to raise on failures
- The `check_permission/2` function is non-raising and returns a boolean
- Missing arguments result in permission denial (returns `false`)
- Permission checks happen before argument validation and function execution
- All permission failures are logged for audit purposes
- Use specific permission modes rather than `false` when possible

### Securing `{:arg, arg_name}` Against user_id Override

When using `{:arg, arg_name}`, the `user_id` in `socket.assigns` **must** be set
by a verified authentication step in `Phoenix.Socket.connect/3` — never from
client payload. The `override_user_id` channel option (default `true`) only
applies when `socket.assigns.user_id` is a verified non-empty string; it will
**NOT** override with a client-supplied `user_id`.

Use `require_verified_user_id: true` (the default) in your channel to reject
unauthenticated requests **before** they reach permission checks. This prevents
requests with no verified `user_id` from ever entering the execution pipeline.
Set `require_verified_user_id: false` only for public endpoints that use
`check_permission: false`.

# `check_permission`

Exception raised when a permission check fails.

Contains details about the request and the permission mode that was denied.

# `check_permission!`

Checks permission and raises an exception if the check fails.

This is the raising version of `check_permission/2`. It should be used in the
request execution pipeline where permission failures should halt processing.

## Parameters

  - `request` - The `Request` struct containing user information and arguments
  - `fun_config` - The `FunConfig` struct with permission settings

## Returns

  - `nil` - Permission check passed (function returns nothing on success)

## Raises

  - `PermissionDenied` - If the permission check fails

## Examples

    request = %Request{
      user_id: "user_123",
      args: %{"user_id" => "user_999"}
    }

    config = %FunConfig{check_permission: {:arg, "user_id"}}

    # This will raise because user_ids don't match
    Permission.check_permission!(request, config)
    # ** (PhoenixGenApi.Permission.PermissionDenied) Permission denied...

## Notes

- Logs a warning with request details when permission is denied
- Always called before request execution in the Executor module
- Returns nothing on success (only side effect is potential exception)

---

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