Documentation
¶
Overview ¶
Package auth carries the request-scoped auth context plus its helpers. Subpackages (signature/, registry/, policy/, nonces) build on this.
Index ¶
- Constants
- Variables
- func EightWordSecret() (string, error)
- func Middleware(cfg Config, next http.Handler) http.Handler
- func WithContext(parent context.Context, c *Context) context.Context
- func WriteForbidden(w http.ResponseWriter, code string)
- type AuthMode
- type Config
- type Context
- type NonceStore
- type SessionStore
Constants ¶
const SessionCookieName = "txco_session"
sessionCookieName is the literal `Set-Cookie: <name>=...` token the chassis emits and reads. Exported as a package constant so the admin server (which sets the cookie) and the middleware (which reads it) agree.
Variables ¶
var ErrReplay = errors.New("nonce_replay")
ErrReplay signals the nonce was already used for this key.
Functions ¶
func EightWordSecret ¶
EightWordSecret returns a hyphen-joined 8-word secret drawn from effLongWords (the EFF "long" wordlist, hyphenless variant, 7772 entries). ~103 bits of entropy.
Use this for any credential that authenticates an unsigned-consume endpoint: the first-boot admin bootstrap secret, admin-issued invitation tokens, and any future "anyone-with-the-string-can-mint- creds" pattern. 32-bit shortcuts (4 words from a small list) are not enough — the consume endpoints are the most exposed HTTP surface, and TTL + burn-after-use are belt-and-suspenders, not a substitute for entropy.
func Middleware ¶
Middleware returns an http.Handler middleware that authenticates requests per Config.Mode and attaches an auth.Context to the request before calling next.
Per the design doc, /healthz and /auth/dev/enroll bypass the auth middleware entirely — they're wired in front of this in server.go. Everything else goes through here.
func WithContext ¶
WithContext returns a copy of parent carrying the given auth.Context.
func WriteForbidden ¶
func WriteForbidden(w http.ResponseWriter, code string)
WriteForbidden is a small helper for handler-side capability checks so they can produce the same error shape as the middleware.
Types ¶
type AuthMode ¶
type AuthMode string
AuthMode controls which auth flavours the middleware accepts.
Basic: only HTTP Basic; signed requests are rejected with 401 Signed: only RFC 9421 signed; Basic is rejected with 401 Both: accept whichever the request presents (preferring signed)
In Basic mode with empty admin user/pass, the chassis falls through to "open dev" (current pre-auth behavior).
Browser session cookies (`txco_session=<id>`) are accepted on top of any mode whenever Config.Sessions is non-nil — they're additive, not a mode of their own. The cookie path resolves the session through Sessions.GetSession and stamps a browser-Source auth.Context. CSRF is enforced via SameSite=Strict at the cookie level plus an Origin allowlist check on mutation methods (handled here).
type Config ¶
type Config struct {
Mode AuthMode
// Basic-auth credentials. When both are empty AND Mode != Signed,
// the middleware enters open-dev mode for backward compat.
BasicUser string
BasicPass string
// Signed-request inputs.
Registry *registry.Registry
Verifier signature.Verifier
Nonces *NonceStore
Skew time.Duration // clock-skew window for `created`; defaults to 5min
// Browser-session inputs. When Sessions is non-nil, the middleware
// also accepts `Cookie: txco_session=<id>`. AllowedOrigins lists
// the Origin header values acceptable on mutation requests (POST/
// PUT/PATCH/DELETE) carrying a session cookie — used as a CSRF
// belt-and-braces on top of SameSite=Strict. An empty allowlist
// disables the Origin check (only safe in tests).
Sessions SessionStore
AllowedOrigins []string
// Logger is optional; when nil the middleware stays quiet.
Logger func(msg string, fields map[string]any)
}
Config bundles everything the middleware needs to authenticate a request. Built once at admin server startup and reused per request.
type Context ¶
type Context struct {
Source string
ActorID string
KeyID string
// Tenant is the verified actor's legacy `actors.tenant` column.
// As of phase 1 of the multi-tenancy work, scoping is mediated by
// the URL's tenant prefix (TenantSlug/TenantID below) and by
// memberships rather than this column. Kept readable for tooling
// that hasn't migrated yet; do not introduce new readers.
Tenant string
// TenantSlug and TenantID are set by the admin server's tenant
// resolver middleware when the request path is `/v1/tenants/{t}/…`.
// Empty on chassis-wide endpoints (/auth/whoami, /healthz, etc.).
// Phase 3 makes RequireCapability gate on these.
TenantSlug string
TenantID string
// SuperAdmin mirrors `actors.super_admin`. When true,
// RequireCapability short-circuits to allow regardless of
// memberships.
SuperAdmin bool
Capabilities []string
}
Context is what the auth middleware attaches to each authenticated request. Handlers read it via FromContext and pass it to policy.RequireCapability.
`Source` distinguishes how the caller authenticated:
- "signed": RFC 9421 signed request from an enrolled actor
- "basic": legacy HTTP basic auth (synthetic admin:all)
- "open": no auth required (dev mode with --admin-user="")
Basic-auth and open callers carry a non-empty Source but an empty ActorID — there's no actor record on disk for them; the capabilities are synthesized in-memory. Signed callers always have ActorID set.
func FromContext ¶
FromContext returns the auth.Context attached by middleware, or nil if none is set (which means the request bypassed auth, e.g. /healthz).
type NonceStore ¶
type NonceStore struct {
// contains filtered or unexported fields
}
NonceStore backs replay-defense. The middleware calls Use on every signed request; ErrReplay means the (key, nonce) pair was already seen within its TTL.
Backed by an in-process sliding-window structure: N+1 buckets that rotate every ttl/N. Inserts go into the head bucket; lookups scan all buckets; on rotation, the oldest bucket is cleared wholesale, which gives O(1) eviction without a GC walk.
Per the design note in db/schema/sqlite/0005_auth.sql, nonces are local-only replay protection — never replicated, never persisted. Restart loss is bounded by the signature's created/expires freshness window (≤ Skew, typically 60s), so memory-only is the natural fit.
func NewNonceStore ¶
func NewNonceStore(ttl time.Duration) *NonceStore
NewNonceStore returns a NonceStore that retains (key_id, nonce) pairs for at least ttl. ttl ≤ 0 falls back to 10 minutes.
func (*NonceStore) CheckFunc ¶
func (s *NonceStore) CheckFunc(ctx context.Context) func(keyID, nonce string) error
CheckFunc returns a closure that wraps Use, matching the signature expected by signature.VerifyOptions.NonceCheck.
func (*NonceStore) Use ¶
func (s *NonceStore) Use(_ context.Context, keyID, nonce string) error
Use records the nonce as seen. Returns nil on success, ErrReplay if the same (key, nonce) pair is already on file (within TTL).
The ctx argument is unused — the in-memory path has no I/O — but stays on the signature so the middleware closure at chassis/auth/middleware.go doesn't need to change.
type SessionStore ¶
type SessionStore interface {
GetSession(ctx context.Context, sessionID string) (*registry.Session, error)
TouchSession(ctx context.Context, sessionID string, now time.Time) error
}
SessionStore is the minimal slice of registry behaviour the middleware needs to authenticate a browser session cookie. Keeping this as an interface (rather than importing the full registry type) avoids a cycle and lets tests swap in a fake.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package policy gates handlers behind capability checks.
|
Package policy gates handlers behind capability checks. |
|
Package registry reads (and minimally mutates) the actor / actor_keys / tenants / actor_memberships tables.
|
Package registry reads (and minimally mutates) the actor / actor_keys / tenants / actor_memberships tables. |
|
Package signature is the chassis-owned wrapper around RFC 9421 HTTP message signatures.
|
Package signature is the chassis-owned wrapper around RFC 9421 HTTP message signatures. |
|
Package throttle is a small per-key fixed-window rate limiter.
|
Package throttle is a small per-key fixed-window rate limiter. |