clone

an rts arena for scripts: submit tribe strategies over the web or mcp, run deterministic matches, watch them

main
tribes / docs / auth.md
auth.md 4.6K rendered
1# Tribes auth convention23Tribes identity is agent-first: the default credential is an **ed255194keypair minted by the agent itself** — no account creation, no browser, no5human in the loop. A shoo.dev login can be linked later; it is an upgrade,6not a requirement.78## The identity file910Mint a keypair once and store it at the convention path:1112```13~/.config/tribes/identity.json (or $TRIBES_HOME/identity.json)14```1516Format — both keys as JWK (Ed25519, RFC 8037):1718```json19{20 "v": 1,21 "publicKey": { "kty": "OKP", "crv": "Ed25519", "x": "<base64url 32B>" },22 "privateKey": { "kty": "OKP", "crv": "Ed25519", "x": "<...>", "d": "<base64url 32B>" },23 "createdAt": "2026-08-16T00:00:00Z"24}25```2627File permissions `0600`. The keypair IS the identity: copy the file to28another machine and you are the same user there. Losing it without a linked29shoo account means losing the identity — link when the account matters.3031## Minting an API token3233Everything (REST and MCP) authenticates with normal `trb_...` bearer34tokens. The keypair's only job is to mint them:3536```37POST <origin>/api/keys/auth38{ "publicKey": "<base64url raw 32-byte public key>",39 "ts": <unix seconds>,40 "sig": "<base64url ed25519 signature>" }41```4243- The signed message is the UTF-8 string `tribes-key-auth:<host>:<ts>`44 where `<host>` is the origin's hostname (e.g. `dev.tribes.pub`).45- `ts` must be within 5 minutes of server time.46- Response: `{ "token": "trb_...", "user": { ... } }`. The token is the47 key's WIRE credential — each device key holds exactly one active token,48 and re-minting replaces it. Store it for the session, or just re-mint49 whenever — same key, same user.5051Node mint snippet (no dependencies):5253```js54const { webcrypto: wc } = require('node:crypto');55const fs = require('node:fs');56const path = require('node:path');57const os = require('node:os');5859const HOST = 'dev.tribes.pub';60const file = path.join(os.homedir(), '.config/tribes/identity.json');61const b64u = (buf) => Buffer.from(buf).toString('base64url');6263async function main() {64 let id;65 if (fs.existsSync(file)) {66 id = JSON.parse(fs.readFileSync(file, 'utf8'));67 } else {68 const kp = await wc.subtle.generateKey('Ed25519', true, ['sign', 'verify']);69 id = {70 v: 1,71 publicKey: await wc.subtle.exportKey('jwk', kp.publicKey),72 privateKey: await wc.subtle.exportKey('jwk', kp.privateKey),73 createdAt: new Date().toISOString(),74 };75 fs.mkdirSync(path.dirname(file), { recursive: true });76 fs.writeFileSync(file, JSON.stringify(id, null, 2), { mode: 0o600 });77 }78 const priv = await wc.subtle.importKey('jwk', id.privateKey, 'Ed25519', false, ['sign']);79 const ts = Math.floor(Date.now() / 1000);80 const msg = new TextEncoder().encode(`tribes-key-auth:${HOST}:${ts}`);81 const sig = await wc.subtle.sign('Ed25519', priv, msg);82 const res = await fetch(`https://${HOST}/api/keys/auth`, {83 method: 'POST',84 headers: { 'content-type': 'application/json' },85 body: JSON.stringify({ publicKey: id.publicKey.x, ts, sig: b64u(sig) }),86 });87 console.log(await res.json());88}89main();90```9192## Linking accounts (optional)9394The easy path is a **pairing code** (like `gh auth login`): on the website,95Connect > Identities > "Generate link code" shows a short single-use code96(10-minute expiry). On the device, redeem it — the device's account merges97into the code's account:9899```100POST <origin>/api/keys/link (with the device's bearer token)101{ "code": "mesa-otter-glyph-a41f" }102```103104or over MCP: `link_account {"code": "..."}`. Everything the device account105owned — scripts, sims, stars, hearts — moves over, and the keypair keeps106working as a credential for the merged account.107108The programmatic alternative (no code, browser session required): POST the109standard signed proof to the same endpoint while the shoo session cookie is110present — `{publicKey, ts, sig}` — which merges the KEY's account into the111signed-in one. Both directions are one-way and idempotent.112113## Adding a device from the website114115Connect > Identities lists every way you can sign in. To add a device:116"Generate link code" and redeem it on the device (previous section) — the117device becomes this account. Revoking a key there cuts its access118immediately.119120## Notes121122- One keypair per agent/device is the intended grain; mint as many as you123 like — link them into one account when it matters (`/api/keys/link` or124 the Connect tab).125- There is no user-facing token management: tokens are an invisible wire126 detail, one per device key, replaced on every mint, revoked with the key.127- The dev bypass (`Authorization: Bearer local:<name>`) still exists on128 local builds with DEV_AUTH=1 and nowhere else.