Atex.XRPC.Router (atex v0.9.1)

View Source

Routing utilities for building ATProto XRPC server endpoints.

Provides the query/3 and procedure/3 macros that expand to Plug.Router.get/3 and Plug.Router.post/3 respectively, with built-in handling for:

Usage

defmodule MyAPI do
  use Plug.Router
  use Atex.XRPC.Router

  plug :match
  plug :dispatch

  # Matches GET /xrpc/com.example.getProfile
  query "com.example.getProfile" do
    send_resp(conn, 200, "ok")
  end

  # Matches POST /xrpc/com.example.createPost, enforces auth
  procedure Com.Example.CreatePost, require_auth: true do
    # conn.assigns[:params] and conn.assigns[:body] are populated
    # when the lexicon module defines Params/Input submodules
    send_resp(conn, 200, "created")
  end
end

Authentication

Authentication uses Atex.ServiceAuth.validate_conn/2. The audience (aud) is read from conn.private[:xrpc_aud], which is populated automatically by Atex.XRPC.Router.AudPlug that reads :service_did from app config. To disable automatic plug injection:

use Atex.XRPC.Router, plug_aud: false

When require_auth: true is passed to a route macro, a missing or invalid token halts with a 401 response. Otherwise auth is attempted softly - on success the decoded JWT is placed at conn.assigns[:current_jwt], on failure the conn is left untouched.

Validation

When a lexicon module atom is passed, the macro checks at compile time whether <Module>.Params and/or <Module>.Input exist. If they do, their from_json/1 is called at request time:

  • Valid params → conn.assigns[:params]
  • Valid body → conn.assigns[:body]
  • Either failing → halts with a 400 response

Summary

Functions

Defines a POST route for an XRPC procedure.

Defines a GET route for an XRPC query.

Functions

procedure(nsid_or_module, opts \\ [], list)

(macro)

Defines a POST route for an XRPC procedure.

The first argument is either:

  • A plain string NSID (e.g. "com.example.createPost") - validated at compile time.
  • A lexicon module atom (e.g. Com.Example.CreatePost) - the NSID is fetched from module.id() at compile time.

Options

  • :require_auth - when true, requests without a valid service auth token are rejected with a 401. Defaults to false.

Assigns

  • :current_jwt - the decoded JOSE.JWT struct, set on successful auth.
  • :params - validated params struct, set when the lexicon module defines a Params submodule.
  • :body - validated input struct, set when the lexicon module defines an Input submodule.

Non-JSON payloads

If a lexicon procedure defines an input with an encoding without an object schema, this will simply validate the incoming Content-Type header against the requested encoding. Nothing happens on success, you will need to read conn's body as usual and do extra validation yourself, as clients may lie about their content. Wildcards are handled correctly as per the atproto documentation.

Examples

procedure "com.example.createPost", require_auth: true do
  send_resp(conn, 200, "created")
end

procedure Com.Example.CreatePost, require_auth: true do
  # conn.assigns[:body] contains the validated Input struct
  send_resp(conn, 200, "created")
end

query(nsid_or_module, opts \\ [], list)

(macro)

Defines a GET route for an XRPC query.

The first argument is either:

  • A plain string NSID (e.g. "com.example.getProfile") - validated at compile time.
  • A lexicon module atom (e.g. Com.Example.GetProfile) - the NSID is fetched from module.id() at compile time.

Options

  • :require_auth - when true, requests without a valid service auth token are rejected with a 401. Defaults to false.

Assigns

  • :current_jwt - the decoded JOSE.JWT struct, set on successful auth.
  • :params - validated params struct, set when the lexicon module defines a Params submodule.

Examples

query "com.example.getTimeline", require_auth: true do
  send_resp(conn, 200, "ok")
end

query Com.Example.GetTimeline do
  send_resp(conn, 200, "ok")
end