RpcElixir.Plug (elixir_ts_rpc v0.0.1)

Copy Markdown View Source

HTTP transport adapter for RpcElixir implemented as a Plug.

Mounts a router module at a configurable path prefix, decodes JSON bodies, dispatches to the procedure pipeline, drains response cookies and headers onto the conn, and renders JSON responses.

Options

  • :router (required) — module using RpcElixir.Router.
  • :path_prefix (optional, default "/rpc") — the prefix to strip from the request path before procedure dispatch. A request to POST /rpc/users.get dispatches procedure "users.get".
  • :ctx_builder (optional) — (Plug.Conn.t() -> RpcElixir.Context.t()). When provided, the returned context is used as the base, but the transport always overwrites its :req field with conn-derived metadata (cookies, headers, remote_ip, session), so those fields are always present regardless of what the builder returns.
  • :max_body_size (optional, default 8 MB) — maximum request body size in bytes. Bodies exceeding this are rejected with 413 :payload_too_large.
  • :max_body_depth (optional, default 64) — maximum structural nesting depth of the decoded JSON body. Payloads nested deeper are rejected with 400 :input_validation_failed before validation runs, bounding stack and CPU use that the byte cap alone cannot.
  • :require_content_type (optional, default true) — when true, requests must carry a content-type of application/json (charset/params allowed); otherwise they are rejected with 415 :unsupported_media_type. See ## Security / CSRF.
  • :allowed_origins (optional, default nil = disabled) — when set to a list of origin strings, a request carrying an origin header not in the list is rejected with 403 :forbidden. See ## Security / CSRF.

Security / CSRF

This adapter dispatches state-changing RPC over POST and can pair with cookie-based sessions (see ## Session integration). That combination is a CSRF surface: a cross-site page can auto-submit a form to an RPC endpoint and the browser will attach the session cookie, so without a defense an attacker could trigger authenticated calls.

Two mitigations are enforced here:

  • Content-Type enforcement (:require_content_type, default true). Requiring application/json means a request can no longer be a "simple" cross-site request — browsers must send a CORS preflight that the server never approves, and HTML forms (which can only send application/x-www-form-urlencoded, multipart/form-data, or text/plain) are blocked outright. This is the primary CSRF defense.
  • Origin allow-listing (:allowed_origins, default disabled). When configured, requests whose origin header is present but not allow-listed are rejected with 403. This is defense-in-depth for browsers that send Origin on state-changing requests.

The bundled JS client always sends Content-Type: application/json, so the default-on enforcement does not affect legitimate usage.

Session integration

To enable session support, configure Plug.Session earlier in your pipeline. The adapter reads the session into ctx.req.session and drains Resolution.resp_session back into the session after dispatch.

defmodule MyApp.Router do
  use Plug.Builder

  plug Plug.Session,
    store: :cookie,
    key: "_my_app_session",
    signing_salt: "my_salt"

  plug :fetch_session
  plug RpcElixir.Plug, router: MyApp.RpcRouter
end

Within middleware, use Resolution.put_session/3, delete_session/2, or clear_session/1 to modify the session. Read session data via res.ctx.req[:session].

Example

plug RpcElixir.Plug, router: MyApp.RpcRouter

Response draining order

After dispatch, session mutations, cookies, and headers are all applied to the conn before render_result writes the response body. This ordering is required by Plug: session and cookie mutations must precede the response.