create-account flow #
Status: implemented. /signup is the resident-facing account creation page,
served from src/internal/signup.zig. This document records the boundary the
page is built against and the product decisions that shaped it.
current server surface #
ZDS already implements the protocol account-creation backend:
com.atproto.server.describeServeradvertises hosted handle suffixes and whether an invite is required.com.atproto.server.reserveSigningKeyreserves an account signing key.com.atproto.server.createAccountacceptshandle,email,password,inviteCode, and optionalsigningKey.- A normal new account gets a signing key, a PLC genesis operation, durable account/repo state, and an initial access/refresh session.
- Migration creation for an existing DID is a distinct, service-authenticated path used by tools such as PDS Moover.
- Invite validation and consumption occur in the same SQLite transaction as account creation.
The production instance advertises .pds.zat.dev, requires an invite, sends
mail through Comail, and has wildcard DNS/TLS for hosted account handles.
The resident-facing create-account page is /signup
(src/internal/signup.zig). The root page links to it, and the /account hub
adopts the session it hands off. The root page, OAuth login, and /account
management hub are server-rendered in src/internal/account.zig; XRPC dispatch
is in src/atproto/server.zig, route metadata is in src/http/router.zig, and
account/invite persistence is in src/storage/store.zig.
invariants for a UI #
A UI should be a client of the existing XRPC behavior, not a second account creation implementation. In particular:
- Fetch
describeServer; do not hard-code invite or handle-domain policy. - Submit the complete handle selected by the user and enforce one advertised suffix. Do not infer that the bare PDS hostname is an account suffix.
- Never log or retain the account password, invite code, session tokens, or PLC key material in browser-visible diagnostics.
- Preserve the server's XRPC error code and message so invalid invites, handles, existing accounts, PLC failures, and transient server failures are not all rendered as the same error.
- Treat a successful response as a newly authenticated session and hand it to the resident account experience deliberately.
- Keep migration out of the normal sign-up form. It has different identity and authorization semantics.
Account creation is security-sensitive and mutates PLC plus local persistent state. Add an end-to-end smoke case for the browser flow rather than relying only on DOM tests. Use a disposable local database for destructive tests and a fresh invite for each attempted creation.
product decisions #
Decisions made for /signup (layout studied against selfhosted.social's join
page):
- the page asks for a short name; the suffix comes from
describeServerand is rendered as an attached segment with a live "you'll be name.suffix" preview (a select appears if more than one suffix is advertised) - the invite is a visible form field, shown only when
inviteCodeRequired, and prefillable via/signup?code=...so invites can be shared as links - on success the session is handed to
/accountvia a one-shotsessionStoragekey (zds-signup-session); email verification and passkey registration are offered there by the existing manage and security sections - migration is kept out of the form as a notice panel linking to PDS Moover
- the page carries a short honest note that this is an experimental PDS
checks and references #
Before changing account semantics, compare the current AT Protocol account lexicons and both mature references listed in references. Relevant local guides are invite codes, account security, passkeys, Comail, and production deployment.
At minimum, retain the existing just smoke coverage and add browser-flow
coverage for validation errors and successful creation. Browser-reported
failures require the actual request, response, and server log evidence before
the server behavior is changed.