Documentation
¶
Overview ¶
Package apitoken is the scoped API-token model (plan §17.1): the keystone integration primitive — a new front DOOR, never a new trust PATH. A token is strictly LESS privileged than a browser session: its scope grammar has NO SYMBOL for a Tier-1 capability (key/allowlist/bind), nor for reveal-secret / raw-Caddy / setup-run / mint-another-token — those are simply not expressible, the same structural impossibility that makes the Tier-2/3 files dashboard-writable.
Tokens are minted ONLY over the CLI/SSH (web minting would be a privesc surface), stored argon2id-hashed (the id scopes the lookup so exactly one verify runs per request), carry a mandatory expiry + a non-empty CIDR set (a token is an auth factor, NOT an allowlist bypass), and are constant-time compared + revocable.
Index ¶
- Variables
- func CIDRUnion(records []Record, now int64) []netip.Prefix
- func ParseCIDRs(cidrs []string) ([]netip.Prefix, error)
- func ParseScopes(scopes []string) ([]string, error)
- func SplitBearer(bearer string) (id, secret string, ok bool)
- func ValidID(s string) bool
- func ValidScope(s string) bool
- type Minted
- type Record
- type Store
- func (s *Store) ActiveCIDRUnion(ctx context.Context, now time.Time) ([]netip.Prefix, error)
- func (s *Store) Get(ctx context.Context, id string) (Record, error)
- func (s *Store) Insert(ctx context.Context, r Record, label string, now time.Time) error
- func (s *Store) List(ctx context.Context) ([]Record, error)
- func (s *Store) Revoke(ctx context.Context, id string) error
- func (s *Store) RevokeAppScoped(ctx context.Context, slug string) (int64, error)
- func (s *Store) TouchLastUsed(ctx context.Context, id string, now time.Time)
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("apitoken: not found")
ErrNotFound means no token row matched the id (an unknown/expired/garbage id looks identical to a wrong secret to the caller — no enumeration oracle).
Functions ¶
func CIDRUnion ¶
CIDRUnion returns the union of every ACTIVE token's CIDR set — the precomputed set the IP allowlist checks the unspoofable peer against BEFORE any bearer is parsed (so a token id can't be an enumeration oracle and an unknown bearer can't trigger a DB lookup before the peer is admitted).
func ParseCIDRs ¶
ParseCIDRs validates a non-empty CIDR set, rejecting a 0-bit prefix (0.0.0.0/0 or ::/0 would turn the bounded CI exception into a global allowlist bypass).
func ParseScopes ¶
ParseScopes validates every requested scope, rejecting the empty set and any unknown/over-broad symbol.
func SplitBearer ¶
SplitBearer extracts the (id, secret) from a bearer, or ok=false on any malformed input. It does NOT hit the DB — the caller looks up by id, so a malformed/unknown token is never an unauthenticated DB-lookup oracle.
func ValidScope ¶
ValidScope reports whether s is a permissible scope. The ONLY shapes are the read scopes and deploy:write:<slug> — there is deliberately no grammar for a Tier-1 / reveal / caddy / setup / mint capability, so a token can never express more than the API exposes.
Types ¶
type Minted ¶
Minted is the freshly-minted token: the one-time plaintext (shown ONCE) + the stored Record.
type Record ¶
type Record struct {
ID string
Hash string // argon2id-encoded
Scopes []string
CIDRs []netip.Prefix
ExpiresAt int64
Revoked bool
}
Record is one stored token (no plaintext secret — only the argon2id hash).
func (Record) VerifySecret ¶
VerifySecret constant-time-checks the presented secret against the record and its active window. The argon2id verify ALWAYS runs first (even for an expired/revoked record) so the cost is identical whether or not the token is active — an expired/revoked id must not be distinguishable by latency from a live one (the active() check used to short-circuit before argon2, which leaked a revocation/expiry timing oracle; M19 review). The id already selected the single record, so the verify still runs at most ONCE per request.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists api_tokens. The id selects exactly one row, so a request runs at most one argon2id verify; minting is CLI-only (no web path constructs a Store write for token creation).
func (*Store) ActiveCIDRUnion ¶
ActiveCIDRUnion returns the union of every currently-active token's CIDR set — the precomputed allowlist addition the IP gate checks the peer against BEFORE any bearer is parsed.
func (*Store) Get ¶
Get loads one token by id. It returns ErrNotFound for any id that is malformed or absent — the caller cannot distinguish "no such token" from "wrong secret".
func (*Store) Insert ¶
Insert persists a freshly-minted Record (the plaintext is never stored). label is an informational operator note.
func (*Store) List ¶
List returns all tokens (for the CLI listing + the CIDR-union recompute on startup/SIGHUP).
func (*Store) Revoke ¶
Revoke marks a token revoked (idempotent). A revoked token fails active() and is excluded from the CIDR union on the next recompute.
func (*Store) RevokeAppScoped ¶
RevokeAppScoped revokes any token whose ONLY capability is deploying THIS app (scopes == "deploy:write:<slug>"), used by the app-delete teardown. It deliberately leaves multi-scope tokens alone — yanking a token that also serves other apps would be collateral damage, and a leftover scope for a now-gone app is inert (its deploy route no longer resolves). Returns the number of tokens revoked.