Skip to content

Users

A user in HiveCast is represented internally as a principal. This page covers what a principal is, how it gets created, and which fields are operator-relevant.

Internal terminology

The code says "principal." The product UI says "user" or "you." This docs page mostly uses "principal" because it is precise — a principal is an identified subject, not a session or a device. The product surface should never expose the term to end users.

The principal record

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts:29-39.

ts
export interface IHostPrincipalRecord {
  readonly id: string;             // p_xxx
  displayName?: string;
  email?: string | null;
  avatarUrl?: string;
  workspaceRealm?: string | null;
  addressRoots: Array<{ root: string }>;
  readonly status: 'active' | 'suspended';
  readonly createdAt: string;
  updatedAt: string;
}

Key fields:

  • id — opaque, stable principal id (p_<random>). Used everywhere internally. Never displayed in user URLs.
  • displayName — the user's display name from their OIDC ID token (name claim).
  • email — from OIDC email claim. Used to derive the initial address root and as a contact handle. Email can change (a user updates Google's primary address); the principal stays.
  • addressRoots — list of bus authority roots claimed for this principal. Today the first entry is derived from the email domain at first login. Later, additional roots can be added via Space creation.
  • workspaceRealm — legacy field. Use addressRoots[0].root in new code.
  • statusactive or suspended. Suspended principals fail session validation.

The principal record is created on first OIDC login and persisted in HostAuthStateStore. Subsequent logins find the existing record by (issuer, subject) — never by email.

OIDC identity is (issuer, subject), not email

This is critical and easy to get wrong. Per ARCHITECTURE-PLATFORM-TOPOLOGY.md §3:

Identity is (issuer, subject) per OIDC — NOT email. Email can change; identity is permanent.

Examples:

ProviderIssuerSubject
Googleaccounts.google.com107348194...
GitHub (target)github.com12345678
Okta (target)acme.okta.com00u1a2b3c4

The findOrCreate op (HostPrincipalStore.findOrCreate(issuer, subject, claims)) does the lookup. This is how a user who changes their email keeps the same principal.

How a principal is created

Source: projects/matrix-3/packages/system-auth/src/google-oidc.ts:151-190 and index.ts:1216-1244.

The Google OIDC flow:

  1. User clicks "Sign in with Google" on /apps/web/.
  2. Browser is redirected to https://accounts.google.com/o/oauth2/v2/auth with PKCE.
  3. Google redirects back to /api/auth/callback/google with code + state.
  4. auth.google.callback op exchanges code for tokens (exchangeCodeForTokens).
  5. verifyGoogleIdToken checks signature against Google JWKS, validates iss/aud/exp.
  6. principalStore.findOrCreate(claims.iss, claims.sub, { email, displayName, provider: 'google' }).
  7. sessionService.createSession(principal.id, ...) mints an mx_session JWT (HMAC-SHA256, 7-day expiry).
  8. Set-Cookie: mx_session=...; HttpOnly; SameSite=Lax.
  9. Browser redirects to the page captured in the OAuth state's redirect.

If principal.id was not in the existing-set before, isNewUser: true is returned. The cloud Host can use this to drive a first-time setup flow.

Listing principals

bash
matrix invoke system.auth auth.principal.list '{}' \
  --home /tmp/matrix-home

Returns every principal known to the local Host. On the platform Host this is every signed-in user. On a local Device it is exactly one principal — the Device's owner — assuming pairing has happened.

Looking up by id

bash
matrix invoke system.auth auth.principal.get \
  '{"principalId":"p_xxx"}'

Returns the principal record or { ok: false, error: "Principal not found" }.

Address-root derivation

The first address root is derived from the email domain at first login. Per ARCHITECTURE-PLATFORM-TOPOLOGY.md §4:

richard@nimbletec.com           -> COM.NIMBLETEC.RICHARD-SANTOMAURO
richard@gmail.com               -> COM.GMAIL.RICHARD-SANTOMAURO
alice@gmail.com                 -> COM.GMAIL.ALICE-TESTUSER
jane@acme.com                   -> COM.ACME.ENG.JANE

Address roots are immutable once assigned. Renaming an email account does not rename the root. (For new Spaces, the v2 contract prefers SPACE.<spaceId> derivation — see Spaces.)

A common misconfiguration is to derive gmail.com to COM.GOOGLE instead of COM.GMAIL. Per the auto-memory ledger ("Root derivation"), this must be COM.GMAIL. Mismatches break Device discovery.

Setting workspace realm (legacy)

bash
matrix invoke system.auth auth.principal.setWorkspaceRealm \
  '{"principalId":"p_xxx","realm":"COM.GMAIL.NEW-REALM"}'

workspaceRealm predates addressRoots[]. New code should write address roots via Space creation. The op exists for migration tooling.

Authority root resolution

For any principal, auth.principal.authorityRoot returns:

  1. The principal's default Space's authorityRoot, if a default Space is set.
  2. Otherwise the first registered address root.
  3. Otherwise the legacy workspaceRealm.
  4. Otherwise an error.

This op is what system-gateway-http's /api/bootstrap handler calls to compute the addressRoot returned to a freshly-loaded browser tab.

Lifecycle: suspending a principal

Today the status: 'suspended' field exists in the schema but is not surfaced via an op. Suspending requires direct state-file edit on the platform Host. This is target state — a auth.principal.suspend / unsuspend op pair is needed for the admin dashboard.

Lifecycle: deleting a principal

Not implemented. Principal deletion has cascading implications for Spaces, Host Links, and stored credentials. The recommended workaround is suspension. Target state.

Target state

  • Multi-provider login. Today only Google. GitHub OIDC and email/passkey are in ARCHITECTURE-PLATFORM-TOPOLOGY.md §3.
  • Enterprise SSO via home-realm discovery. Schema-ready (workspaceRealm); discovery flow not implemented.
  • auth.principal.suspend / auth.principal.unsuspend ops.
  • auth.principal.delete op with cascade rules.

See also

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts:29-39 for the schema. projects/matrix-3/packages/system-auth/src/index.ts:450-528 for auth.principal.* ops. projects/matrix-3/packages/system-auth/src/google-oidc.ts for the OIDC implementation.