Noizu.MCP.Auth.Server.Errors (Noizu MCP v0.1.6)

Copy Markdown View Source

OAuth error codes and rendering (RFC 6749 §4.1.2.1/§5.2, RFC 7591 §3.2.2, RFC 8707 §2, RFC 6750 §3.1).

error_description never reflects input

Every description here is a fixed string chosen by the code, keyed off the error code. Nothing a client sent is echoed into a response body or a redirect query. That closes two things at once: reflected XSS on the rendered error page a browser will see, and the oracle where a description tells an attacker which of several checks their probe tripped.

When more detail is genuinely needed, it goes to the log with a correlation id — annotate/2 carries a private reason that renders nowhere.

iex> Noizu.MCP.Auth.Server.Errors.new(:invalid_grant) |> Noizu.MCP.Auth.Server.Errors.to_map()
%{"error" => "invalid_grant", "error_description" => "The grant is invalid, expired, or has already been used."}

Summary

Functions

Attach a private reason for logging. Never rendered.

Every code this module renders.

The canned description for a code.

Build an error. An unrecognized code becomes :server_error — a code that is not in the catalog is a bug here, not something to render verbatim.

Append an error to a redirect URI's query.

HTTP status for an error.

JSON body for a token/registration/revocation error response.

URL-encoded query fragment for a redirect error response.

Attach the state to echo on a redirect error response.

Types

code()

@type code() ::
  :invalid_request
  | :invalid_client
  | :invalid_grant
  | :unauthorized_client
  | :unsupported_grant_type
  | :unsupported_response_type
  | :invalid_scope
  | :invalid_target
  | :access_denied
  | :server_error
  | :temporarily_unavailable
  | :invalid_redirect_uri
  | :invalid_client_metadata
  | :invalid_token
  | :insufficient_scope
  | :login_required
  | :consent_required
  | :interaction_required

t()

@type t() :: %Noizu.MCP.Auth.Server.Errors{
  code: code(),
  reason: term(),
  state: String.t() | nil,
  status: 200..599
}

Functions

annotate(error, reason)

@spec annotate(t(), term()) :: t()

Attach a private reason for logging. Never rendered.

codes()

@spec codes() :: [code()]

Every code this module renders.

description(code)

@spec description(t() | code()) :: String.t()

The canned description for a code.

new(code, opts \\ [])

@spec new(
  code() | String.t(),
  keyword()
) :: t()

Build an error. An unrecognized code becomes :server_error — a code that is not in the catalog is a bug here, not something to render verbatim.

Options: :state (echoed back on a redirect response, per RFC 6749), :status (override), :reason (private, logged never rendered).

redirect_url(redirect_uri, error)

@spec redirect_url(String.t(), t()) :: String.t()

Append an error to a redirect URI's query.

Only ever called with a redirect_uri that already passed Noizu.MCP.Auth.Server.RedirectURI validation against the resolved client — an unresolved client or an unvalidated URI must be rendered, never redirected to, or the authorization endpoint becomes an open redirector.

status(code)

@spec status(t() | code()) :: 200..599

HTTP status for an error.

to_map(error)

@spec to_map(t() | code()) :: %{required(String.t()) => String.t()}

JSON body for a token/registration/revocation error response.

to_query(error)

@spec to_query(t() | code()) :: String.t()

URL-encoded query fragment for a redirect error response.

with_state(error, state)

@spec with_state(t(), String.t() | nil) :: t()

Attach the state to echo on a redirect error response.