Deploying a Matey homeserver (Tuwunel + broker) #
This directory deploys the spec's Strategy A on Tuwunel (README §5.2, Appendix B): Tuwunel's built-in next-generation OIDC server delegates user authentication to the Matey broker, which authenticates users via AT Protocol OAuth and asserts their DID back to Tuwunel. No MAS is involved.
Matrix client ──OAuth──▶ Tuwunel (own OIDC AS) ──OIDC──▶ matey-broker ──atproto OAuth──▶ user's authserver/PDS
│
sub = DID, preferred_username,
name, picture claims
Prerequisites #
- A host running Docker with ports 80, 443, and 8448 reachable.
- Two DNS names pointing at it: the Matrix server name (e.g.
matrix.example.org) and the broker host (e.g.matey-auth.example.org). Caddy obtains TLS certificates automatically. - The Matrix server name is permanent: changing it later means wiping the Tuwunel database.
Steps #
cd deploy
cp .env.example .env
$EDITOR .env # hosts + a fresh BROKER_RP_CLIENT_SECRET
docker compose up -d --build
Smoke checks:
curl https://$AUTH_HOST/.well-known/openid-configuration # broker OIDC discovery
curl https://$AUTH_HOST/atproto/client-metadata.json # broker as atproto client
curl https://$MATRIX_HOST/_matrix/client/versions # Tuwunel C–S API
curl https://$MATRIX_HOST/_matrix/client/v1/auth_metadata # next-gen auth advertised
Then point the example app (counter/) or any next-gen-auth Matrix
client at the server. Logging in walks: Matrix client → Tuwunel authorize →
"AT Protocol" provider → broker (handle form) → your PDS → back, with an
account provisioned on first login.
What to know about this topology #
-
The broker always shows its handle form. Tuwunel does not forward
login_hintto upstream providers (spec §7 item 11), so even when the Matey client resolved the user's DID already, the user types their handle once more at the broker. MAS deployments forward the hint and skip the form. -
Localparts. The broker emits
preferred_usernamealready shaped (did.plc.…by default,MATEY_LOCALPART_STYLE=handlefor vanity names); Tuwunel derives the localpart from it. Do not enabletrustedor customuserid_claimson the provider unless you know what you're doing —on_conflict-style auto-linking is an account-takeover vector (spec §8). -
Linking an existing Tuwunel account (spec §5.4.2) uses the admin room:
!admin query oauth associate <provider_id> @alice:matrix.example.org \ --claim sub=did:plc:…Pending approvals are held in memory; the user must log in via the AT provider before the next server restart. (On MAS this is self-serve via the account UI instead.)
-
The DID profile field. The reference broker asserts identity claims but homeserver-side writing of
com.berjon.matey.did(spec §3.3.1 server-managed mode) is not yet wired into Tuwunel provisioning; the example app writes the field from the client after login, which default Tuwunel permits. Track this as spec §7 item — a Tuwunel/MAS provisioning hook is the clean fix. -
Legacy clients get AT login too: the same provider serves
m.login.sso, so Element-classic-era clients can also authenticate via atproto (without any Matey mooring logic). -
Broker state is in-memory (in-flight logins only; no user data). A restart aborts in-flight sign-ins, nothing else. Keys persist in the
broker-datavolume.
Synapse + MAS route #
Prefer MAS semantics (login_hint forwarding, self-serve account linking,
claims_imports templates)? Run Synapse + MAS instead and register the broker
as a MAS upstream provider — the exact YAML is in the spec (README §5.2). The
broker is identical in both topologies.
Local development (no domains, no TLS) #
The broker runs on plain HTTP with MATEY_DEV=1 (uses the atproto loopback
client; only works against PDSes reachable from your machine, and the
whole flow must stay on localhost). Combined with a local Tuwunel
(TUWUNEL_ALLOW_REGISTRATION, plain HTTP listener) this gives a
laptop-only stack:
cd broker
MATEY_DEV=1 MATEY_BROKER_CLIENT_ID=tuwunel MATEY_BROKER_CLIENT_SECRET=dev \
MATEY_BROKER_REDIRECT_URIS=http://localhost:8008/_matrix/client/unstable/login/sso/callback/tuwunel \
pnpm start
Real atproto authserver interactions require the broker to be reachable at a public HTTPS URL (atproto client metadata is fetched by the user's authserver), so end-to-end testing against real PDSes needs the full deployment or a tunnel.