user-engine/workplans/USER-WP-0022-public-registration-and-jit-application-profiles.md
tegwick 667ea694c2
Some checks are pending
CI Smoke / container-smoke (push) Waiting to run
CI Smoke / host-smoke (push) Successful in 0s
Delegate tenant lifecycle to the tenant authority
TEN-WP-0005 landed the authoritative metadata update and reversible
retirement contract, so USER-WP-0021-T01's deferred tenant operations are
now implementable without user-engine inventing lifecycle semantics.

TenantManagementPort gains read, update, retire, and reactivate. The HTTP
adapter echoes the record version as an If-Match ETag (never `*`), sends an
Idempotency-Key plus actor/reason/correlation_id, and surfaces
Idempotent-Replay. Authority failures map to redacted domain errors carrying
only the contract's stable error_code; its detail text never crosses the
boundary.

Platform operators get the matching API routes and a CSRF-protected browser
screen that reads the record before mutating it and hides the metadata form
for a retired tenant. Portal OpenAPI moves to 0.3.0 with TenantRecord,
UpdateTenant, and TenantLifecycleChange.

Full suite: 145 tests, 3 external-provider skips.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 01:28:02 +02:00

12 KiB

id type title domain repo status owner topic_slug created updated depends_on state_hub_workstream_id
USER-WP-0022 workplan Public registration and JIT application profiles communication user-engine blocked codex netkingdom 2026-08-09 2026-08-16
USER-WP-0021
NK-WP-0025
97145e5b-5f8f-4cb1-9135-2593f5baac8f

USER-WP-0022 - public registration and application JIT

Turn the existing provider-neutral registration domain into a safe public product flow and provide idempotent first-login application-profile creation. user-engine remains the user/profile source of truth but delegates login identity creation to NetKingdom.

T01 - Publish the public registration journey

id: USER-WP-0022-T01
status: wait
priority: high
state_hub_task_id: "43dd49b7-6dbc-4117-a401-6d9f6e59aa26"

Add accessible browser/API entry points for start, email verification, provider password-setup handoff, resume, expiry, cancel, and completion. Enable public registration only through explicit runtime configuration and signed allow-listed return context. Apply rate limits, anti-enumeration responses, CSRF, idempotency, and safe diagnostics.

Done when the flow can be enabled per deployment without exposing whether an email or username already exists.

Implementation contract established in NetKingdom at docs/public-registration-orchestration-contract.md. The current authenticated registration API will not be moved ahead of authentication; the public adapter will use opaque intent handles and construct a narrowly scoped applicant actor only from consumed verification evidence. First code slice added RegistrationVerificationPort, bound request/receipt/evidence types, and a fail-closed HTTP adapter with purpose checking and normalized mailbox evidence. Adapter tests and the full 123-test suite pass.

Second code slice adds explicit allow-listed anonymous endpoints: POST /api/v1/public/registrations returns only a generic 202 status, while POST /api/v1/public/registrations/verify consumes purpose-bound evidence and attaches the verified email factor. Persisted registration sessions bind the random applicant subject, canonical username, client, and tenant. Evidence mismatch fails closed, browser-supplied return URLs are ignored, and this transition creates neither a user nor an LLDAP identity. Full suite now passes 125 tests with 3 environment-dependent skips.

Third code slice completes the verified happy path: create the local user, call identity-provisioner with deterministic registration idempotency and only the baseline user role, link the returned subject under the configured KeyCape issuer, and redirect only to an allow-listed HTTPS provider password setup origin. Full suite now passes 126 tests with 3 skips. Automated recovery after local completion/provider failure remains open before production enablement.

2026-08-10 browser increment: /register now provides the accessible public account form and the home page links to it only when registration is configured. Anonymous mutations use a short-lived Secure/HttpOnly/SameSite double-submit CSRF cookie. Verification links land on an explicit one-time confirmation page, successful provisioning presents the allow-listed provider password handoff, and provider outages render a retry form backed by the separate resume handle. Responses remain non-enumerating. The full suite passes 128 tests with three environment-dependent skips. Cancellation, ingress rate limiting, and the credential-gated production enablement remain.

The tested UI image is published at forgejo.coulomb.social/coulomb/user-engine@sha256:2fdb60eec093a8c8a0bff1736ab515916cefac49348c0098d308728b72ab3594. A guarded rollout correctly failed closed because the production manifest does not yet supply the required flex-auth/event delivery runtime configuration; the last known-good revision was restored and remained available. Do not activate this image or public registration until the OpenBao delivery and verification tokens plus transactional SMTP lane are installed.

2026-08-10 security increment: every anonymous registration mutation now has a bounded per-peer sliding-window limit, configurable through USER_ENGINE_REGISTRATION_RATE_LIMIT and USER_ENGINE_REGISTRATION_RATE_WINDOW_SECONDS. The application deliberately ignores spoofable forwarding headers and relies on the ingress-normalized peer address. Conformance proves isolation between peers and a generic 429 response. Expired and replayed verification handles are also proven to produce the same redacted rejection without provider or account detail. The full suite passes 130 tests with three environment-dependent skips. Cluster ingress throttling remains defense in depth before public enablement.

2026-08-10 cancellation increment: each verification email now carries a fixed cancellation link using the same digest-only opaque handle. A CSRF-protected confirmation consumes purpose-bound cancellation evidence and terminally abandons the matching registration. Cancellation and verification atomically invalidate one another, replay fails closed, and no identity is created from cancellation. The full user-engine suite passes 133 tests with three environment-dependent skips; email-connect passes all 24 tests.

2026-08-10 idempotency completion: public API starts now require an Idempotency-Key, while the browser form receives a cryptographically random hidden key. user-engine persists only a keyed HMAC of that value plus a hash of the normalized request. Exact retries return the same generic response without creating another session or requesting another email; reuse with different inputs returns 409. Raw keys and applicant fields are absent from idempotency evidence. The full suite passes 134 tests with three external skips.

2026-08-16 gate review: every implementation item listed for this task is built and covered — accessible start/verify/resume/cancel entry points, allow-listed anonymous routes, opaque intent handles, CSRF, per-peer rate limiting, required idempotency keys, and non-enumerating responses. The application-level registration limit is configured in the runtime manifest on top of the Traefik-wide limit, closing the ingress defense-in-depth item.

What remains is not user-engine code. Public registration stays disabled until the OpenBao verification and delivery tokens plus the transactional SMTP lane are delivered through governed channels. The service already fails closed when they are absent, and no placeholder or reused credential will be added to unblock it. This task stays in wait because the remaining gate is a credential-custody handoff, not an outstanding change here.

T02 - Orchestrate provider identity creation

id: USER-WP-0022-T02
status: done
priority: high
state_hub_task_id: "74239ce4-5c1e-46cf-8abf-3cbec3f3f989"

Use the existing provisioning port to create or resume an ordinary NetKingdom identity only after required verification. Persist lifecycle intent and outbox state atomically; compensate or surface recoverable partial state on provider failure. Never accept or retain an LLDAP password, admin credential, MFA seed, or verification secret in the user-engine domain.

Done when a registration can resume safely across timeout/retry and produces one external identity link.

The existing provisioning port remains the privileged boundary. Public registration adds canonical username and uses the registration ID as its stable idempotency key; it accepts only the baseline ordinary-user group and the provider-owned password-setup handoff. ProvisioningRequest now carries an optional canonical preferred_username through the HTTP adapter without changing existing callers.

Retry-safe recovery is now implemented for the local-completion/provider- failure window. A single-use mailbox handle is never replayed; instead, user-engine returns a separate random resume handle, stores only its SHA-256 digest, retries with the same registration idempotency key and local user, and invalidates the handle after provider linking. Replay is denied. The full suite passes 127 tests with 3 environment-dependent skips.

2026-08-10 completion: identity-provisioner now validates and honors the canonical preferred username instead of silently deriving it from the mailbox. The corrected image is live at immutable digest sha256:4a6ec93d74eed6e17065a7e5d4c3d8d0ed14078bdfda0112738438528c6b2680. Together with the deterministic retry path, this completes the provider orchestration boundary without exposing provider credentials or passwords.

T03 - Create application profiles on first login

id: USER-WP-0022-T03
status: cancel
priority: high
state_hub_task_id: "01fa1a22-0f4a-4a36-a9b3-0f94d3c70be1"

Implement an idempotent ensure-application-profile operation keyed by application plus verified issuer and subject. Seed only approved profile defaults and preserve application ownership of application-specific data. Verified email may suggest an explicit link to an existing local profile but must never authorize an automatic takeover.

Done when repeated OIDC callbacks return the same profile and collisions require explicit authenticated resolution.

Cancelled here because application profiles are application-owned. The coulomb-social consumer implements this under CSOC-WP-0003 using its unique (issuer, subject) Member binding; user-engine continues to supply /me.

T04 - Carry assurance and step-up requirements

id: USER-WP-0022-T04
status: cancel
priority: high
state_hub_task_id: "d3bec8c6-e8d8-46d5-8fa2-b4675ff1ade8"

Model the application's minimum assurance and per-user/profile step-up policy as data consumed by KeyCape/application authorization. Do not let profile data issue tokens or weaken client minimums. Add a reauthentication/step-up handoff that returns to the intended action after fresh OIDC authentication.

Done when a regular profile can be password-only while a profile or protected action can require MFA without changing global identity facts.

Cancelled here in favor of KEY-WP-0008 client policy and CSOC-WP-0003 action step-up. user-engine must not become the token assurance authority.

T05 - Registration and JIT security matrix

id: USER-WP-0022-T05
status: progress
priority: high
state_hub_task_id: "0a5a5f3a-0d47-4d5d-bda5-e2c7c737fee6"

Cover enumeration resistance, duplicate username/email, verification replay/expiry, provider outage and compensation, return-state tampering, JIT replay, account-link collision, cross-tenant profile access, step-up downgrade, unlink, and deletion. Verify audit/outbox redaction and correlation.

Done when the full suite and deployed consumer conformance both pass.

The user-engine portion now covers return-context rejection, provider outage and idempotent recovery, resume replay denial, purpose/binding mismatch, verification expiry/replay redaction, peer rate limiting, identity-link collision, cross-tenant denial, lifecycle deletion, audit/outbox redaction, and request correlation. Deployed consumer conformance remains gated on public runtime credentials and activation.

Duplicate-input conformance now also proves that a verified mailbox reused with a different username creates a distinct local user instead of implicitly linking to an existing account. Reusing an existing provider username fails at the identity-link uniqueness boundary, leaves the original link unchanged, and never transfers that identity to the later registration. The full suite passes 131 tests with three environment-dependent skips.

2026-08-16 authorization evidence: flex-auth reports FLEX-WP-0009 finished and handed back deployed-policy evidence for the user-engine surface, including the three registration-applicant fixtures (applicant allow, membership deny, wrong issuer deny) replayed against the live POST /v1/check. The fail-closed FlexAuthHTTPAdapter maps a missing or refused service to deny. This removes authorization from this task's gate; only the credential handoff remains.

The latest tested image is published at forgejo.coulomb.social/coulomb/user-engine@sha256:a49a0a105d392e374f6da958f8ad1bff36f2cfa8779ac5cafa479b7f34b6efc1; the matching email-connect image is forgejo.coulomb.social/coulomb/email-connect@sha256:bc7a01b4ab9acd2f12f3150c84e4b6590744c035bc15689c93584d8da06bd730. The cluster already applies a Traefik-wide rate limit, and the runtime manifest now explicitly configures the stricter application registration limit. Public registration remains disabled pending its OpenBao verification/delivery tokens and transactional SMTP configuration.