OapiCodemode.Credentials behaviour (oapi_codemode v0.5.0)

Copy Markdown View Source

Host-implemented credential resolution, library-implemented attachment.

The host resolves what to attach, per request; the spec's securityScheme dictates how it is attached. Credential values never enter the sandbox or the transcript.

Refresh has two modes, and a host can use either or both:

  • Proactiveresolve/4 runs on every request, so a host that knows a token's expiry refreshes inside resolve/4 before handing it back.
  • Reactiveunauthorized/4 (optional) is called when the upstream answers 401 to a credential resolve/4 supplied: for revoked tokens, server-side session resets, and the many APIs whose expires_in cannot be trusted. Returning {:retry, credential} re-sends the identical request once with the new credential.

Summary

Types

Where the request is going, resolved before credential attachment.

Callbacks

Resolve a credential for one request. context is the opaque identity map the host passed into the execute handler (tenant, user, org).

Called once when the upstream answers 401 after a credential from resolve/4 was attached. Return {:retry, credential} to have the library re-attach the new credential and re-send the same request exactly once; return :pass to hand the 401 back unchanged. Same error contract as resolve/4: a binary {:error, msg} crosses verbatim; any non-binary reason is logged and replaced with a fixed string. If this callback raises/exits/returns garbage, the ORIGINAL 401 response is returned (never converted into a transport error).

Functions

Turn a resolved credential into headers and query params for one request.

Types

credential()

@type credential() ::
  {:bearer, String.t()}
  | {:basic, String.t(), String.t()}
  | {:api_key, String.t()}
  | :none

request_info()

@type request_info() :: %{
  method: String.t(),
  base_url: String.t(),
  host: String.t() | nil,
  path: String.t()
}

Where the request is going, resolved before credential attachment.

path is the OpenAPI path template — path params are not substituted (e.g. "/pets/{id}", not "/pets/42"). base_url may itself include a path prefix (e.g. "https://api.example.com/v1"); the full wire path is base_url's path segment concatenated with the substituted path, not path alone. method is always lowercase ("get", not "GET").

Callbacks

resolve(api_name, scheme, request, context)

@callback resolve(
  api_name :: String.t(),
  scheme :: map() | nil,
  request :: request_info(),
  context :: map()
) :: {:ok, credential()} | {:error, term()}

Resolve a credential for one request. context is the opaque identity map the host passed into the execute handler (tenant, user, org).

Error contract: a binary {:error, message} crosses back to the sandbox/model verbatim — hosts must keep secrets out of binary error messages. Any non-binary {:error, reason} (e.g. {:expired, token}) is logged in full and replaced with a fixed, redacted string before it reaches the sandbox.

unauthorized(api_name, scheme, request, context)

(optional)
@callback unauthorized(
  api_name :: String.t(),
  scheme :: map() | nil,
  request :: request_info(),
  context :: map()
) :: {:retry, credential()} | :pass | {:error, term()}

Called once when the upstream answers 401 after a credential from resolve/4 was attached. Return {:retry, credential} to have the library re-attach the new credential and re-send the same request exactly once; return :pass to hand the 401 back unchanged. Same error contract as resolve/4: a binary {:error, msg} crosses verbatim; any non-binary reason is logged and replaced with a fixed string. If this callback raises/exits/returns garbage, the ORIGINAL 401 response is returned (never converted into a transport error).

Functions

attach(scheme, credential)

@spec attach(map() | nil, credential()) ::
  {:ok, %{headers: [{String.t(), String.t()}], query: map()}}
  | {:error, String.t()}

Turn a resolved credential into headers and query params for one request.

Credential values are screened for non-printable bytes first: a secret with a stray newline (the classic env-var-with-trailing-\n) would otherwise reach Mint, whose invalid_header_value error embeds the raw value in its message and leaks it into whatever logs that message. Errors from here never echo the credential.