Documentation
¶
Index ¶
Constants ¶
const MaxClockSkew = 5 * time.Minute
MaxClockSkew is the maximum allowed difference between a request's Signature-Timestamp and the server's clock, in either direction.
Variables ¶
This section is empty.
Functions ¶
func HasSignatureHeaders ¶ added in v0.12.0
HasSignatureHeaders reports whether r carries request-signature headers at all -- i.e. whether the caller was *trying* to authenticate. Lets a handler tell "anonymous" apart from "authentication failed" without inspecting the error.
Types ¶
type Identity ¶
Identity is the authenticated caller of a signed request, injected into the request context by Middleware.Require.
type Middleware ¶
type Middleware struct {
DB store.DBTX
Logger *slog.Logger
// Now returns the current time; overridable in tests.
Now func() time.Time
// StreamedBodyPaths are routes whose bodies are too large to buffer for
// authentication (the blob upload), written as "METHOD /path/prefix".
// On these, and ONLY these, the canonical string's body hash is taken
// from the client's Blob-Digest header instead of from the body itself,
// so the signature can be checked before reading anything -- see
// authenticate.
//
// Deliberately an explicit allowlist rather than "honour the header
// whenever it appears": on a normal route, trusting a claimed digest
// would let a caller sign one body and send another, breaking the
// body-to-signature binding every other endpoint relies on. A route
// listed here MUST verify the body against the signed digest as it
// reads it (blobstore.Put does).
//
// The method is part of the entry because it is specifically the upload
// that streams: a GET or DELETE under the same path prefix has no body
// and must keep authenticating the ordinary way.
StreamedBodyPaths []string
// contains filtered or unexported fields
}
Middleware authenticates incoming requests using per-request Ed25519 signatures (see signature.go) against devices registered in db.
func NewMiddleware ¶
func NewMiddleware(db store.DBTX, logger *slog.Logger) *Middleware
NewMiddleware builds a Middleware backed by db, logging authentication failures (at Warn level, with detail) to logger. The nonce replay cache is created internally, so callers (and tests) need not manage it.
func (*Middleware) PurgeExpiredNonces ¶
func (m *Middleware) PurgeExpiredNonces(now time.Time) int
PurgeExpiredNonces drops replay-cache entries whose skew window has passed, returning the number removed. Intended to be called periodically from the server's cleanup ticker to keep the cache bounded during idle periods.
func (*Middleware) Require ¶
func (m *Middleware) Require(next http.Handler) http.Handler
Require wraps next so it only runs for requests with a valid signature, injecting the resulting Identity into the request context. Every failure mode (unknown key, bad signature, expired timestamp, replayed nonce, revoked device) produces the same generic 401 response, so as not to give an attacker an oracle; specifics go only to the log.
func (*Middleware) TryAuthenticate ¶ added in v0.12.0
func (m *Middleware) TryAuthenticate(r *http.Request) (Identity, error)
TryAuthenticate runs the same check [Require] does but returns the outcome instead of writing a response, for a handler that must decide for itself what an unauthenticated caller gets.
Only for routes where "no credentials" is a legitimate case with its own answer -- the prekey-bundle claim (SRV-04), which serves anyone but hands a one-time prekey only to a caller it can identify. Everything else belongs behind [Require], so the generic 401 stays the single answer to every failure mode and no handler can accidentally invent a weaker one.
Note the asymmetry a caller must preserve: an *absent* signature is a legitimate anonymous request, but a *present but invalid* one must still be refused. Silently treating a bad signature as anonymous would turn every client bug into a quiet downgrade.