auth

package
v0.44.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package auth closes the Brevis interface behind an operator credential.

It exists for a concrete reason: in dev, an anonymous `POST /workflows/<slug>/trigger` answered 303 and started the pipeline. Anyone on the internet could run a `dbt build` that MERGEs into the warehouse. An orchestration interface is a remote control for the warehouse -- leaving it open is the same as publishing the terminal.

The scope is deliberately small: ONE operator credential, from the configuration. There is no user registry, no roles and no multi-user, because none of that exists in the product yet and inventing it here would be building the floor before the wall. What does exist has to be right: a slow derivation hash, a signed session, constant-time comparison.

All stdlib. `crypto/pbkdf2` landed in the standard library in Go 1.24, which removes the need for `x/crypto` for the one piece that was missing.

Index

Constants

View Source
const CodeSignInRequired = "sign_in_required"

CodeSignInRequired is the error code of every 401 the gate answers: a session is missing or has expired. The server speaks codes and never sentences; the app says it in the reader's language and goes to its login.

View Source
const CookieName = "brevis_session"

CookieName is the session cookie's name.

View Source
const LegacyCookieName = "brevis_sessao"

LegacyCookieName is what releases before 0.43 called the session cookie ("sessao" is Portuguese for "session"). It is only READ: a session signed in under it stays valid until its own expiry, so upgrading signs nobody out, and every new session is written under CookieName. shortcut: one session lifetime (SessionLifetime) after 0.43 every such cookie has expired; delete this and the two fallbacks below in the next release.

View Source
const NextParam = "next"

NextParam is the query parameter carrying where the operator was going.

It is a constant because it has two ends -- the redirect that writes it, in this file, and the login screen that reads it, in internal/api -- and they were once different words.

View Source
const SessionLifetime = 12 * time.Hour

SessionLifetime is how long a login lasts. A working shift: short enough that a tab forgotten on a laptop does not become permanent access, long enough not to ask for a password in the middle of an investigation.

View Source
const (

	// SharedKeyMaxLock caps the lockout when every client may share one key
	// (no trusted client-IP header): a guesser must not be able to keep the
	// operator out of the console for 15 minutes at a time.
	SharedKeyMaxLock = 30 * time.Second
)

Variables

This section is empty.

Functions

func Anonymous added in v0.43.0

func Anonymous(ctx context.Context) bool

Anonymous reports that the console has a credential and this request has no valid session, on a path the Gate lets through without one. False on an open console (nobody is asked to sign in, so nobody is a stranger) and on every request that did sign in.

func CheckPassword

func CheckPassword(hash, password string) bool

CheckPassword compares the password against the hash in constant time.

Returns false -- and not an error -- for a malformed hash: the caller is on a login path, and the only safe answer there is "did not get in". The configuration error is caught at boot, by Credential.Validate.

func ClientKey

func ClientKey(r *http.Request, header string) string

ClientKey is the key an attempt is counted under: the address of the client, IPv6 reduced to its /64 (one subscriber holds a whole /64, so rotating inside it must not reset the counters). With header set (BREVIS_AUTH_CLIENT_IP_HEADER, for a deployment behind a proxy we trust) it is the LAST entry of that header -- the one our proxy appended; the first is whatever the client sent. Absent or invalid, it falls back to the connection's own address.

func GenerateHash

func GenerateHash(password string) (string, error)

GenerateHash produces the text that goes into the configuration, as `pbkdf2-sha256$<iterations>$<salt>$<key>`.

The format carries the iteration count with it because that number will change: when we double the cost a few years from now, old hashes have to keep verifying. A format that stores only the digest forces invalidating everyone.

func IntoContext

func IntoContext(ctx context.Context, user string) context.Context

IntoContext stores the request's operator. The layout uses it to decide whether to show the sign-out button -- an installation with no credential should not display a button that does nothing.

func OperatorOf added in v0.43.0

func OperatorOf(ctx context.Context) string

OperatorOf returns the request's operator, or empty when there is no session.

func Target

func Target(raw string) string

Target sanitizes `?next=` when redirecting after the login.

Types

type Credential

type Credential struct {
	User string
	Hash string

	// Secret signs the session cookie. Changing it drops every session, which
	// is the emergency lever when a leak is suspected.
	Secret []byte

	// ClientIPHeader names the header a trusted proxy fills with the client
	// address (see ClientKey); empty uses the connection's address.
	ClientIPHeader string
}

Credential is the installation's single operator, coming from the configuration.

func (Credential) Enabled

func (c Credential) Enabled() bool

Enabled says whether a credential is configured.

func (Credential) Validate

func (c Credential) Validate() error

Validate refuses a half-finished configuration.

Half configured is worse than nothing: whoever filled in the username believes they closed the door. Failing at boot is the only way that belief does not last until the incident.

type Gate

type Gate struct {
	Cred     Credential
	Next     http.Handler
	Login    http.Handler // renders the login screen
	Insecure bool         // plain http: sends the cookie without the Secure flag

	// Limit throttles Attempt; nil gets a default on first use.
	Limit *Limiter
	// Check verifies a password against the hash; nil is CheckPassword.
	// Injectable so a test can count the hashes that run.
	Check func(hash, password string) bool
	// contains filtered or unexported fields
}

Gate wraps a handler, requiring a valid session.

The routes that need no session are few and explicit. Kubernetes probes are on that list out of necessity -- a /health that asks for a password kills the pod.

func (*Gate) Attempt

func (p *Gate) Attempt(ctx context.Context, w http.ResponseWriter, source, user, password string) (Outcome, time.Duration)

Attempt is SignIn behind the Limiter, keyed by source (the client address). A Locked or Busy answer did not run the hash.

func (*Gate) ServeHTTP

func (p *Gate) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Gate) SignIn

func (p *Gate) SignIn(w http.ResponseWriter, user, password string) bool

SignIn checks the credential and writes the cookie. Returns false if it did not match.

func (*Gate) SignOut

func (p *Gate) SignOut(w http.ResponseWriter)

SignOut deletes the cookie, under both names: a session that was signed in under the legacy one must end too.

type Limiter

type Limiter struct {

	// MaxLock caps one lockout; zero is 15 minutes.
	MaxLock time.Duration
	// contains filtered or unexported fields
}

Limiter bounds login attempts: a few hashes at a time, one per source, and an exponential lockout per source after repeated failures. In memory and per process; shortcut: with several replicas each keeps its own counters, so the effective limit is multiplied by the replica count -- move to a shared store if Brevis ever runs more than a couple.

func NewLimiter

func NewLimiter() *Limiter

func (*Limiter) Do

func (l *Limiter) Do(ctx context.Context, source string, check func() bool) (out Outcome, retry time.Duration)

Do runs check (the expensive hash) for source unless the source is locked, already has a hash running, or no slot frees up within slotWait, and records the result. retry is how long a Locked source must wait.

type Outcome

type Outcome int

Outcome is what one throttled login attempt came to.

const (
	Signed  Outcome = iota // the credential matched
	Refused                // it did not
	Locked                 // this source failed too often; the hash was not run
	Busy                   // every hash slot was taken; the hash was not run
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL