- Replace gorilla/csrf with net/http CrossOriginProtection - Require valkey-password and add TLS options for session store - End session at /logout and revoke refresh tokens - Re-derive identity and roles from provider every five minutes - Process each Stripe webhook event in its own Temporal workflow - Give each outbox entry its own workflow with Temporal retries - Guard against stale Stripe events with provider timestamps - Derive transport security from base-url scheme
70 lines
2.7 KiB
Go
70 lines
2.7 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// CSRFConfig configures cross-origin request protection.
|
|
//
|
|
// This wraps net/http's CrossOriginProtection, which rejects non-safe
|
|
// cross-origin browser requests by reading Sec-Fetch-Site (sent by every
|
|
// browser since 2023) and falling back to comparing the Origin header's
|
|
// hostname with Host. There is no token: nothing is issued, stored in a
|
|
// cookie, embedded in a form, or echoed in a header.
|
|
//
|
|
// It replaced gorilla/csrf in 2026-09 because that library carries
|
|
// GO-2025-3884 (CVE-2025-47909) with no fixed release: its trusted-origin
|
|
// comparison ignored the scheme, so a policy written for https was satisfied
|
|
// by plain http. AddTrustedOrigin here requires a full origin, scheme
|
|
// included, which is the defect's direct answer.
|
|
//
|
|
// Two consequences worth knowing. GET, HEAD and OPTIONS are always allowed, so
|
|
// no handler may change state on those methods. And a request carrying neither
|
|
// Sec-Fetch-Site nor Origin is treated as same-origin or non-browser and
|
|
// allowed, which is what lets server-to-server callers through.
|
|
type CSRFConfig struct {
|
|
// TrustedOrigins are additional origins allowed to make non-safe
|
|
// requests, each a full origin such as "https://console.example.coop".
|
|
// An entry without a scheme is rejected at construction.
|
|
TrustedOrigins []string
|
|
|
|
// BypassPatterns are net/http.ServeMux patterns exempted from the check
|
|
// entirely, for endpoints that authenticate their caller some other way
|
|
// (a provider webhook verifying its own signature). Each one is a
|
|
// deliberate hole; see the server's exempt-path list for the contract.
|
|
BypassPatterns []string
|
|
|
|
// DenyHandler serves a rejected request. When nil the standard library's
|
|
// default 403 is used.
|
|
DenyHandler http.Handler
|
|
}
|
|
|
|
// CSRF returns middleware enforcing cross-origin protection.
|
|
//
|
|
// An invalid trusted origin is a configuration error the caller must handle;
|
|
// it is never silently dropped, because a trusted origin that fails to
|
|
// register would leave a legitimate deployment rejecting its own form posts.
|
|
func CSRF(config CSRFConfig) (Middleware, error) {
|
|
protection := http.NewCrossOriginProtection()
|
|
|
|
for _, origin := range config.TrustedOrigins {
|
|
if err := protection.AddTrustedOrigin(origin); err != nil {
|
|
return nil, fmt.Errorf("trusted origin %q: %w", origin, err)
|
|
}
|
|
}
|
|
for _, pattern := range config.BypassPatterns {
|
|
protection.AddInsecureBypassPattern(pattern)
|
|
}
|
|
if config.DenyHandler != nil {
|
|
protection.SetDenyHandler(config.DenyHandler)
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return protection.Handler(next)
|
|
}, nil
|
|
}
|