auth

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: GPL-3.0 Imports: 29 Imported by: 0

Documentation

Index

Constants

View Source
const SessionCookie = "streamline_session"

SessionCookie is the name of the httpOnly cookie that carries the webui session JWT. API clients (Authorization: Bearer, X-API-Key) must NOT use it.

Variables

View Source
var (
	// ErrPasswordInvalid is returned when the supplied current password does
	// not match the stored hash. Handlers map to 401.
	ErrPasswordInvalid = errors.New("current password invalid")

	// ErrPasswordWeak is returned when a new password fails the strength
	// policy. Handlers map to 422.
	ErrPasswordWeak = errors.New("new password fails policy")

	// ErrAPIKeyNotFound is returned when no key matches the (userID, keyID)
	// tuple — either the key does not exist or it belongs to another user.
	// Handlers must not leak ownership information.
	ErrAPIKeyNotFound = errors.New("api key not found")
)

Account-related sentinel errors. Handlers translate these to HTTP codes.

View Source
var (
	// ErrSelfDeleteForbidden is returned when an admin attempts to delete
	// their own account. Handlers map to 409 self_delete_forbidden.
	ErrSelfDeleteForbidden = errors.New("cannot delete yourself")

	// ErrLastAdmin is returned when demoting or deleting a user would leave
	// the system without any admin. Handlers map to 409 last_admin.
	ErrLastAdmin = errors.New("cannot remove the last admin")

	// ErrUserEmailExists is returned when direct user creation collides with
	// an existing email. Handlers map to 409 email_exists.
	ErrUserEmailExists = errors.New("email already registered")

	// ErrUserNotFound is returned when the target user does not exist.
	// Handlers map to 404.
	ErrUserNotFound = errors.New("user not found")

	// ErrAccountLocked is the sentinel callers compare against using errors.As
	// (errors.Is also works because Unwrap is not defined). The runtime value
	// returned by Login carries the auto-expiry time in LockedUntil.
	ErrAccountLocked = ErrAccountLockedT{}
)

Admin-only sentinel errors. Handlers translate these to HTTP codes.

View Source
var (
	ErrOIDCEmailUnverified = errors.New("oidc_email_unverified")
	ErrOIDCRegDisabled     = errors.New("oidc_registration_disabled")
	ErrOIDCNoInvite        = errors.New("oidc_no_invite")
)

OIDC sentinel errors translated to user-facing messages by the webui.

View Source
var (
	// ErrSessionRevoked is returned when a session row has a non-nil revoked_at.
	ErrSessionRevoked = errors.New("session revoked")

	// ErrSessionExpired is returned when a session row's expires_at is in the past.
	// Middleware usually catches this via JWT expiration first; this covers the
	// edge case of a clock-skewed token or a session revoked-by-expiry.
	ErrSessionExpired = errors.New("session expired")

	// ErrSessionNotFound is returned when no row matches the jti.
	ErrSessionNotFound = errors.New("session not found")
)

Session-related sentinel errors. Middleware translates these to 401/302.

View Source
var ErrInviteInvalid = errors.New("invite invalid or expired")

Functions

func ClearSession

func ClearSession(w http.ResponseWriter, r *http.Request)

ClearSession expires the session cookie.

func ClearTransientCookies

func ClearTransientCookies(w http.ResponseWriter, r *http.Request, names ...string)

ClearTransientCookies expires the named cookies.

func ContextWithClaims

func ContextWithClaims(ctx context.Context, claims *Claims) context.Context

ContextWithClaims returns a copy of ctx with claims attached. Used by the HTTP auth middleware after authentication succeeds.

func IsLocked

func IsLocked(s LockoutState, now time.Time) (bool, time.Time)

IsLocked reports whether the account is locked at now and, if so, when the lockout expires.

func JTILogValue

func JTILogValue(jti string) string

JTILogValue returns a session.id_hash value that identifies a session in logs without leaking the raw jti (which, combined with the signing secret, is the session bearer credential). The 16-char prefix of the SHA-256 hex digest is enough to correlate log lines while being computationally infeasible to reverse.

func ReadTransientCookies

func ReadTransientCookies(r *http.Request, names ...string) map[string]string

ReadTransientCookies returns a map of name→value for each requested cookie. Missing cookies map to empty string.

func SetSession

func SetSession(
	w http.ResponseWriter,
	r *http.Request,
	token string,
	ttl time.Duration,
)

SetSession writes the session cookie. Secure flag tracks the current request's transport so local http development still works.

func SetTransientCookie

func SetTransientCookie(w http.ResponseWriter, r *http.Request, name, value string)

SetTransientCookie writes a short-lived httpOnly cookie scoped to /auth/oidc/. Used to carry state + nonce + PKCE verifier across the provider redirect round-trip.

Types

type Claims

type Claims struct {
	UserID      uint32 `json:"user_id"`
	Email       string `json:"email"`
	DisplayName string `json:"display_name,omitempty"`
	Role        string `json:"role"`
	JTI         string `json:"jti,omitempty"`
	jwt.RegisteredClaims
}

Claims embedded in JWT tokens.

JTI (JWT ID) is the per-session handle. It maps 1:1 to a row in the sessions table so the server can revoke the session without rotating the signing secret. Generated by issueToken; validated by middleware on every request against the sessions table.

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) *Claims

ClaimsFromContext extracts auth claims from ctx. Returns nil when no middleware has run upstream (e.g. /health probes that bypass auth).

type ErrAccountLockedT

type ErrAccountLockedT struct {
	LockedUntil time.Time
}

ErrAccountLockedT is returned by Login when the account has been locked after too many failed attempts. LockedUntil reports when the lockout auto-expires; handlers surface this to users via Retry-After / banner.

func (ErrAccountLockedT) Error

func (e ErrAccountLockedT) Error() string

type Limiter

type Limiter interface {
	Allow(key string) bool
	RetryAfter(key string) time.Duration
}

Limiter is the consumer-facing surface used by login and OIDC handlers to throttle credential attempts per IP.

func NewLimiter

func NewLimiter(limit uint8, window time.Duration) Limiter

NewLimiter returns a Limiter allowing `limit` hits per key within `window`.

func NewLimiterWithClock

func NewLimiterWithClock(
	limit uint8,
	window time.Duration,
	now func() time.Time,
) Limiter

NewLimiterWithClock is NewLimiter with a pluggable clock for deterministic tests.

type LockoutPolicy

type LockoutPolicy struct {
	Threshold uint8
	Window    time.Duration
	Duration  time.Duration
}

LockoutPolicy is the tunable knobs read fresh from config on each login.

type LockoutState

type LockoutState struct {
	FailedCount  uint8
	LastFailedAt *time.Time
	LockedUntil  *time.Time
}

LockoutState mirrors the three lockout columns on the user row. Pointers distinguish "no failure yet" / "no active lockout" from zero values.

func OnFailedAttempt

func OnFailedAttempt(s LockoutState, now time.Time, p LockoutPolicy) LockoutState

OnFailedAttempt advances state for one mismatched-password attempt. Resets the counter when the previous failure has fallen out of the sliding window. Stamps LockedUntil when the new counter meets the threshold.

type Manager

type Manager interface {
	// Service: core auth + JWT + API key
	Login(
		ctx context.Context,
		email, password string,
		meta SessionMeta,
	) (string, error)
	ValidateToken(token string) (*Claims, error)
	ValidateAPIKey(ctx context.Context, key string) (*ent.User, error)
	GetUserByID(ctx context.Context, id uint32) (*ent.User, error)
	CreateAPIKey(
		ctx context.Context,
		userID uint32,
		name string,
	) (string, *ent.ApiKey, error)
	RotateJWTSecret(ctx context.Context, callerID uint32) (string, error)

	// Account: profile + own credentials/keys
	UpdateProfile(
		ctx context.Context,
		userID uint32,
		displayName string,
	) (*ent.User, error)
	ChangePassword(
		ctx context.Context,
		userID uint32,
		current, newPassword, keepJTI string,
	) error
	ListAPIKeys(ctx context.Context, userID uint32) ([]*ent.ApiKey, error)
	RevokeAPIKeyByID(ctx context.Context, userID, keyID uint32) error

	// OIDC
	LoginOIDC(
		ctx context.Context,
		provider, subject, email, displayName string,
		emailVerified bool,
		claims map[string]any,
		meta SessionMeta,
	) (*ent.User, string, error)

	// Admin: user CRUD + admin actions
	ListUsers(ctx context.Context, f UserFilter) ([]*ent.User, int, error)
	CreateUserDirect(
		ctx context.Context,
		email, password, role, displayName string,
	) (*ent.User, error)
	GetUserDetail(
		ctx context.Context,
		id uint32,
	) (*ent.User, []*ent.ApiKey, []*ent.Session, error)
	UpdateUser(ctx context.Context, id uint32, p UserPatch) error
	DeleteUser(ctx context.Context, id, requesterID uint32) error
	AdminResetPassword(ctx context.Context, id uint32, newPassword string) error
	AdminRevokeAPIKey(ctx context.Context, userID, keyID uint32) error
	AdminRevokeSession(ctx context.Context, userID, sessionID uint32) error
	Unlock(ctx context.Context, email string, mode UnlockMode) error
	AdminUnlock(ctx context.Context, id uint32) error

	// Invites
	CreateInvite(
		ctx context.Context,
		createdByID uint32,
		email, role string,
		ttl time.Duration,
	) (string, *ent.Invite, error)
	LookupInviteForPrefill(ctx context.Context, rawToken string) (*ent.Invite, error)
	ListInvites(ctx context.Context) ([]*ent.Invite, error)
	RevokeInvite(ctx context.Context, id uint32) error
	RegisterWithInvite(
		ctx context.Context,
		rawToken, email, password, displayName string,
		meta SessionMeta,
	) (*ent.User, string, error)

	// Bootstrap + open registration
	BootstrapSeedAdmin(ctx context.Context) error
	RegisterOpen(
		ctx context.Context,
		email, password, displayName, defaultRole string,
		meta SessionMeta,
	) (*ent.User, string, error)

	// Sessions
	ValidateSession(ctx context.Context, jti string) error
	TouchSessionAsync(jti string)
	Shutdown(ctx context.Context) error
	RevokeSession(ctx context.Context, jti string) error
	RevokeSessionByID(ctx context.Context, userID, sessionID uint32) error
	ListUserSessions(ctx context.Context, userID uint32) ([]*ent.Session, error)
	PurgeExpiredSessions(ctx context.Context, before time.Time) (int, error)
}

Manager is the consumer-facing surface used by HTTP handlers, the auth middleware, the scheduler, and the composition root.

func New

func New(store db.Store) (Manager, error)

New constructs a Manager using session credentials from the process-wide config singleton. Returns an error if auth.session_ttl fails to parse.

type OIDCManager

type OIDCManager interface {
	Init(ctx context.Context, redirectBase string)
	Get(name string) (*OIDCProvider, bool)
}

OIDCManager is the consumer-facing surface for resolving configured OIDC providers at HTTP request time and warming the provider cache at startup.

func NewOIDCManager

func NewOIDCManager() OIDCManager

type OIDCProvider

type OIDCProvider struct {
	Name     string
	Verifier *oidc.IDTokenVerifier
	OAuth2   *oauth2.Config
}

OIDCProvider bundles a verifier + OAuth2 config for one named provider.

type SessionMeta

type SessionMeta struct {
	// IP is the originating client IP. Handlers should use the proxy-aware
	// client IP (resolved by the chi ClientIPFrom* middleware), not RemoteAddr.
	IP string

	// UserAgent is the raw User-Agent header, truncated to 512 chars at the
	// storage layer (schema MaxLen). Self-reported, so trustworthy only as
	// a UX signal — never for authorization.
	UserAgent string
}

SessionMeta carries per-request metadata recorded on session creation. Callers (HTTP handlers) populate this from the incoming request; service methods store the values on the Session row for the user-facing session list and for audit.

type SessionPurger

type SessionPurger interface {
	PurgeExpiredSessions(ctx context.Context, before time.Time) (int, error)
}

SessionPurger is the consumer-facing surface for the periodic expired-session sweep. jobs.PurgeSessions accepts it so it can be driven by a fake in tests without standing up the full Service.

type UnlockMode

type UnlockMode string

UnlockMode tags the operator surface that triggered an unlock event. Surfaces in the auth.unlocked slog event so audit logs can distinguish admin-modal clicks from CLI invocations.

const (
	UnlockModeAdmin UnlockMode = "admin"
	UnlockModeCLI   UnlockMode = "cli"
)

type UserFilter

type UserFilter struct {
	Q      string
	Role   string
	Limit  uint16
	Offset uint32
	Sort   db.UserSort
	Order  db.UserOrder
}

UserFilter bundles the optional search, role, pagination, and ordering parameters accepted by ListUsers.

type UserPatch

type UserPatch struct {
	Email       *string
	Role        *string
	DisplayName *string
	AuthMethod  *string
}

UserPatch is the partial update bundle accepted by UpdateUser. Nil fields are preserved; non-nil fields are applied.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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