net-kingdom/docs/public-registration-orchestration-contract.md
tegwick 96266b9371
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Resume user-engine integration rollout
2026-08-13 14:56:53 +02:00

6 KiB

Public registration orchestration contract

Status: implementation contract for NK-WP-0025-T02 and USER-WP-0022-T01/T02.

Boundary

user-engine owns the public registration state machine. It never accepts an LLDAP password or an LLDAP administrator credential. A verification service proves mailbox control; the existing identity-provisioner creates the ordinary LLDAP identity and returns a provider-owned, single-use password-setup URL.

The public browser is not an authenticated user-engine actor. It receives only opaque registration and verification handles. A verified applicant actor is constructed from consumed verification evidence and is authorized only for the registration instance named by that evidence.

Public flow

  1. POST /api/v1/public/registrations accepts username, email, display_name, client_id, and optional tenant_hint. Client and tenant values must be registered allow-list entries; browser-provided return URLs are rejected.
  2. The response is always the same 202 Accepted envelope for syntactically valid requests, whether the username/email is new, duplicated, throttled, or already pending. It contains no existence result.
  3. user-engine records an expiring registration intent and emits registration.verification_requested through its transactional outbox. The mail receiver obtains the recipient and an opaque verification handle; the general event receiver gets only recipient_present=true.
  4. POST /api/v1/public/registrations/verify consumes the opaque handle. Verification is single-use, expiry checked, purpose bound, and returns normalized mailbox evidence bound to the registration ID and submitted username.
  5. user-engine attaches the verified email factor, completes registration, and calls identity-provisioner using the registration ID as the stable idempotency key. Only the baseline ordinary-user group may be requested.
  6. The provisioner creates or resumes exactly one LLDAP identity, links the provider subject to the user-engine user, and returns its single-use password-setup URL. Provider collision is a reconciliation result, never a second privileged identity.
  7. After provider password setup, the browser starts a fresh KeyCape authorization-code/PKCE flow for the allow-listed client. Registration state is never accepted as an application session.

Required state

Registration intent persistence must include:

  • opaque registration ID and lifecycle status;
  • normalized username and email hash, with display values separately protected from general event/audit output;
  • registered client ID and tenant;
  • verification handle digest, issued/expiry/consumed timestamps, attempt counters, and purpose;
  • provisioning idempotency key and external provider result;
  • allow-listed post-setup authorization target identifier, not a raw URL;
  • correlation ID and redacted failure/compensation state.

No password, verification token plaintext, LLDAP admin credential, MFA seed, or provider bearer token may enter registration state, logs, audit, State Hub, or the general event lane.

Validation and abuse controls

  • Usernames use the LLDAP-compatible canonical grammar, length limit, and reserved-name list; case folding happens before uniqueness checks.
  • Email is normalized for comparison without provider-specific alias rewriting. Mailbox verification proves control but does not authorize linking to an existing user.
  • Rate limits apply per source network bucket, normalized email digest, and username digest. Responses and timing remain non-enumerating.
  • CSRF applies to browser form submission; JSON callers additionally require an allow-listed client registration and idempotency key.
  • Verification handles are random, high entropy, stored only as a digest, purpose-bound, expiring, and atomically consumed.
  • Duplicate email/username, expired/replayed verification, and provider conflict enter explicit reconciliation or terminal states. They never fall back to auto-linking by email.

Provider contract

The identity-provisioner request is the existing ProvisioningRequest plus a validated canonical username. The response remains ProvisioningResult with external_subject, status, and optional password_setup_url.

For public registration the adapter must guarantee:

  • the same idempotency key returns the same external subject;
  • baseline membership only; no tenant-admin or platform groups;
  • password setup is provider-owned, single-use, and expiring;
  • partial creation is resumable/reconcilable and observable without exposing credentials;
  • the external subject is linked before registration reports completion.

Activation gates

Public registration stays disabled until all of the following pass together:

  1. verification issuer and mail receiver conformance, including replay and expiry;
  2. user-engine persistence migration and anonymous-route abuse controls;
  3. flex-auth policy limiting the verified applicant actor to its registration;
  4. identity-provisioner canonical-username/idempotency conformance;
  5. KeyCape fresh-login return flow and client allow-list checks;
  6. disposable-user live tests and cleanup on railiance01.

Runtime configuration

The source implementation is fail closed and remains disabled unless all of these are configured together:

  • USER_ENGINE_PUBLIC_REGISTRATION=true;
  • USER_ENGINE_REGISTRATION_VERIFICATION_URL and its OpenBao-backed USER_ENGINE_REGISTRATION_VERIFICATION_TOKEN;
  • comma-separated USER_ENGINE_REGISTRATION_CLIENTS and USER_ENGINE_REGISTRATION_TENANTS allow-lists;
  • USER_ENGINE_REGISTRATION_PASSWORD_SETUP_ORIGINS, containing only the trusted HTTPS provider origins allowed to receive the browser.

After verification, user-engine completes the local user, provisions with the deterministic public-registration-<registration-id> idempotency key and only the user role, then links the returned subject under the configured KeyCape issuer. A setup URL outside the explicit HTTPS origin allow-list is rejected.