Skip to main content

Owners create pending users with single-use password setup

Morten requested user creation while public registration remains closed, and chose the existing organization owner/member model and single-use password setup links on 20 October 2026 (task ed3ebb61). He approved the concrete additive schema on that task at 10:01 UTC.

A user belongs to the server, while a membership belongs to an organization. Provisioning must commit a new user, a member membership and a setup verifier together. The existing create-user and set-member operations have separate transactions; composing them leaves an orphan on failure. UserSetupService owns that invariant and the setup lifecycle behind one interface.

Existing users may have no password, and activated users may remove their password. A null hash cannot distinguish a new user awaiting setup from an existing user whose credentials an owner must never control. New provisioning explicitly inserts activation_pending = 1; the migration defaults every existing user to 0. Activation clears the flag permanently. The private setup row records the creating organization, which is the only organization whose current human owners can replace or revoke that pending user's link, even if the user joins another organization.

Considered options​

  • Administrator-chosen initial passwords: rejected in the agreed flow; recipients choose their own password without an owner knowing it or needing email integration.
  • Infer pending state from a missing hash: rejected; this would enable credential takeover of existing passwordless users and re-enable setup after someone removes their password.
  • Compose user creation and membership writes: rejected; failure cannot roll both back.
  • Widen general user creation/password APIs to owners: rejected; those interfaces have no creating-organization scope and must not become cross-organization password reset authority.
  • Explicit pending state and a private setup lifecycle module: chosen. Additional persisted state buys an unambiguous, enforceable distinction between first setup and credential reset.

Consequences​

  • Public registration stays closed. Direct human owners create users as members; members, agents, runs and Slack-delegated contexts cannot provision or manage setup links.
  • Links expire after 24 hours. Replacement invalidates the old verifier, revocation clears it, and successful password submission atomically writes the hash, activates and consumes. GET/open/inspection never consumes. Existing password rules and scrypt hashing are reused.
  • Only SHA-256 verifiers of 32 random bytes are persisted. Raw capabilities use URL fragments, fixed POST bodies and temporary view state; they do not enter logs, replicas, analytics or caches.
  • A missing originating membership or deleted user/organization invalidates redemption. Removal revokes the link in the same transaction, so adding the membership again cannot resurrect it.
  • The schema transition is one-way for a stock older binary: Kysely rejects unknown applied migrations. Live rollout therefore requires a tested rollback image retaining the migration registration and pending-user safeguards. An automatic down migration or restore is excluded.