Retire the bespoke, chrome-less /.tokens page. It now renders through the
shared tables engine — getting the standard header (logo, theme, profile
menu) + declarative columns/filters for free — from a server-injected,
pre-assembled #table-context built from the user's tokens (Store.List).
New, reusable "tables over an API collection" primitive (tables/js/
api-actions.js): when the injected context carries an `apiActions` block,
it drives create (a modal form → POST, surfacing the one-time secret) and
per-row delete (→ DELETE) against a REST endpoint, and hides the file-model
toolbar affordances (+ Add row / Save). It deliberately does NOT touch the
file-save/row-ops machinery (ETag/conflict/row-file writes), so the secrets
surface stays on the existing tested /.api/tokens endpoints.
Server: handler.injectTableContextObj injects an arbitrary pre-assembled
context; EmbeddedTablesHTML() exposes the renderer to sibling handlers;
ServeTokensPage builds the token context (+ apiActions for /.api/tokens)
and serves the tables HTML, falling back to the legacy skeleton only when
the store or the tables renderer is unavailable.
This is the first dynamic/virtual-record collection rendered by the same
declarative engine + chrome as on-disk tables — no bespoke page. Validated
end-to-end in a containerized browser (list + create→secret + revoke);
tests/tokens.spec.js updated to the new UI; full Go suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zddc-server now issues its own bearer tokens for non-browser callers
(CLI tools, scripts, downstream proxy/cache/mirror instances). No
external IDP, no JWKS rotation. Self-service flow: sign in via the
browser, visit /.tokens, click "Create token," paste the resulting
plaintext into a 0600 file, and pass --bearer-file <path> to whatever
calls back into the server.
Storage is <ZDDC_ROOT>/.zddc.d/tokens/<sha256-hex>, YAML per token
with email/created/expires/description. Filename is the *hash* of the
plaintext, never the plaintext itself — a leak of the tokens
directory exposes hashes, not credentials. Mode 0600 / 0700, atomic
writes via temp+rename. Already shielded from public serving by the
existing dot-prefix guards in dispatch and fs.ListDirectory.
ACLMiddleware now recognises Authorization: Bearer <token>. On valid
token, sets the request email from the token file and falls through
to the existing ACL chain. On any failure (unknown / expired / store
unavailable / Bearer with no validator), returns 401 — no silent
fallback to anonymous, so a misconfigured client fails loudly.
JSON API at /.api/tokens (GET list, POST create, DELETE /<id> revoke)
backs a small inline HTML self-service page at /.tokens. Users can
only see and revoke their own tokens; cross-user revoke returns 404
to avoid leaking ownership.
--no-auth (ZDDC_NO_AUTH=1) skips ACL enforcement entirely on this
instance. On master: anyone reads everything (dev / trusted-LAN /
public-read deployments). On a downstream proxy/cache/mirror: trust
upstream's filtering, don't re-evaluate ACLs locally. Implemented as
a swap to policy.AllowAllDecider; all existing handlers keep calling
AllowFromChain unchanged. Distinct from --insecure, which only
relaxes the no-root-.zddc startup check. WARN-level startup log when
--no-auth is active so accidental enablement is visible.
33 new tests covering token storage, validation/expiry/revocation,
the JSON API end-to-end, the HTML page, and the middleware-Bearer
integration including the case-insensitive prefix and expired-token
paths. Full suite + go vet clean.
Doc updates: zddc/README.md "Authentication" rewritten to cover both
auth paths and the token UI/API; AGENTS.md gains ZDDC_NO_AUTH and a
"Bearer tokens" subsection flagging the dot-prefix-shielding pre-
condition; ARCHITECTURE.md adds "Bearer token issuance" and
"--no-auth" subsections under "Server security model" with the
hash-as-filename rationale and dispatch-shielding regression-
sensitivity called out; CLAUDE.md adds a one-line summary of the new
auth topology so future agents pick it up by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>