Pds: invite codes are only presence-checked — no store, validation, single-use semantics, or admin endpoints #36

Open
opened 2026-06-10 22:46:51 +00:00 by Grandiras · 0 comments
Owner

With PdsOptions.OpenRegistration = false, PdsService.CreateAccountAsync only checks that the invite code is non-empty (PdsService.cs:52-53) — any string passes:

if (!_options.OpenRegistration && string.IsNullOrEmpty(inviteCode))
    throw new PdsException("InvalidInviteCode", "An invite code is required to create an account.");

So "invite codes are required" is not actually enforceable with the package alone. There is no storage, validation, consumption (single-use semantics), or admin surface for codes.

While building Updraft's PDS container I had to implement all of this app-side:

  • An InviteCodes table + service with atomic claim/confirm/release (single-UPDATE claim so concurrent signups can't double-spend a code)
  • Terminal middleware that fully shadows POST /xrpc/com.atproto.server.createAccount (because the SDK's mapped endpoint would accept any non-empty string)
  • Admin endpoints: com.atproto.server.createInviteCode, com.atproto.admin.getInviteCodes, com.atproto.admin.disableInviteCodes with Basic admin auth

Suggestion: an IInviteCodeStore abstraction (with an in-memory default) that CreateAccountAsync consults + consumes when OpenRegistration == false, plus optional mapping of the standard invite admin XRPC endpoints (they exist in the com.atproto lexicons) guarded by a configurable admin credential. Happy to share our implementation as a starting point: https://git.grandiras.net/Grandiras/Updraft (src/Updraft.Pds).

With `PdsOptions.OpenRegistration = false`, `PdsService.CreateAccountAsync` only checks that the invite code is **non-empty** (PdsService.cs:52-53) — any string passes: ```csharp if (!_options.OpenRegistration && string.IsNullOrEmpty(inviteCode)) throw new PdsException("InvalidInviteCode", "An invite code is required to create an account."); ``` So "invite codes are required" is not actually enforceable with the package alone. There is no storage, validation, consumption (single-use semantics), or admin surface for codes. While building Updraft's PDS container I had to implement all of this app-side: - An `InviteCodes` table + service with atomic claim/confirm/release (single-UPDATE claim so concurrent signups can't double-spend a code) - Terminal middleware that fully shadows `POST /xrpc/com.atproto.server.createAccount` (because the SDK's mapped endpoint would accept any non-empty string) - Admin endpoints: `com.atproto.server.createInviteCode`, `com.atproto.admin.getInviteCodes`, `com.atproto.admin.disableInviteCodes` with Basic admin auth Suggestion: an `IInviteCodeStore` abstraction (with an in-memory default) that `CreateAccountAsync` consults + consumes when `OpenRegistration == false`, plus optional mapping of the standard invite admin XRPC endpoints (they exist in the com.atproto lexicons) guarded by a configurable admin credential. Happy to share our implementation as a starting point: https://git.grandiras.net/Grandiras/Updraft (src/Updraft.Pds).
Sign in to join this conversation.
No description provided.