Help us shape the future of Go! Take the 2026 Go Developer Survey

operator

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package operator is the operator-surface authentication domain (ADR-0021): the single operator's password, browser sessions, and operator tokens. It is transport-agnostic; the command line and the REST façade are two callers of the same functions. Persistence is behind Store, implemented with hand-written SQL in internal/store (ADR-0006).

Version 1 has one operator and binary authorization: a caller is authenticated or it is not. Both credential forms (a session from a password login, or a bearer token) resolve to the same principal (authn.Principal), and nothing downstream can tell which one was presented.

Index

Constants

View Source
const DefaultSessionTTL = 7 * 24 * time.Hour

DefaultSessionTTL is the fixed lifetime of a session. It is set at creation and never extended: the longest a leaked cookie stays useful is bounded by the clock, not by activity (ADR-0021).

View Source
const MinPasswordLength = 12

MinPasswordLength is the shortest password SetPassword accepts. Length is the one property of a password that reliably matters; composition rules are not enforced.

View Source
const TokenPrefix = "iwo_"

TokenPrefix marks an operator token in transit. It differs from the provisioning token's prefix so a secret scanner, and the surface, can tell the two apart without knowledge of the deployment that minted them.

Variables

View Source
var (
	// ErrNoOperator means no password has ever been set. The surface maps
	// it to a distinct problem type so a fresh install is recognizable.
	ErrNoOperator = errors.New("operator: no password has been set")
	// ErrPasswordMismatch means the password did not verify.
	ErrPasswordMismatch = errors.New("operator: password does not match")
	// ErrPasswordTooShort means a new password is below MinPasswordLength.
	ErrPasswordTooShort = errors.New("operator: password is too short")

	ErrSessionMalformed = errors.New("operator: session identifier is malformed")
	ErrSessionUnknown   = errors.New("operator: session is not recognized")
	ErrSessionExpired   = errors.New("operator: session has expired")

	ErrTokenMalformed = errors.New("operator: operator token is malformed")
	ErrTokenUnknown   = errors.New("operator: operator token is not recognized")
	ErrTokenExpired   = errors.New("operator: operator token has expired")
	ErrTokenRevoked   = errors.New("operator: operator token has been revoked")
)

Errors the domain reports. They are distinct so an operator sees exactly why a credential was refused; none of them reveals anything a caller without the credential could not already guess.

Functions

func HashEqual

func HashEqual(a, b []byte) bool

HashEqual compares two digests in constant time.

func HashPassword

func HashPassword(password string) (string, error)

HashPassword computes an argon2id hash of password and returns it in the self-describing form

$argon2id$v=19$m=<KiB>,t=<iterations>,p=<lanes>$<salt>$<hash>

with salt and hash in unpadded standard base64.

func HashSessionID

func HashSessionID(plaintext string) ([]byte, error)

HashSessionID validates the shape of a session identifier and returns its SHA-256 digest, or ErrSessionMalformed.

func HashToken

func HashToken(plaintext string) ([]byte, error)

HashToken validates the shape of a plaintext token and returns its SHA-256 digest. Shape validation happens before hashing so a malformed value is reported as malformed rather than as unknown.

func NewAuthenticator added in v0.5.0

func NewAuthenticator(s *Service) *authn.Chain

NewAuthenticator composes the operator domain's verifiers, one per credential form, over its session minter.

func NewSessionID

func NewSessionID() (plaintext string, hash []byte, err error)

NewSessionID draws a fresh session identifier. The plaintext goes to the browser; the hash is what gets stored.

func NewToken

func NewToken() (plaintext string, hash []byte, prefix string, err error)

NewToken draws a fresh operator token. The plaintext is returned once, for display to the operator; the hash is stored, and the prefix is what a listing shows.

func VerifyPassword

func VerifyPassword(encoded, password string) error

VerifyPassword reports whether password matches the stored hash, recomputing with the parameters the hash carries. It returns ErrPasswordMismatch on a wrong password and a distinct error on a hash that cannot be parsed, because the two mean different things to an operator.

Types

type Operator

type Operator struct {
	PasswordHash string
	DisplayName  *string
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

Operator is the single operator row: the credential and the attributes beside it. DisplayName is optional; a surface renders a fallback when it is nil.

type PasswordVerifier added in v0.5.0

type PasswordVerifier struct{ Service *Service }

PasswordVerifier verifies the operator password. It reports ErrNoOperator when no password has been set and ErrPasswordMismatch when it does not verify.

func (PasswordVerifier) Form added in v0.5.0

func (PasswordVerifier) Form() authn.Form

Form implements authn.Verifier.

func (PasswordVerifier) Verify added in v0.5.0

func (v PasswordVerifier) Verify(ctx context.Context, password string) (*authn.Principal, error)

Verify implements authn.Verifier.

type Service

type Service struct {
	Store Store
	// SessionTTL is the lifetime of a session; DefaultSessionTTL if zero.
	SessionTTL time.Duration
	// Now is the clock; time.Now if nil.
	Now func() time.Time
}

Service is the operator authentication domain.

func (*Service) CreateSession

func (s *Service) CreateSession(ctx context.Context) (plaintext string, sess Session, err error)

CreateSession draws a session and persists its hash. The plaintext is returned once, for the cookie.

func (*Service) DeleteSession

func (s *Service) DeleteSession(ctx context.Context, plaintext string) error

DeleteSession revokes a session. A malformed or unknown identifier is not an error: the outcome the caller wants, that the identifier no longer authenticates, already holds.

func (*Service) ListTokens

func (s *Service) ListTokens(ctx context.Context) ([]Token, error)

ListTokens returns every token, newest first.

func (*Service) MintToken

func (s *Service) MintToken(ctx context.Context, name string, ttl time.Duration) (plaintext string, tok Token, err error)

MintToken creates an operator token with the given lifetime (none if ttl is zero) and returns the plaintext exactly once.

func (*Service) PruneSessions

func (s *Service) PruneSessions(ctx context.Context, now time.Time) (int64, error)

PruneSessions deletes expired sessions. It rides the retention schedule the flow store runs (ADR-0021); there is no job of its own.

func (*Service) RevokeToken

func (s *Service) RevokeToken(ctx context.Context, id uuid.UUID) error

RevokeToken revokes a token as of now.

func (*Service) SetPassword

func (s *Service) SetPassword(ctx context.Context, password string, displayName *string) error

SetPassword creates the operator or replaces its password, and sets the display name (nil clears it). It refuses a password shorter than MinPasswordLength.

func (*Service) ValidateSession

func (s *Service) ValidateSession(ctx context.Context, plaintext string) (*authn.Principal, error)

ValidateSession resolves a session identifier to the principal. Expiry is judged here, at the instant of use, so a session past its lifetime is refused whether or not retention has pruned it yet.

func (*Service) ValidateToken

func (s *Service) ValidateToken(ctx context.Context, plaintext string) (*authn.Principal, error)

ValidateToken resolves a bearer token to the principal and records the use. The token is checked before anything else is read, so a caller without a valid token learns nothing.

func (*Service) VerifyPassword

func (s *Service) VerifyPassword(ctx context.Context, password string) error

VerifyPassword checks password against the stored hash. It returns ErrNoOperator when no password has been set and ErrPasswordMismatch when it does not verify.

type Session

type Session struct {
	IDHash    []byte
	CreatedAt time.Time
	ExpiresAt time.Time
}

Session is the stored form of a browser session. IDHash is the SHA-256 digest of the identifier the browser holds; the identifier itself is never stored.

func (*Session) Check

func (s *Session) Check(now time.Time) error

Check reports whether the session is live at instant now.

type SessionMinter added in v0.5.0

type SessionMinter struct{ Service *Service }

SessionMinter mints and ends sessions with the domain's fixed lifetime.

func (SessionMinter) End added in v0.5.0

func (m SessionMinter) End(ctx context.Context, id string) error

End implements authn.Minter.

func (SessionMinter) Mint added in v0.5.0

Mint implements authn.Minter.

type SessionVerifier added in v0.5.0

type SessionVerifier struct{ Service *Service }

SessionVerifier verifies a session identifier a login minted.

func (SessionVerifier) Form added in v0.5.0

func (SessionVerifier) Form() authn.Form

Form implements authn.Verifier.

func (SessionVerifier) Verify added in v0.5.0

func (v SessionVerifier) Verify(ctx context.Context, id string) (*authn.Principal, error)

Verify implements authn.Verifier.

type Store

type Store interface {
	// GetOperator returns ErrNoOperator when no password has been set.
	GetOperator(ctx context.Context) (*Operator, error)
	// SetOperator creates or replaces the single operator row.
	SetOperator(ctx context.Context, op Operator) error

	// CreateSession persists a session (hash only).
	CreateSession(ctx context.Context, s Session) error
	// GetSession returns ErrSessionUnknown when no session has that hash.
	// Expiry is the domain's judgement, not the store's.
	GetSession(ctx context.Context, idHash []byte) (*Session, error)
	// DeleteSession removes a session; an unknown hash is not an error.
	DeleteSession(ctx context.Context, idHash []byte) error
	// DeleteExpiredSessions removes every session whose expiry is at or
	// before now and reports how many.
	DeleteExpiredSessions(ctx context.Context, now time.Time) (int64, error)

	// CreateOperatorToken persists a minted token (hash only).
	CreateOperatorToken(ctx context.Context, t Token) error
	// FindOperatorTokenByHash returns ErrTokenUnknown when no token has that hash.
	FindOperatorTokenByHash(ctx context.Context, hash []byte) (*Token, error)
	// ListOperatorTokens returns every token, newest first.
	ListOperatorTokens(ctx context.Context) ([]Token, error)
	// RevokeOperatorToken marks a token revoked at instant now. It returns
	// ErrTokenUnknown for an unknown id and ErrTokenRevoked if it already was.
	RevokeOperatorToken(ctx context.Context, id uuid.UUID, now time.Time) error
	// RecordOperatorTokenUse stores the instant a token last authenticated a call.
	RecordOperatorTokenUse(ctx context.Context, id uuid.UUID, now time.Time) error
}

Store is the persistence the domain needs.

type Token

type Token struct {
	ID         uuid.UUID
	Hash       []byte
	Prefix     string
	Name       string
	CreatedAt  time.Time
	ExpiresAt  *time.Time
	RevokedAt  *time.Time
	LastUsedAt *time.Time
}

Token is the stored form of an operator token. It never carries the plaintext: Hash is what is looked up, Prefix is what is listed.

func (*Token) Check

func (t *Token) Check(now time.Time) error

Check reports whether the token may authenticate at instant now. Revocation is checked before expiry so a revoked token reports as revoked even after it would have expired anyway; the operator's action is the more specific fact.

type TokenVerifier added in v0.5.0

type TokenVerifier struct{ Service *Service }

TokenVerifier verifies a bearer operator token and records its use.

func (TokenVerifier) Form added in v0.5.0

func (TokenVerifier) Form() authn.Form

Form implements authn.Verifier.

func (TokenVerifier) Verify added in v0.5.0

func (v TokenVerifier) Verify(ctx context.Context, token string) (*authn.Principal, error)

Verify implements authn.Verifier.

Directories

Path Synopsis
Package operatortest holds test doubles for the operator domain.
Package operatortest holds test doubles for the operator domain.

Jump to

Keyboard shortcuts

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