Canonical identities and authentication
Cheetah's user and tenant identities are opaque, canonical UUID-based values. Authentication providers translate host credentials and external account records into those values. Routing, storage, and authorization code should consume the validated identity, not derive meaning from an email address, display name, or provider-specific subject.
Canonical formats
| Identity | Format | Example |
|---|---|---|
| user | usr_ plus a canonical lowercase hyphenated UUID | usr_02cd8db7-d030-4daf-ae72-810d4606fb42 |
| tenant | ten_ plus a canonical lowercase hyphenated UUID | ten_3314b161-bb89-441d-bc2f-5e214b473c45 |
| shared effective principal | validated tenant, @, validated user | ten_3314b161-bb89-441d-bc2f-5e214b473c45@usr_02cd8db7-d030-4daf-ae72-810d4606fb42 |
The supplied generators create UUIDv4 values. Validators accept a canonical UUID regardless of UUID version, which permits stable imported identifiers without accepting alternate casing, missing hyphens, or free-form aliases.
An external subject such as auth0|123, an email address, or an account slug can be an attribute of the product account that maps to a canonical Cheetah user. It must not be inserted directly into a stream prefix or used as the effective principal. This keeps identity-provider migration and human-readable renaming separate from protocol routing.
Authentication result contract
An authentication provider receives a host-defined credential and returns an AuthResult. For valid credentials, the result contains a canonical user ID and, in shared mode, a canonical tenant ID. Identity objects validate eagerly, so malformed IDs fail at the authentication boundary instead of entering registry or storage keys.
Invalid credentials should return valid=False; an expected invalid token is not an exception. Operational failures such as an unavailable identity service are different and should fail closed while remaining visible to diagnostics.
A custom provider should return leaf IDs. It must not precompose tenant@user as a free-form string. The shared authentication path validates both leaves and constructs the effective principal in one place. This prevents alternate spellings or an already-composed input from bypassing proof and validation.
WebSocket authentication
The client supplies auth_token in its hello. The WebSocket host passes that opaque credential to the configured provider after transport and protocol checks. On success, registration is scoped to the derived principal; message-body IDs cannot change it later.
When require_tenant is false, a canonical user is sufficient and becomes the effective principal. When it is true, the authentication result must contain both tenant and user. A tenantless or malformed claim is rejected. The remote client receives a generic auth_failed reason in shared mode, while detailed causes stay on the protected diagnostic side of the boundary.
Credentials travel during hello, so strict transport is the normal deployment posture. Keep tokens out of metadata, URLs, trace attributes, retained error payloads, and log messages.
HTTP authentication
The REST role does not parse a universal Authorization header and does not trust a body principal. The hosting FastAPI, aiohttp, or other endpoint authenticates the request using its chosen mechanism and passes the resulting trusted identity into the service.
Dedicated mode can call the ordinary principal path with a validated user. Shared mode must call authenticate_tenant_principal() and pass the resulting AuthenticatedTenantPrincipal proof through the restricted boundary. The compatibility authenticate_principal() result is string-like; even if a caller manufactures text that looks like a valid combined principal, shared nodes reject it because it is not the typed proof from the shared authentication path.
The WebSocket and HTTP edges may use different credential presentations—for example, a hello token and a signed HTTP header—but they must resolve to the same canonical identity semantics. In a distributed deployment, every role must use the same identity store or equivalent provider configuration. A user accepted as one principal on WebSocket and another on HTTP cannot safely return results.
Application-side identity
In dedicated mode, an application obtains a user-scoped App surface and supplies logical client IDs beneath that user. The scoped surface constructs routing identities rather than asking each call site to concatenate strings.
In shared mode, application requests enter through an authenticated tenant proof. The returned surface is already bound to the effective principal. Application code should keep product account lookup and authentication at the edge, then pass the proof inward rather than exposing a method that accepts arbitrary tenant and user text from business logic.
Authorization remains separate from authentication. Proving who a caller is does not decide which client action the product may request. Central product authorization, client-local policy, and optional human approval each operate at their own boundary.
Rotation, revocation, and deletion
Credential rotation affects future authentication; it does not automatically disconnect every existing socket, purge history, remove deduplication entries, delete product records, or retract exported telemetry. A deployed product needs an offboarding workflow that covers live connections and every store carrying the canonical principal.