- CORS: rewrite middleware with Vary: Origin, regex origin patterns, startup validation, and prod boot-fail on empty allowlist; shared CORSConfig exported for CSRF reuse - CSRF: new Origin/Referer check middleware sharing CORS allowlist; Bearer-token clients exempt; mounts globally after CORS - Argon2id: new password package with PHC format, bcrypt dispatch, and NeedsRehash; lazy upgrade on login in auth service - Rate limiting: add RateLimitByKey with custom key function; apply per-route limits to /auth/login, /refresh, /2fa/verify, /auth/magic-link, and /auth/password - apierror: add CSRFMismatch and RefreshReuse error constructors - Migrations: 000027 (session model schema columns for D2/D3), 000028 (TOTP secret_v2 column + totp_backup_codes table) - cmd/totp-encrypt: one-shot job to encrypt existing TOTP secrets
51 lines
1.2 KiB
Go
51 lines
1.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"marktvogt.de/backend/internal/pkg/apierror"
|
|
)
|
|
|
|
// CSRF returns middleware that validates the Origin (or Referer) header for
|
|
// state-changing cookie-authed requests. Bearer-token requests (mobile/API)
|
|
// are exempt — they're not CSRF-vulnerable.
|
|
func CSRF(cfg CORSConfig) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
method := c.Request.Method
|
|
|
|
// Safe methods and preflight are not CSRF-vulnerable.
|
|
if method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Bearer-token clients (mobile, third-party API) are exempt.
|
|
if strings.HasPrefix(c.GetHeader("Authorization"), "Bearer ") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
origin := c.GetHeader("Origin")
|
|
if origin == "" {
|
|
// Fall back to Referer — extract scheme+host as origin.
|
|
if ref := c.GetHeader("Referer"); ref != "" {
|
|
if u, err := url.Parse(ref); err == nil && u.Host != "" {
|
|
origin = u.Scheme + "://" + u.Host
|
|
}
|
|
}
|
|
}
|
|
|
|
if !cfg.IsAllowedOrigin(origin) {
|
|
apiErr := apierror.CSRFMismatch()
|
|
c.AbortWithStatusJSON(apiErr.Status, apierror.NewResponse(apiErr))
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|