Multi-tenant rollout: daemon, identity CLI, invite flow, setup page #7
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
posta/server!7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "multi-tenant-rollout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Hostheader. Identities are defined entirely by/etc/posta/identities.toml; per-identity files live by convention at/var/lib/posta/<slug>/{keys.json,inbox.db}. (Closes #1)posta-server identity add/list/remove/purgeand slug-scopedtoken create/list/revoke. Manifest writes are atomic (tmpfile + fsync + rename); slug regex[a-z0-9-]+is enforced at write time; mutating commands print a "restart the daemon" hint and never auto-restart. (Closes #2)invitestable),posta-server invite create/list/revoke, plusGET /api/v1/invite/infoandPOST /api/v1/invite/redeem. Tokens are 32 random bytes, base64url-encoded withpinv_prefix; only sha256 hashes persist; default TTL 24h. The redeem path mints + consumes atomically; failure cases (unknown / expired / consumed) all return 410, symmetric with/info. (Closes #4)/setupHTML page that drives the user-facing half of the invite flow. Vanilla JS reads#invite=fromwindow.location.hash, calls/infoon load, and/redeemon submit; success view shows themst_…plaintext once with a copy button and aposta://pair?token=…&url=…deep link. Visual treatment matches the landing card precedent. (Closes #5)Auth middleware grows a path-skip variant for the public carve-out (invite endpoints + setup page); a per-IP rate limiter dampens brute-force noise on
/api/v1/invite/*. The flag surface onserveshrinks to--listenand--manifest;--acl,--rate-per-minute,--configremain as global daemon-wide flags (per Lenient choice during triage); per-identity flags--url,--keys,--db,--nameare gone.#3 (production cutover) and #6 (legacy container cleanup) stay open as
ready-for-human— runbooks the operator executes after this lands.Test plan
go test ./...cleango vet ./...clean/setupHTML,/info200/410,/redeem200/410, second redeem 410, minted token authenticates against/api/v1/identity, auth still rejects without beareridentity add --print-token→invite create→serve→curl /setup→curl /info→curl /redeem→curl -X POST /redeem(second time, expects 410) →curl -H "Authorization: Bearer …" /api/v1/identity→curl -H "Host: unknown.example" /api/v1/identity(expects 404)🤖 Generated with Claude Code
- /api/v1/invite/info: GET ?invite= → POST {invite} so the plaintext never reaches access logs (setup.html and invite_test updated) - Auth-bypass prefix match now requires a path boundary, so /setupanything no longer skips authentication; mirrored into the invite IP rate-limiter for defense-in-depth - Token create/revoke no longer print the manifest restart hint — per-identity DB writes are picked up live by the running daemon; revoke help reverts to "stop authenticating immediately" - Sort Runners() output (the doc claimed sorted iteration but the body iterated a map) - RedeemInvite checks RowsAffected on the consume UPDATE, so a concurrent redeem race loser rolls back the freshly minted token - atomicWriteFile now fsyncs the parent dir after rename so a fresh identity add survives a power loss; matches WriteManifest - runIdentityAdd error path tells the operator how to clean up orphan files when WriteManifest fails after keys/DB are created - Smaller cleanups: rename IPRateLimiter.cap → maxEntries (shadowed builtin), drop the dbPathForSlug shim, stop capturing+discarding the unused Identity in openIdentityStore, gofmt across the tree Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>