authhandlers

package
v0.1.151 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: GPL-2.0, GPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package authhandlers provides shared types and utilities for the server's authentication handler cluster: login, WebAuthn, OIDC, admin user management, and security management (password change, API keys, passkeys).

Index

Constants

View Source
const (
	CookieNameHTTP   = "sfx_session"
	CookieNameSecure = "__Host-" + CookieNameHTTP
)

Session cookie names as they appear on the wire: the bare base name over plain HTTP (LAN, ip:port) and the __Host--prefixed Secure form over HTTPS. These are the two names SessionCookie's per-request posture alternates between; tests assert against them as subflux's observable cookie contract.

View Source
const (
	// CeremonyTTL is the maximum age for pending WebAuthn sessions.
	CeremonyTTL = authwebauthn.CeremonyTimeout

	// MaxCeremonySessions caps the in-memory ceremony maps to prevent OOM
	// from unauthenticated flooding of /api/auth/login or /api/auth/webauthn/login/begin.
	MaxCeremonySessions = 10000

	// CeremonyShards is the number of shards for ceremony maps.
	CeremonyShards = 16

	// HeaderWebAuthnSession is the HTTP header carrying the WebAuthn session token.
	HeaderWebAuthnSession = "X-WebAuthn-Session"
)
View Source
const AuditEventKind = "auth"

AuditEventKind is the fixed attribute value used to mark auth audit records. Filter on `event_kind="auth"` in log queries.

Variables

View Source
var SessionCookie = auth.CookieConfig{
	Posture:               auth.PosturePerRequest,
	Name:                  CookieNameHTTP,
	TrustForwardedHeaders: true,
}

SessionCookie is subflux's session-cookie configuration. Subflux serves both HTTP (LAN, ip:port) and HTTPS (behind a reverse proxy) from a single instance, so PosturePerRequest selects CookieNameSecure with the Secure flag over HTTPS and CookieNameHTTP without it over plain HTTP, per request. TrustForwardedHeaders honors X-Forwarded-Proto for the HTTPS decision; it is safe only because SanitizeForwardedProto strips that header from any request whose direct peer is not a configured trusted proxy.

Functions

func Audit

func Audit(r *http.Request, level slog.Level, event AuditEvent, success bool, user string, kvs ...any)

Audit emits a structured auth audit record at the specified slog level. Failures should emit at WARN; successes at INFO (so operator dashboards still see failures distinct from routine success traffic while auditors filter the full trail by event_kind).

`user` is the username when known; pass "" for failures on unknown usernames (still useful as the audit record will carry the IP and the failure reason).

Extra attributes are appended verbatim. Use `slog.String("reason", "invalid_password")` or `slog.String("key_id", id)` to keep the structured shape consistent.

func Base64URLEncode

func Base64URLEncode(data []byte) string

Base64URLEncode encodes bytes as base64url without padding.

func ClientIP

func ClientIP(r *http.Request) string

ClientIP extracts the client IP address from the request using the current trusted-proxy set. With no trusted proxies configured it returns the unspoofable socket-peer host and ignores X-Forwarded-For; when the direct peer is a trusted proxy it resolves the real client from a trusted X-Forwarded-For chain (spoof-safe, walked right-to-left). It delegates to webhttp.ClientIP, the shared spoof-aware resolver — passing no trusted ranges is byte-identical to the previous unconfigured behavior.

func GenerateCeremonyToken

func GenerateCeremonyToken() (string, error)

GenerateCeremonyToken generates a random hex token for ephemeral ceremony state.

func SanitizeForwardedProto added in v0.1.148

func SanitizeForwardedProto(next http.Handler) http.Handler

SanitizeForwardedProto strips the X-Forwarded-Proto header from any request whose direct peer is not in the configured trusted-proxy set (the same hot-reloadable set ClientIP consults). A trusted reverse proxy always overwrites the header for the requests it forwards, so the only surviving spoofing path is a direct connection — and for those the header must not influence the per-request cookie posture (a forged "https" over plain LAN HTTP would flip the session cookie to the __Host-/Secure form the browser then refuses to store or send). With no trusted proxies configured the header is stripped from every request: scheme detection falls back to the unspoofable r.TLS, and HTTPS-terminating deployments declare their proxy via trusted_proxies exactly as they already must for client-IP resolution.

func SetTrustedProxies added in v0.1.92

func SetTrustedProxies(nets []*net.IPNet)

SetTrustedProxies replaces the trusted reverse-proxy CIDR set used by ClientIP. Passing nil or an empty slice restores the trust-nothing default. It is safe for concurrent use and the new set takes effect on the next request, so a config hot-reload updates client-IP resolution without a restart.

func UnauthorizedResponse added in v0.1.148

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

UnauthorizedResponse writes subflux's unauthorized response: a 302 to /login for browsers and subflux's typed 401 JSON envelope otherwise. Installed on the library Authenticator via auth.WithUnauthorizedResponse so RequireAuth speaks subflux's error vocabulary.

func ValidateAndHashPassword

func ValidateAndHashPassword(ctx context.Context, password, username string, passwordOnly, checkBreach bool, client *http.Client) (hash, userMsg string, err error)

ValidateAndHashPassword validates password length and context (rejecting passwords that contain the username or app name), checks against breach databases (if checkBreach is true), and returns the Argon2id hash.

Types

type APIKeyInfo added in v0.1.148

type APIKeyInfo struct {
	CreatedAt time.Time `json:"created_at"`
	KeyPrefix string    `json:"key_prefix"`
	KeySuffix string    `json:"key_suffix"`
	Label     string    `json:"label"`
	ID        int64     `json:"id"`
}

APIKeyInfo is one entry of the GET /api/auth/apikeys response.

type AuditEvent

type AuditEvent string

AuditEvent enumerates the security-relevant events captured in the audit trail. Add new events here when introducing new auth flows.

const (
	AuditLoginSuccess     AuditEvent = "login.success"
	AuditLoginFailure     AuditEvent = "login.failure"
	AuditLoginRateLimited AuditEvent = "login.rate_limited"
	AuditLogout           AuditEvent = "logout"
	AuditPasswordChange   AuditEvent = "password.change"
	AuditPasskeyAdd       AuditEvent = "passkey.add"
	AuditPasskeyDelete    AuditEvent = "passkey.delete"
	AuditPasskeyRename    AuditEvent = "passkey.rename"
	AuditAPIKeyCreate     AuditEvent = "apikey.create"
	AuditAPIKeyRevoke     AuditEvent = "apikey.revoke"
	AuditOIDCCallback     AuditEvent = "oidc.callback"
)

AuditEvent constants enumerate the security-relevant events captured in the audit trail.

type AuthAdminStore

type AuthAdminStore interface {
	ListUsers(ctx context.Context) ([]auth.User, error)
	CreateUser(ctx context.Context, user *auth.User) error
	DeleteUser(ctx context.Context, id int64) error
}

AuthAdminStore is the narrow interface consumed by admin user management handlers.

type AuthConfig

type AuthConfig interface {
	BasicAuthEnabled() bool
	CheckBreachedPasswords() bool
	OIDCEnabled() bool
}

AuthConfig is the narrow interface consumed by auth handlers for configuration access. Mirrors the auth-related subset of api.ConfigProvider.

type CeremonyStore

type CeremonyStore struct {
	WebAuthn *ShardedCeremonyMap[*WebAuthnSession]
	Link     *ShardedCeremonyMap[*PendingLink]
}

CeremonyStore holds ephemeral ceremony state for auth flows. Owned by the Server struct to enable per-instance isolation in tests.

func NewCeremonyStore

func NewCeremonyStore() *CeremonyStore

NewCeremonyStore creates a new ceremony store.

func (*CeremonyStore) Cleanup

func (cs *CeremonyStore) Cleanup()

Cleanup removes expired pending WebAuthn sessions. Called periodically by the server.

func (*CeremonyStore) ConsumeWebAuthnSession

func (cs *CeremonyStore) ConsumeWebAuthnSession(token string) *webauthn.SessionData

ConsumeWebAuthnSession atomically retrieves and removes a WebAuthn session. Returns nil if the session is missing or expired.

type Handler

type Handler struct {
	Store       authstore.AuthStore
	AdminDB     AuthAdminStore
	SecDB       SecurityStore
	OidcDB      OIDCStore
	RateLimiter ratelimit.Checker
	// WebAuthnResolver resolves the current WebAuthn instance from the live
	// snapshot per request (may resolve nil: RP ID unset or construction
	// degraded). A direct field would freeze the boot-time instance across
	// hot config edits; the resolver is the same seam OIDCResolver and
	// Config already use.
	WebAuthnResolver func() *webauthn.WebAuthn
	OIDCResolver     func() *authoidc.Provider
	Ceremonies       *CeremonyStore
	Config           func() AuthConfig // returns current config (hot-reloadable)
	Configured       func() bool       // returns whether server has valid config
	HTTPClient       *http.Client      // shared client for outbound requests (HIBP, etc.)
	// contains filtered or unexported fields
}

Handler holds all dependencies for the auth handler family. Constructed by the server package and stored on the Server struct.

func (*Handler) HandleAuthMe

func (h *Handler) HandleAuthMe(w http.ResponseWriter, r *http.Request)

HandleAuthMe handles GET /api/auth/me — returns profile information for the current user.

func (*Handler) HandleChangePassword

func (h *Handler) HandleChangePassword(w http.ResponseWriter, r *http.Request)

HandleChangePassword handles PUT /api/auth/password — changes the current user's password after verifying the existing one, then invalidates all other sessions.

func (*Handler) HandleCreateUser

func (h *Handler) HandleCreateUser(w http.ResponseWriter, r *http.Request)

HandleCreateUser handles POST /api/auth/users — creates a new user account.

func (*Handler) HandleDeletePasskey

func (h *Handler) HandleDeletePasskey(w http.ResponseWriter, r *http.Request)

HandleDeletePasskey handles DELETE /api/auth/passkeys/{id} — removes a passkey. Refuses when deleting would leave the account with no authentication method.

func (*Handler) HandleDeleteUser

func (h *Handler) HandleDeleteUser(w http.ResponseWriter, r *http.Request)

HandleDeleteUser handles DELETE /api/auth/users/{id} — deletes a user account. Refuses to delete the caller's own account or the last admin.

func (*Handler) HandleGenerateAPIKey

func (h *Handler) HandleGenerateAPIKey(w http.ResponseWriter, r *http.Request)

HandleGenerateAPIKey handles POST /api/auth/apikeys — generates a new API key for the current user.

func (*Handler) HandleListAPIKeys

func (h *Handler) HandleListAPIKeys(w http.ResponseWriter, r *http.Request)

HandleListAPIKeys handles GET /api/auth/apikeys — lists API keys for the current user.

func (*Handler) HandleListPasskeys

func (h *Handler) HandleListPasskeys(w http.ResponseWriter, r *http.Request)

HandleListPasskeys handles GET /api/auth/passkeys — lists passkeys for the current user.

func (*Handler) HandleListUsers

func (h *Handler) HandleListUsers(w http.ResponseWriter, r *http.Request)

HandleListUsers handles GET /api/auth/users — lists all user accounts.

func (*Handler) HandleLogin

func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request)

HandleLogin handles POST /api/auth/login — authenticates with username and password.

func (*Handler) HandleLogout

func (h *Handler) HandleLogout(w http.ResponseWriter, r *http.Request)

HandleLogout handles POST /api/auth/logout — invalidates the current session cookie.

func (*Handler) HandleOIDCCallback

func (h *Handler) HandleOIDCCallback(w http.ResponseWriter, r *http.Request)

HandleOIDCCallback handles GET /api/auth/oidc/callback — completes the OIDC authorization code exchange, resolves or JIT-provisions the user, and creates a session. Redirects to /login.html when a link ceremony is required.

func (h *Handler) HandleOIDCLink(w http.ResponseWriter, r *http.Request)

HandleOIDCLink completes link-on-login: the user proves ownership of the existing local account by password, and the pending OIDC identity is linked to it. The link token is single-use and TTL-bounded.

func (*Handler) HandleOIDCRedirect

func (h *Handler) HandleOIDCRedirect(w http.ResponseWriter, r *http.Request)

HandleOIDCRedirect handles GET /api/auth/oidc — initiates the OIDC authorization code flow by generating state, nonce, and PKCE and redirecting to the provider.

func (h *Handler) HandleOIDCUnlink(w http.ResponseWriter, r *http.Request)

HandleOIDCUnlink removes the OIDC binding from the current user's account. It refuses if doing so would leave the account with no way to log in (no password and no passkey), mirroring the disable-password lockout guard.

func (*Handler) HandleRenamePasskey

func (h *Handler) HandleRenamePasskey(w http.ResponseWriter, r *http.Request)

HandleRenamePasskey handles PUT /api/auth/passkeys/{id} — renames a passkey.

func (*Handler) HandleRevokeAPIKey

func (h *Handler) HandleRevokeAPIKey(w http.ResponseWriter, r *http.Request)

HandleRevokeAPIKey handles DELETE /api/auth/apikeys/{id} — revokes an API key owned by the current user.

func (*Handler) HandleSetupCreate

func (h *Handler) HandleSetupCreate(w http.ResponseWriter, r *http.Request)

HandleSetupCreate handles POST /api/auth/setup — creates the initial admin account. Only succeeds when no users exist; subsequent calls return 409.

func (*Handler) HandleSetupStatus

func (h *Handler) HandleSetupStatus(w http.ResponseWriter, r *http.Request)

HandleSetupStatus handles GET /api/auth/setup — returns whether initial setup is required.

func (*Handler) HandleWebAuthnLoginBegin

func (h *Handler) HandleWebAuthnLoginBegin(w http.ResponseWriter, r *http.Request)

HandleWebAuthnLoginBegin handles POST /api/auth/webauthn/login/begin — issues a WebAuthn assertion challenge. Supports both standard and conditional (passkey autofill) mediation modes.

func (*Handler) HandleWebAuthnLoginFinish

func (h *Handler) HandleWebAuthnLoginFinish(w http.ResponseWriter, r *http.Request)

HandleWebAuthnLoginFinish handles POST /api/auth/webauthn/login/finish — verifies the assertion response, updates the credential sign count, and creates a session for the authenticated user.

func (*Handler) HandleWebAuthnRegisterBegin

func (h *Handler) HandleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Request)

HandleWebAuthnRegisterBegin handles POST /api/auth/webauthn/register/begin — initiates passkey registration. Requires password verification before issuing the creation challenge to prevent unauthorized credential provisioning.

func (*Handler) HandleWebAuthnRegisterFinish

func (h *Handler) HandleWebAuthnRegisterFinish(w http.ResponseWriter, r *http.Request)

HandleWebAuthnRegisterFinish handles POST /api/auth/webauthn/register/finish — completes passkey registration, stores the new credential, and emits an audit record.

func (*Handler) HandleWebAuthnSignalData

func (h *Handler) HandleWebAuthnSignalData(w http.ResponseWriter, r *http.Request)

HandleWebAuthnSignalData handles GET /api/auth/webauthn/signal-data — returns the WebAuthn signal data needed by the browser for credential management.

type OIDCStore

type OIDCStore interface {
	CreateOIDCState(ctx context.Context, state, nonce, codeVerifier, redirectURI string) error
	ConsumeOIDCState(ctx context.Context, state string) (nonce, codeVerifier, redirectURI string, err error)
	GetUserByOIDCSub(ctx context.Context, issuer, sub string) (*auth.User, error)
	GetUserByEmail(ctx context.Context, email string) (*auth.User, error)
	GetUserByUsername(ctx context.Context, username string) (*auth.User, error)
	CreateUser(ctx context.Context, user *auth.User) error
	UpdateUser(ctx context.Context, user *auth.User) error
}

OIDCStore is the narrow interface consumed by OIDC authentication handlers.

type PasskeyInfo added in v0.1.148

type PasskeyInfo struct {
	CreatedAt      time.Time `json:"created_at"`
	Name           string    `json:"name"`
	Transport      string    `json:"transport,omitempty"`
	ID             int64     `json:"id"`
	BackupEligible bool      `json:"backup_eligible"`
}

PasskeyInfo is one entry of the GET /api/auth/passkeys response.

type PendingLink struct {
	CreatedAt  time.Time
	OIDCSub    string
	OIDCIssuer string
	UserID     int64
}

PendingLink holds state for an OIDC login that matched an existing local account by username but not by (issuer, sub). The user must prove ownership of the local account with its password before the OIDC identity is linked (link-on-login). Stored under a random token, single-use, TTL-bounded.

type SecurityStore

type SecurityStore interface {
	UpdateUser(ctx context.Context, user *auth.User) error
	DeleteUserSessions(ctx context.Context, userID int64, exceptHash string) error
	PasskeyCountForUser(ctx context.Context, userID int64) (int, error)
	GetPasskeysByUserID(ctx context.Context, userID int64) ([]auth.PasskeyCredential, error)
	CreatePasskey(ctx context.Context, cred *auth.PasskeyCredential) error
	DeletePasskey(ctx context.Context, id, userID int64) error
	RenamePasskey(ctx context.Context, id, userID int64, name string) error
	CreateAPIKey(ctx context.Context, key *auth.Key) error
	DeleteAPIKey(ctx context.Context, id, userID int64) error
	ListAPIKeysByUserID(ctx context.Context, userID int64) ([]auth.Key, error)
}

SecurityStore is the narrow interface consumed by security management handlers.

type ShardedCeremonyMap

type ShardedCeremonyMap[V any] struct {
	// contains filtered or unexported fields
}

ShardedCeremonyMap is a sharded map for ephemeral ceremony state. Sharding reduces lock contention under concurrent auth requests.

func NewShardedCeremonyMap

func NewShardedCeremonyMap[V any]() *ShardedCeremonyMap[V]

NewShardedCeremonyMap creates a new sharded ceremony map.

func (*ShardedCeremonyMap[V]) Cleanup

func (sm *ShardedCeremonyMap[V]) Cleanup(isExpired func(V) bool)

Cleanup removes entries matching the isExpired predicate.

func (*ShardedCeremonyMap[V]) Load

func (sm *ShardedCeremonyMap[V]) Load(key string) (V, bool)

Load retrieves a value from the sharded ceremony map by key without removing it.

func (*ShardedCeremonyMap[V]) LoadAndDelete

func (sm *ShardedCeremonyMap[V]) LoadAndDelete(key string) (V, bool)

LoadAndDelete atomically retrieves and removes a value from the map.

func (*ShardedCeremonyMap[V]) Store

func (sm *ShardedCeremonyMap[V]) Store(key string, val V) bool

Store adds a value to the map. Returns false if the session limit is reached.

type UserInfo added in v0.1.148

type UserInfo struct {
	CreatedAt time.Time `json:"created_at"`
	Username  string    `json:"username"`
	Email     string    `json:"email"`
	Role      auth.Role `json:"role"`
	ID        int64     `json:"id"`
	Enabled   bool      `json:"enabled"`
}

UserInfo is one entry of the GET /api/auth/users response.

type WebAuthnLoginBeginResponse

type WebAuthnLoginBeginResponse struct {
	PublicKey    *protocol.CredentialAssertion `json:"publicKey"`
	SessionToken string                        `json:"session_token"`
}

WebAuthnLoginBeginResponse wraps WebAuthn assertion options with a session token. Used for login ceremonies.

type WebAuthnRegisterBeginResponse

type WebAuthnRegisterBeginResponse struct {
	PublicKey    *protocol.CredentialCreation `json:"publicKey"`
	SessionToken string                       `json:"session_token"`
}

WebAuthnRegisterBeginResponse wraps WebAuthn creation options with a session token. Used for passkey registration ceremonies.

type WebAuthnSession

type WebAuthnSession struct {
	Data      *webauthn.SessionData
	CreatedAt time.Time
}

WebAuthnSession holds ephemeral WebAuthn ceremony data.

Jump to

Keyboard shortcuts

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