a1d93f7a8e
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
62 lines
1.5 KiB
Go
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
|
|
}
|