-
Notifications
You must be signed in to change notification settings - Fork 0
Accounts and Auth Internals
Audience: Engine Developer Status: ✅ Ready
telos-account owns identity: accounts, characters, trust tiers, and the OAuth flow that turns a browser sign-in into a signed token the rest of the fleet trusts. Auth is terminal-native and OAuth-only — there are no passwords and no SSH. The design keeps the account service off the hot path: it mints a short-lived signed session assertion that the world verifies offline, so gameplay never makes a per-action auth call.
Related: RPC & Protobuf (the Account service), Trust Tier Model and First-Admin Setup (the audience-facing views), Edge & Protocol (the gate side), Sysadmin OAuth Setup (deployment).
Login is a browser device bridge, not RFC 8628 device flow and not a typed link code — the "device code" is TelosMUD's own high-entropy handle. Only GitHub is implemented today (the migration comment lists Google/Discord, but no provider code exists for them).
sequenceDiagram
participant C as Player (telnet)
participant G as telos-gate
participant A as telos-account (broker + gRPC)
participant GH as GitHub OAuth
C->>G: connect (account-backed gate)
G->>A: StartDeviceAuth
A-->>G: device_code + login URL
G-->>C: prints the clickable URL, gate polls
C->>A: opens the login URL in a browser
A->>GH: redirect (CSRF state + PKCE verifier in a signed cookie)
GH-->>A: callback with code
A->>GH: exchange code with the PKCE verifier
A->>A: resolve-or-create account by provider uid (never email)
A-->>G: poll returns authed + account id + character list
G->>C: character select / create
The device code has a 10-minute TTL; the flow cookie is HMAC-signed; the account is keyed on the immutable (provider, provider_uid) pair, never the user-settable email. Redis backs the short-lived device sessions — without it, the broker and device auth are disabled but the gRPC API still serves.
IssueSessionAssertion mints an Ed25519-signed envelope with claims {account, character, session, exp} (and the trust tier). The gate carries it in the Play stream's Attach frame; the world verifies it offline against the account's public key and captures the tier only from a signature-checked claim. This is why the world never calls the account service during play — the assertion is a self-contained, cryptographically bound capability. Signing/verify keys are supplied by the operator (TELOS_ACCOUNT_SIGNING_KEY on the account service, TELOS_ACCOUNT_VERIFY_KEY on every world shard); with none configured the assertion is empty, and a discoverable world refuses to boot unless TELOS_ALLOW_INSECURE is set. The gate authenticates to the account gRPC API with a shared caller token (TELOS_ACCOUNT_CALLER_TOKEN).
- An account comes into existence implicitly on first sign-in (default tier
player); there is no separate provisioning step. - Character names are unique (CITEXT), reserved via
ReserveName. -
Chargen is content-driven (Model A):
GetChargenFlowreturns the content-defined step flow (point-buy, bundle choices) that the gate walks as prompts;CreateChargenCharactervalidates the submission with the cost curve kept server-side. The chosen output is recorded and applied by the world on first spawn — the account service only reads content and validates. See Pack Entity Reference (chargen_defs).
The trust tier is stored on the account, signed into the assertion, and applied by the world as capability flags on next login. The account service is the authority:
-
Bootstrap: the
TELOS_BOOTSTRAP_ADMINconfig pin grantsadminto the first account created for that GitHub login (not email), atomically with an audit row — first-run only. Atelos-account set-tierbreak-glass CLI (host/DB access = authorization) is the recovery path. See First-Admin Setup. -
Promote/demote: the in-game gate verbs call
SetAccountTier, whose authorization is enforced here — the actor's tier is read from the authoritative store, promotion ceilings are enforced with a compare-and-set under a row lock (you can't grant above your own authority, and concurrent changes can't race), the path is fail-closed if the content trust ladder can't be validated, and every change writes an audit row to the shared append-onlycharacter_audittrail in the same transaction as the tier change (atomic — a promotion is never recorded-but-unapplied or vice versa). The effect lands on the target's next login. See Persistence & Durability → the audit trail. -
The
manage_tiersvisibility bit: for visibility only,IssueSessionAssertionalso resolves amanage_tierscapability bit — the sameGrantsFlag(tier, admin)predicateSetAccountTierchecks authoritatively — and returns it alongside the assertion (resolved even on a signing-less gate; fail-safe tofalseon an unknown account, a tier-read error, or an unavailable ladder). The gate caches it per login and uses it only to decide whether to interceptpromote/demote: a non-staff actor's attempt falls through to a worldHuh?before any parse, output, orSetAccountTiercall, so the verbs neither leak their existence nor amplify into an account-service probe. This is defense-in-depth visibility only — a stale or spoofed too-high bit still hits the authoritative service refusal (fail-closed), and a too-low bit only costs aHuh?(fail-safe).
The ladder itself is a content-defined ordinal (default player 0 / builder 20 / admin 40, with gaps for pack-inserted rungs) that both the world and the account service load, so there's a single source of truth. The world-side application (reserved-flag denylist, rank comparison, the signed-handoff tier carry) is documented on Trust Tier Model.
A character has at most one live session fleet-wide. The lock is heartbeated; a newer login anywhere in the fleet displaces the older connection (Edge & Protocol). This is what keeps a duplicated or hijacked stream from co-existing with the legitimate one.
Do not document these as usable: passphrase login (an account_auth table exists but no Argon2/verify code — dropped in Phase 15), SSH pubkey login (an ssh_keys table exists but no SSH server; auth is OAuth-only), and the Google/Discord providers.
TelosMUD — Wiki under construction.
- Builder Reference
- Builder Commands
- Trust Tier Model
- Pack Authoring
- Pack MUD Settings
- Pack Lua Scripting
- Pack Lua Hooks
- Pack Entity Reference
- Building Instanced Zones
- Engine Developer Reference
- Architecture Overview
- Entity Component Model
- Zone Runtime & Actor Model
- Instanced Zones
- Command Parser & Targeting
- Edge & Protocol
- GMCP Reference
- Persistence & Durability
- Content Loading & Hot Reload
- Abilities & Effects
- Combat System
- Loot, Spawns & Crafting
- Accounts & Auth Internals
- Orchestration & Directors
- Scoped Event Bus
- Cross-Shard Handoff
- Lua Sandbox Internals
- Distributed Systems Model
- RPC & Protobuf