Files
marktvogt.de/backend/internal/middleware/auth.go
T
vikingowl a1d93f7a8e feat: implement MVP backend API
Go backend with Gin, pgx, Valkey (go-valkey), and PostGIS.

Domains:
- Market search with PostGIS geo-queries (ST_DWithin, ST_Distance),
  German full-text search (tsvector + ILIKE fallback for compound words),
  date range filtering, pagination, and slug-based detail endpoint
- Auth with email+password (bcrypt), JWT access tokens (15min),
  session tokens (30d, dual Valkey+Postgres storage), OAuth
  (Google/GitHub/Facebook), magic links, and TOTP 2FA
- User profile with CRUD, soft-delete (30d grace), and restore

Infrastructure:
- 6 database migrations (users, sessions, oauth_accounts, magic_links,
  markets with PostGIS+FTS, totp_secrets)
- Middleware: recovery, request ID, structured logging (slog), CORS,
  per-IP rate limiting, JWT auth
- Seed data: 10 medieval markets across DACH region
- Docker Compose (PostGIS 17 + Valkey 8), multi-stage Dockerfile,
  Woodpecker CI pipeline, Kubernetes manifests
- Justfile, golangci-lint config, env example
2026-02-18 05:52:20 +01:00

62 lines
1.5 KiB
Go

package middleware
import (
"strings"
"github.com/gin-gonic/gin"
"marktvogt.de/backend/internal/domain/auth"
"marktvogt.de/backend/internal/pkg/apierror"
)
func RequireAuth(tokenSvc *auth.TokenService) gin.HandlerFunc {
return func(c *gin.Context) {
claims, ok := extractAndValidate(c, tokenSvc)
if !ok {
return
}
c.Set("user_id", claims.UserID)
c.Set("user_email", claims.Email)
c.Set("user_role", claims.Role)
c.Next()
}
}
func OptionalAuth(tokenSvc *auth.TokenService) gin.HandlerFunc {
return func(c *gin.Context) {
header := c.GetHeader("Authorization")
if header == "" || !strings.HasPrefix(header, "Bearer ") {
c.Next()
return
}
claims, _ := extractAndValidate(c, tokenSvc)
if claims != nil {
c.Set("user_id", claims.UserID)
c.Set("user_email", claims.Email)
c.Set("user_role", claims.Role)
}
c.Next()
}
}
func extractAndValidate(c *gin.Context, tokenSvc *auth.TokenService) (*auth.TokenClaims, bool) {
header := c.GetHeader("Authorization")
if header == "" || !strings.HasPrefix(header, "Bearer ") {
apiErr := apierror.Unauthorized("missing or invalid authorization header")
c.AbortWithStatusJSON(apiErr.Status, apierror.NewResponse(apiErr))
return nil, false
}
tokenString := strings.TrimPrefix(header, "Bearer ")
claims, err := tokenSvc.ValidateAccessToken(tokenString)
if err != nil {
apiErr := apierror.Unauthorized("invalid or expired token")
c.AbortWithStatusJSON(apiErr.Status, apierror.NewResponse(apiErr))
return nil, false
}
return claims, true
}