middleware

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package middleware provides HTTP middleware for the HAMR framework.

Index

Constants

View Source
const FlashCookieName = "hamr_flash"

FlashCookieName is the cookie name used for flash messages.

Variables

View Source
var DefaultImmutableExtensions = []string{
	".webp", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico",
	".woff2", ".woff", ".ttf", ".eot",
}

DefaultImmutableExtensions are file extensions treated as immutable assets.

View Source
var DefaultStaticExtensions = []string{".css", ".js"}

DefaultStaticExtensions are file extensions treated as cacheable static assets.

Functions

func Audit

func Audit(logger AuditLogger) echo.MiddlewareFunc

Audit returns middleware that logs non-GET mutations via the given logger.

func AuditWithConfig

func AuditWithConfig(cfg AuditConfig) echo.MiddlewareFunc

AuditWithConfig returns audit middleware with the given config.

func CORS

func CORS() echo.MiddlewareFunc

CORS returns CORS middleware with framework defaults.

func CORSWithConfig

func CORSWithConfig(cfg CORSConfig) echo.MiddlewareFunc

CORSWithConfig returns CORS middleware with the given config.

When AllowOrigins is empty, cross-origin requests are DENIED (no Access-Control-Allow-Origin header) rather than falling back to Echo's permissive "*" default — apps that need CORS must pass explicit origins.

func CSRF

func CSRF() echo.MiddlewareFunc

CSRF returns CSRF protection middleware with framework defaults.

func CSRFWithConfig

func CSRFWithConfig(cfg CSRFConfig) echo.MiddlewareFunc

CSRFWithConfig returns CSRF protection middleware with the given config.

func CacheControl

func CacheControl(disableCaching bool) echo.MiddlewareFunc

CacheControl sets Cache-Control headers based on asset type. When disableCaching is true every response gets no-cache directives.

func CacheControlWithConfig

func CacheControlWithConfig(cfg CacheConfig) echo.MiddlewareFunc

CacheControlWithConfig sets Cache-Control headers using the given config.

func ErrorPages

func ErrorPages(defaultPage ErrorPage, overrides ...PageOverride) echo.MiddlewareFunc

ErrorPages returns middleware that catches errors and renders error pages. The default ErrorPage handles all codes unless overridden by Page() entries.

func Flash

func Flash() echo.MiddlewareFunc

Flash reads a flash cookie from the request, stores it in the context, and clears the cookie so it is only shown once.

func FlashWithConfig

func FlashWithConfig(cfg FlashConfig) echo.MiddlewareFunc

FlashWithConfig returns flash middleware with the given config.

func GetDirection

func GetDirection(c echo.Context) string

GetDirection is a convenience helper that returns the text direction from the context's translator.

func GetLocale

func GetLocale(c echo.Context) string

GetLocale is a convenience helper that returns the locale from the context.

func GetSubject

func GetSubject(c echo.Context) any

GetSubject returns the loaded subject from the request context. Only populated when a SubjectLoader is configured (session-based auth). Returns nil if no subject is loaded.

func GetSubjectID

func GetSubjectID(c echo.Context) string

GetSubjectID returns the authenticated subject's ID from the request context. Works with both session-based auth (Auth middleware) and trusted header auth (TrustedSubject middleware). Returns empty string if no subject is set.

func LocaleFromPath

func LocaleFromPath(cfg LocaleConfig) echo.MiddlewareFunc

LocaleFromPath returns pre-router middleware that extracts the locale from the first URL path segment. Used for public/SEO pages where the locale appears in the URL (e.g. /en/about, /fr/contact).

IMPORTANT: Register with e.Pre(), not e.Use(), because the locale prefix must be stripped before the Echo router matches a route:

e.Pre(middleware.LocaleFromPath(cfg))

If the first segment is a supported locale, it is stripped from the path and the translator is injected into the context. If no valid locale prefix is found, the request is redirected to /{defaultLocale}/... with a 301 (GET) or 307 (POST).

func LocaleFromPreference

func LocaleFromPreference(cfg LocaleConfig) echo.MiddlewareFunc

LocaleFromPreference returns middleware that resolves the locale from user preferences: UserLocaleFunc > cookie > Accept-Language header > default. Used for authenticated/dashboard pages.

func RateLimit

func RateLimit(store RateLimitStore) echo.MiddlewareFunc

RateLimit returns rate limiting middleware using the given store with default settings (60 req/min + 10 burst, fail-open).

func RateLimitWithConfig

func RateLimitWithConfig(cfg RateLimitConfig) echo.MiddlewareFunc

RateLimitWithConfig returns rate limiting middleware with the given config.

func RequireActive

func RequireActive(checker ActiveChecker) echo.MiddlewareFunc

RequireActive returns middleware that checks whether the authenticated subject's account is active. Returns 401 if no subject is present or 403 if the account is not active.

func RequireRoles

func RequireRoles(checker RoleChecker, roles ...string) echo.MiddlewareFunc

RequireRoles returns middleware that checks whether the authenticated subject has one of the required roles. Returns 401 if no subject is present or 403 if the role check fails.

func Secure

func Secure() echo.MiddlewareFunc

Secure returns security headers middleware with framework defaults.

func SecureWithConfig

func SecureWithConfig(cfg SecureConfig) echo.MiddlewareFunc

SecureWithConfig returns security headers middleware with the given config.

func SetFlash

func SetFlash(c echo.Context, message string, flashType FlashType)

SetFlash stores a flash message in a cookie for the next request. Uses the cookie policy from Flash middleware if present, otherwise falls back to secure defaults.

func TrustedSubject

func TrustedSubject() echo.MiddlewareFunc

TrustedSubject reads X-Subject-ID and sets it as the subject identity with NO verification — equivalent to TrustedSubjectWithConfig(TrustedSubjectConfig{}). Use only behind a gateway on a trusted internal network. Prefer TrustedSubjectWithConfig with a shared secret and/or trusted CIDRs whenever the mount could be reached by untrusted clients; otherwise a single spoofed header is a full authentication bypass.

trusted := middleware.TrustedSubject()
api.GET("/billing", billingHandler.Get, trusted)

func TrustedSubjectWithConfig

func TrustedSubjectWithConfig(cfg TrustedSubjectConfig) echo.MiddlewareFunc

TrustedSubjectWithConfig is TrustedSubject gated by a shared secret and/or trusted source CIDRs. When a gate is configured and the request fails it, the X-Subject-ID header is ignored (the subject is left unset) so downstream authorization fails closed.

Types

type ActiveChecker

type ActiveChecker func(subject any) bool

ActiveChecker returns true if the subject's account is active.

type AuditConfig

type AuditConfig struct {
	Logger      AuditLogger
	ActorIDFunc func(c echo.Context) string // default: GetSubjectID
}

AuditConfig configures audit middleware.

type AuditEntry

type AuditEntry struct {
	ActorID    string         `json:"actor_id"`
	Action     string         `json:"action"`      // HTTP method
	EntityType string         `json:"entity_type"` // route path pattern
	Data       map[string]any `json:"data"`
	Timestamp  time.Time      `json:"timestamp"`
}

AuditEntry records a single auditable action.

type AuditLogger

type AuditLogger interface {
	Log(ctx context.Context, entry *AuditEntry) error
}

AuditLogger persists audit entries. Projects implement this interface to store entries in their database, log aggregator, etc.

type BrowserAuth

type BrowserAuth struct {
	// contains filtered or unexported fields
}

BrowserAuth provides session-based authentication middleware split into a loader (DB access) and pure policy checks (ctx-only).

func NewBrowserAuth

func NewBrowserAuth(sm *auth.SessionManager, opts ...BrowserAuthOption) *BrowserAuth

NewBrowserAuth creates a BrowserAuth with the given session manager and options.

func (*BrowserAuth) Load

func (b *BrowserAuth) Load() echo.MiddlewareFunc

Load returns middleware that validates the session cookie and populates the Echo context. This is the only middleware that touches the database.

Behavior:

  • No cookie → next (no subject in ctx)
  • Cookie present, invalid/expired → clear cookie, next (no subject in ctx)
  • DB error on ValidateSession or SubjectLoader → return error (500)
  • Valid session → set SubjectIDKey, SessionKey, SubjectKey in ctx, enrich logger

func (*BrowserAuth) RequireAuth

func (b *BrowserAuth) RequireAuth() echo.MiddlewareFunc

RequireAuth returns middleware that requires an authenticated subject in the context. Must be mounted after Load(). No database calls.

No subject in ctx → redirect to LoginRedirect (303 or HX-Redirect).

func (*BrowserAuth) RequireNotAuth

func (b *BrowserAuth) RequireNotAuth() echo.MiddlewareFunc

RequireNotAuth returns middleware that redirects already-authenticated users away (e.g. from login/register pages). Must be mounted after Load(). No database calls.

Subject in ctx → redirect to HomeRedirect (303 or HX-Redirect).

type BrowserAuthOption

type BrowserAuthOption func(*BrowserAuth)

BrowserAuthOption configures a BrowserAuth instance.

func WithHXRedirect

func WithHXRedirect() BrowserAuthOption

WithHXRedirect makes policy middleware respond with an HX-Redirect header instead of a 303 Location redirect, for HTMX-driven navigations.

func WithHomeRedirect

func WithHomeRedirect(url string) BrowserAuthOption

WithHomeRedirect sets the URL authenticated users are redirected away to (e.g. from login/register pages). Default: "/dashboard".

func WithLoginRedirect

func WithLoginRedirect(url string) BrowserAuthOption

WithLoginRedirect sets the URL unauthenticated users are redirected to. Default: "/login".

func WithSubjectLoader

func WithSubjectLoader(loader SubjectLoader) BrowserAuthOption

WithSubjectLoader sets a function that loads the full subject by ID. If not set, only SubjectIDKey and SessionKey are populated in context.

type CORSConfig

type CORSConfig struct {
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	AllowCredentials bool
}

CORSConfig allows overriding CORS defaults.

type CSRFConfig

type CSRFConfig struct {
	CookieName  string // default: "csrf"
	TokenLookup string // default: "form:csrf_token,header:X-CSRF-Token"
	Secure      bool   // default: true
	// SameSite controls the cookie's SameSite attribute. Zero value defaults
	// to Lax (a sensible CSRF defense-in-depth) rather than the browser
	// default. Set explicitly to override (e.g. http.SameSiteStrictMode).
	SameSite http.SameSite
}

CSRFConfig allows overriding CSRF defaults.

type CacheConfig

type CacheConfig struct {
	// ImmutableExtensions are file extensions cached as immutable.
	// Default: DefaultImmutableExtensions.
	ImmutableExtensions []string

	// ImmutableMaxAge is the max-age in seconds for immutable assets.
	// Default: 31536000 (1 year).
	ImmutableMaxAge int

	// StaticExtensions are file extensions cached with a shorter TTL.
	// Default: DefaultStaticExtensions.
	StaticExtensions []string

	// StaticMaxAge is the max-age in seconds for static assets.
	// Default: 86400 (1 day).
	StaticMaxAge int

	// DisableCaching sets no-cache directives on every response.
	DisableCaching bool

	// AllowDynamicCaching disables the default "no-store, private" header on
	// dynamic (non-static) responses. Leave false (default) so authenticated
	// pages aren't retained by the browser back-button or shared proxy caches;
	// set true for apps that serve cacheable public dynamic content. A handler
	// can always override per-route by setting its own Cache-Control.
	AllowDynamicCaching bool
}

CacheConfig configures the CacheControlWithConfig middleware.

type DB

type DB interface {
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

DB is the minimal database interface satisfied by *sql.DB and *sqlx.DB.

type ErrorPage

type ErrorPage func(code int, message string) templ.Component

ErrorPage is a function that returns a templ component for an error.

type FlashConfig

type FlashConfig struct {
	Path   string // default: "/"
	Secure bool   // default: true
}

FlashConfig configures flash cookie behaviour.

type FlashMessage

type FlashMessage struct {
	Message string    `json:"message"`
	Type    FlashType `json:"type"`
}

FlashMessage is a one-time message shown to the user after a redirect.

func GetFlash

func GetFlash(c echo.Context) *FlashMessage

GetFlash returns the flash message from the context, or nil if none is set.

SECURITY: the flash cookie is base64-encoded only, NOT authenticated (no MAC), so Message and Type are attacker-influenceable — treat them as UNTRUSTED when rendering. They are safe interpolated through templ (which auto-escapes); do not render them via templ.Raw/unescaped HTML, and validate Type before using it to select a code path.

type FlashType

type FlashType string

FlashType categorises a flash message.

const (
	FlashInfo    FlashType = "info"
	FlashSuccess FlashType = "success"
	FlashWarning FlashType = "warning"
	FlashError   FlashType = "error"
)

type LocaleConfig

type LocaleConfig struct {
	Bundle         *i18n.Bundle
	CookieName     string // default: "hamr_locale"
	CookieMaxAge   int    // default: 31536000 (1 year)
	CookieSecure   bool
	DefaultLocale  string                      // from bundle if empty
	UserLocaleFunc func(c echo.Context) string // optional: load from user preference
}

LocaleConfig configures locale middleware.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore is an in-memory fixed-window rate limit store.

func NewMemoryStore

func NewMemoryStore(opts ...MemoryStoreOption) *MemoryStore

NewMemoryStore returns a new in-memory rate limit store.

func (*MemoryStore) Allow

func (s *MemoryStore) Allow(_ context.Context, key string, rate int, dur time.Duration) (bool, int, time.Time, error)

Allow implements RateLimitStore.

func (*MemoryStore) CleanupExpired

func (s *MemoryStore) CleanupExpired(window time.Duration)

CleanupExpired removes expired windows.

type MemoryStoreOption

type MemoryStoreOption func(*MemoryStore)

MemoryStoreOption configures a MemoryStore.

func WithAutoCleanup

func WithAutoCleanup(ctx context.Context, interval time.Duration) MemoryStoreOption

WithAutoCleanup starts a background goroutine that calls CleanupExpired at the given interval. The goroutine stops when ctx is cancelled.

func WithMaxSize

func WithMaxSize(n int) MemoryStoreOption

WithMaxSize sets the maximum number of entries in the memory store. When full, the oldest entry is evicted.

func WithWindow

func WithWindow(d time.Duration) MemoryStoreOption

WithWindow sets the rate limit window duration used by autoCleanup when expiring entries. If unset, autoCleanup falls back to cleanupInterval.

type PGStore

type PGStore struct {
	// contains filtered or unexported fields
}

PGStore is a PostgreSQL-backed fixed-window rate limit store using an UNLOGGED table for performance.

func NewPGStore

func NewPGStore(db DB) *PGStore

NewPGStore returns a new PostgreSQL rate limit store.

func (*PGStore) Allow

func (s *PGStore) Allow(ctx context.Context, key string, rate int, dur time.Duration) (bool, int, time.Time, error)

Allow implements RateLimitStore using an atomic upsert.

func (*PGStore) Cleanup

func (s *PGStore) Cleanup(ctx context.Context, dur time.Duration) (int64, error)

Cleanup removes expired rate limit entries.

func (*PGStore) CreateTable

func (s *PGStore) CreateTable(ctx context.Context) error

CreateTable creates the _rate_limits table if it does not exist.

type PageOverride

type PageOverride struct {
	Code int
	Page ErrorPage
}

PageOverride maps a specific status code to an ErrorPage.

func Page

func Page(code int, page ErrorPage) PageOverride

Page creates a per-status-code override.

type RateLimitConfig

type RateLimitConfig struct {
	Store      RateLimitStore
	Rate       int                                  // max requests per window (default: 60)
	Burst      int                                  // additional burst allowance over Rate (default: 10)
	Window     time.Duration                        // sliding window duration (default: 1 minute)
	KeyFunc    func(c echo.Context) (string, error) // default: c.RealIP()
	FailClosed bool                                 // deny requests when store errors (default: false)
}

RateLimitConfig configures rate limiting middleware.

type RateLimitStore

type RateLimitStore interface {
	Allow(ctx context.Context, key string, rate int, window time.Duration) (allowed bool, remaining int, resetAt time.Time, err error)
}

RateLimitStore checks whether a key is within its rate limit.

type RoleChecker

type RoleChecker func(subject any, roles []string) bool

RoleChecker returns true if the subject has at least one of the given roles.

type SecureConfig

type SecureConfig struct {
	ContentSecurityPolicy string // default: "default-src 'self'"
	XFrameOptions         string // default: "DENY"
	ReferrerPolicy        string // default: "strict-origin-when-cross-origin"
	XSSProtection         string // default: "0"
	ContentTypeNosniff    string // default: "nosniff"
}

SecureConfig allows overriding security response headers. Zero-value fields use sensible defaults.

type SubjectLoader

type SubjectLoader func(ctx context.Context, subjectID string) (any, error)

SubjectLoader loads a subject by ID. Projects provide their own implementation (e.g. loading a User from the database).

type TrustedSubjectConfig

type TrustedSubjectConfig struct {
	// SharedSecret, when non-empty, requires the request to present this exact
	// value in SecretHeader (constant-time compared) before X-Subject-ID is
	// honoured.
	SharedSecret string

	// SecretHeader carries SharedSecret. Default: "X-Internal-Secret".
	SecretHeader string

	// TrustedProxies, when non-empty, requires the direct peer IP (c.RealIP())
	// to fall within one of these CIDRs. Unparseable entries are dropped, so a
	// list with no valid CIDR can never satisfy the gate (fails closed).
	//
	// NOTE: this is only as trustworthy as c.RealIP(). Configure the server's
	// trusted-proxy IP extractor (server.WithTrustedProxies) so X-Forwarded-For
	// can't be spoofed; the default extractor uses the direct peer, which is safe.
	TrustedProxies []string
}

TrustedSubjectConfig gates which requests may establish the subject identity from the X-Subject-ID header. With neither field set the header is trusted unconditionally (the legacy behaviour) — only safe on a fully internal network where nothing untrusted can reach the mount.

Jump to

Keyboard shortcuts

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