EndPointBlank.BaseUrl (end_point_blank_elixir v0.6.0)

Copy Markdown

Resolves the base URL the caller used -- scheme, host and port -- from a Plug.Conn.

This reads the request headers directly rather than trusting conn.scheme, conn.host and conn.port. Plug has no notion of a trusted proxy at all unless the host application installs Plug.RewriteOn itself, so those three describe the internal hop and nothing else. More generally: Rack, Express, the servlet spec and Plug each resolve "host" differently (Rack takes the last X-Forwarded-Host hop, Express the first, WSGI and Plug neither), which is why the same request produced five different answers across the five clients.

Forwarded headers are honored when trust_proxy_headers is on, which it is by default, taking the LAST hop: host was already caller-controlled in every client (conn.host comes from the Host header), and behind a proxy that appends, the last value is the proxy's own observation rather than anything the caller planted. A directly-exposed deployment can pass false and get scheme, host and port from the conn and the Host header only.

The flag arrives as an argument rather than being read from EndPointBlank.Config here, so that this module stays configuration-free and both states are directly testable.

Summary

Functions

The hostname alone, for the authorize path.

Returns a map carrying only the fields that resolved to a usable value.

Functions

hostname(conn)

@spec hostname(Plug.Conn.t() | any()) :: String.t() | nil

The hostname alone, for the authorize path.

Deliberately not resolve/2's :host: reads the host header only, never the forwarded chain, whatever trust_proxy_headers is set to. The value feeds target_hostname and the access-token cache key, and the portal resolves an application environment from it -- a value matching no registered row is a hard 422 with no fallback, not a cache miss.

Composed from the same split_authority/1 and clean_host/1 pair resolve/2 uses, so lowercasing and shape and length validation are identical between the two; only the authority's source differs, plus the IPv6 fix-up below.

resolve(conn, trust_proxy_headers \\ true)

@spec resolve(Plug.Conn.t() | any(), boolean()) :: map()

Returns a map carrying only the fields that resolved to a usable value.

A field that could not be resolved is absent, never nil: the receiver has to be able to tell "this SDK did not report a port" from "the port is null".

With trust_proxy_headers false the three x-forwarded-* headers are not read at all, so the request is never treated as proxied and conn.scheme and conn.port stay evidence.