From 727b25be4fa003b85a03fdeb7ae2874b1a6a7c15 Mon Sep 17 00:00:00 2001 From: webdevtodayjason Date: Mon, 21 Sep 2026 18:14:20 -0500 Subject: [PATCH] README: people sign in, programs use keys The access control section said the key was the whole story and that the dashboard asks for it on first open. With the front door login (#261) a person signs in with a name and a password and keeps a session; a key is what a program presents. Documentation only, and `CHANGELOG.md` is not touched. - **A new paragraph in Access control**: what a person presents and what a program presents, the two setup commands on the master (`ainode auth user add --admin`, then `ainode auth enable`), accounts replicated from the master with sessions per node, the two roles, the account page and the Users page, `~/.ainode/users.json` at 0600 with hashes only, and a link to the docs site's new page. - **The installer paragraph** no longer says the dashboard asks for the key on first open, because a person signs in there now. - **The keyless list** gains `/api/auth/me` and `/api/auth/login`, with the reason for each: the page has to be able to ask whether you are signed in, and the request that signs you in cannot need a session first. The join paragraph's "one of five keyless paths" drops the count, which is the half that drifts (`tests/test_join_flow.py` asserts the paths, never a number). - **The CLI reference** gains `ainode auth user add|list|passwd|disable|enable| remove` and `ainode auth session list|revoke|clear`. No version number is claimed and no feature row is added: the release that carries the login names it in the release notes. Docs site page: https://docs.ainode.dev/security/sign-in (getainode/ainode-docs#8). Co-authored-by: webdevtodayjason Co-authored-by: Claude Fable 5.1 --- README.md | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6009c13d..76f9018f 100644 --- a/README.md +++ b/README.md @@ -960,6 +960,10 @@ ainode auth enable|disable|status # Require an API key on /api and /v1, or sto ainode auth key create --name # Mint a key for one client, printed once ainode auth key list # Which clients hold a key, by id and name ainode auth key revoke ID # Take one away, live +ainode auth user add NAME --admin # Make an account a person signs in with. It + # prompts for the password, --password-stdin in a script +ainode auth user list|passwd|disable|enable|remove # Manage accounts, on the master +ainode auth session list|revoke|clear # Who is signed in, and end a session ainode tls enable # Serve HTTPS on its own port (3443), HTTP untouched. # --cert/--key installs a pair you have, --tailscale # gets a real Let's Encrypt cert for this node's @@ -1119,24 +1123,42 @@ gets 8001, 8002 and so on the same way. **A fresh install requires an API key.** The installer mints one, stores its SHA-256 in `~/.ainode/auth.json` and prints the key once in a box at the end of the -install; the dashboard asks for it the first time you open the node and remembers it -after that. Everything on both ports is behind it, because AINode serves the +install. Everything on both ports is behind it, because AINode serves the dashboard, the OpenAI-compatible API and every management route (load a model, unload -one, change the config) on the same ports, so the key is the whole access control. -Install with `AINODE_AUTH=off` for the old behaviour on a LAN you trust, and the -installer prints what that choice means. An update never changes the auth state or -the keys of a node that is already installed: `ainode auth enable` and +one, change the config) on the same ports, so nothing reaches a node without a +credential. Install with `AINODE_AUTH=off` for the old behaviour on a LAN you trust, +and the installer prints what that choice means. An update never changes the auth +state or the keys of a node that is already installed: `ainode auth enable` and `ainode auth disable` are how that changes, and both take effect on the running node with no restart. -With auth on, every path under `/api` and `/v1` wants the key, with these deliberate -exceptions: the static shell (`/` and `/static/*`), because it is what asks for the -key; `/api/health`, because a liveness probe has none, which is also why `ainode -update` verifies a release there; `/api/auth/status`, so the UI can say a key is -wanted instead of rendering blank; `/api/auth/login`, because the caller with no -credential is exactly who knocks on it, and `/api/auth/me`, which answers -`{"user": null}` to a caller it does not recognise so the dashboard can draw the -login page instead of guessing; `/api/cluster/endpoint`, which carries names, +**People sign in, programs use keys.** A person opens the dashboard and signs in with +a name and a password, and the session lasts until they sign out or until an admin +revokes it. Programs are unchanged: a coding agent, AINode Desktop, the bench and curl +all keep presenting `Authorization: Bearer `. Two commands on the master set the +first account up, and the first one prompts for the password (`--password-stdin` in a +script): + +```bash +ainode auth user add --admin +ainode auth enable # only if this node is not requiring auth yet +``` + +Accounts are managed on the master and replicated to every node in the cluster, and +sessions are per node. The roles are `admin` (users, keys and the auth switch) and +`member` (the dashboard). Your own account page changes your password and revokes your +sessions; admins get a Users page to add, remove, reset, disable and enable accounts. +`~/.ainode/users.json` holds the accounts, 0600 and hashes only, beside `auth.json`. +The whole walkthrough, including why a login belongs behind HTTPS, is on the docs site: +[Sign in and accounts](https://docs.ainode.dev/security/sign-in). + +With auth on, every path under `/api` and `/v1` wants a credential, with these +deliberate exceptions: the static shell (`/` and `/static/*`), because it is what +asks you for one; `/api/health`, because a liveness probe has none, which is also why +`ainode update` verifies a release there; `/api/auth/status`, so the UI can say a +credential is wanted instead of rendering blank; `/api/auth/me` and `/api/auth/login`, +because the page has to be able to ask whether you are signed in and the request that +signs you in cannot need a session first; `/api/cluster/endpoint`, which carries names, addresses and ports so a client stranded by its own node can find another; and `POST /api/cluster/join`, because a node joining this cluster cannot hold this cluster's key yet. That last one takes a single-use expiring join token instead, and the handler