124 lines
6 KiB
Markdown
124 lines
6 KiB
Markdown
# 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.
|