Packages
The shared sans-io core of the gleam-atproto client packages: XRPC error vocabulary and response handling, plus the OAuth effect kernel.
Current section
Files
Jump to
Current section
Files
atproto_core
README.md
README.md
# atproto_core
The shared sans-io core of the gleam-atproto client packages: the XRPC
error vocabulary and response handling, plus the OAuth effect kernel that
`atproto_client` (sync, erlang) and `atproto_browser` (async, javascript)
both interpret with their own runners.
This package performs no IO and depends on no HTTP backend. Unless you are
building a runner for a new target, you want [`atproto_client`](https://hex.pm/packages/atproto_client)
or `atproto_browser` instead; they re-surface everything here behind their
own entry points.
## Modules
| Module | What it does |
| ------------------------------ | ------------------------------------------------------------------ |
| `atproto_core/xrpc` | `TransportError`, `XrpcError`, `describe`, `parse`, `check_ok` |
| `atproto_core/remote` | `Origin`/`Url`: public https hosts; opt-in `AllowLocalDevelopment` |
| `atproto_core/did` | `Did` and native DID document resolution (PLC, `did:web`) |
| `atproto_core/identity` | Slingshot `resolveMiniDoc` (a hint, never trusted for auth) |
| `atproto_core/oauth/effect` | The `Effect` type: `Fetch`/`FetchBits`/`DpopProof`/`Done` steps |
| `atproto_core/oauth/transport` | Form POST with DPoP, client auth, and the `use_dpop_nonce` retry |
| `atproto_core/oauth/metadata` | Verified protected-resource and authorization-server metadata |
| `atproto_core/oauth/flow` | `start` (resolve, discover, PAR) and `callback` (verify, exchange) |
| `atproto_core/oauth/session` | `VerifiedSession`: tokens bound to a verified DID and issuer |
| `atproto_core/oauth/tokens` | Refresh and revocation of a `VerifiedSession` |
| `atproto_core/oauth/resource` | DPoP-signed requests, pinned to the session's PDS origin |
## The effect kernel in one paragraph
Every OAuth operation is a pure `Effect` value: a step asking for a fetch,
a bit-array fetch, or a DPoP proof, with a continuation for the answer. A
runner walks the value, performing real IO per step; the kernel never sees
a socket or a key. That is what lets one implementation of the nonce-retry
and token-exchange logic serve both a sync erlang client and an async
browser client, and it is why the whole kernel is unit-testable by stepping
effects by hand (see `test/oauth_effect_test.gleam`).
## What the OAuth kernel verifies
Every hop of discovery is attacker-influenced, so each is parsed and checked
before the next request is made:
1. The account is a DID. A handle goes through the resolver for its DID
only; the DID document (fetched natively from the PLC directory or the
`did:web` host) must list the handle in `alsoKnownAs`.
2. The PDS is the DID document's `#atproto_pds` service, parsed as a
`remote.Origin`.
3. The PDS's protected-resource metadata must name that PDS as `resource`.
4. The authorization server's metadata `issuer` must be exactly the issuer
it was fetched from, and it must advertise the atproto profile
capabilities.
5. `flow.callback` requires `state` and `iss`, then exchanges the code. The
token `sub` must be the flow's DID, `token_type` DPoP, the scope must
include `atproto`, and the DID must resolve back to the same issuer.
6. The access token is only ever sent to the session's PDS origin.
What parsing cannot decide is the transport's job: no redirects, bounded
response size and request time, and on a server, no connections to private,
loopback, link-local, or unique-local addresses after DNS resolution.