Limen.Context (limen v0.2.1)

Copy Markdown View Source

Everything Limen knows about a request while it is being evaluated.

A context is built once per request by Limen.Plug, for one Limen.Instance, which it carries so that everything downstream reads its configuration and state without looking anything up. Identity fields (client_ip, prefix, ja4, user_agent) are always populated; the signals map is filled by the Limen.Signal modules a policy depends on.

Limen.Signal.identify/2 reads the identity headers in one pass over the request headers, and with them the ones later stages need: fetch_dest (the sec-fetch-dest header) and cookie_headers (the values of the cookie headers, in order, unparsed).

via_proxy tells whether client_ip came from a forwarding header set by a trusted proxy (see Limen.Signal.ClientIP); a request from a trusted proxy that carried no such header is the proxy's own.

mode, when set, is the request's own mode, overriding every other (see Limen.Test.put_mode/2, which sets the :limen_mode private connection field); it is nil otherwise.

facts are what the application knows about the request and Limen cannot, such as whether the client is signed in. The application states them with Limen.put_facts/2 before Limen.Plug runs (or with Limen.LiveView.put_facts/2 and the :facts option of Limen.Socket.check/3 for sockets), and policies read them with fact/1.

Every signal value and fact that ends up in a context is copied into the Limen.Decision record, so a decision can always be explained from the values that produced it.

Summary

Functions

Returns the fact key the application stated, or nil.

Builds a context for instance from a Plug.Conn, without resolving identity.

Returns the first value of the request header name, or nil.

The identity of the client, as recorded in decisions and bound into tokens.

Stores a signal value and, optionally, the evidence that produced it.

Stores several signal values at once, with evidence for some of them.

Returns the value of signal key, or nil when it was not collected.

Types

prefix()

@type prefix() :: Limen.IP.prefix()

t()

@type t() :: %Limen.Context{
  client_ip: :inet.ip_address() | nil,
  cookie_headers: [String.t()],
  evidence: %{optional(atom()) => term()},
  facts: %{optional(atom()) => term()},
  fetch_dest: String.t() | nil,
  headers: [{String.t(), String.t()}],
  host: String.t(),
  instance: Limen.Instance.t() | nil,
  ja4: String.t() | nil,
  method: String.t(),
  mode: :dry_run | :enforce | nil,
  monotonic: integer(),
  now: integer(),
  path: String.t(),
  peer_ip: :inet.ip_address() | nil,
  prefix: prefix() | nil,
  query: String.t(),
  rates: %{optional(term()) => non_neg_integer() | tuple()},
  scheme: :http | :https,
  signals: %{optional(atom()) => term()},
  user_agent: String.t() | nil,
  via_proxy: boolean()
}

Functions

fact(context, key)

@spec fact(t(), atom()) :: term()

Returns the fact key the application stated, or nil.

from_conn(conn, instance \\ nil)

@spec from_conn(Plug.Conn.t(), Limen.Instance.t() | nil) :: t()

Builds a context for instance from a Plug.Conn, without resolving identity.

now is the system time in milliseconds, used for time windows and token expiry; monotonic is monotonic time in microseconds, used for limits. Tests can pin both with the :limen_now and :limen_monotonic private connection fields, and its mode with :limen_mode. Facts come from Limen.put_facts/2.

header(context, name)

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

Returns the first value of the request header name, or nil.

name must be lowercase, as Plug normalises header names.

identity(ctx)

@spec identity(t()) :: map()

The identity of the client, as recorded in decisions and bound into tokens.

put_signal(ctx, key, value, evidence \\ nil)

@spec put_signal(t(), atom(), term(), term()) :: t()

Stores a signal value and, optionally, the evidence that produced it.

put_signals(ctx, values, why \\ %{})

@spec put_signals(t(), map(), map()) :: t()

Stores several signal values at once, with evidence for some of them.

Equivalent to calling put_signal/4 for each value, in one update of the context.

signal(ctx, key)

@spec signal(t(), atom()) :: term()

Returns the value of signal key, or nil when it was not collected.