From b948d29b5eb052142adf0013b00d377124020df3 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 6 Feb 2026 11:54:14 -0500 Subject: [PATCH] fix: add proper JWT user context to router middleware Add createJWTMiddleware helper that sets database.Users object in context, matching the original main.go JWT middleware behavior. This fixes 'authentication context error' panics in handlers that call MustGetAuthenticatedUser. Changes: - Add createJWTMiddleware() in router.go - Update all route files to use the helper - Set user claims AND database.Users object in context --- internal/router/analytics.go | 9 +-------- internal/router/auth.go | 17 ++--------------- internal/router/conflicts.go | 9 +-------- internal/router/device.go | 19 +------------------ internal/router/media.go | 7 +------ internal/router/queue.go | 6 +----- internal/router/router.go | 36 ++++++++++++++++++++++++++++++++++++ internal/router/sync.go | 17 +---------------- 8 files changed, 44 insertions(+), 76 deletions(-) diff --git a/internal/router/analytics.go b/internal/router/analytics.go index f464ab2..1c34136 100644 --- a/internal/router/analytics.go +++ b/internal/router/analytics.go @@ -1,17 +1,10 @@ package router -import ( - "github.com/labstack/echo-jwt/v4" -) - func registerAnalyticsRoutes(cfg *Config) { e := cfg.Echo // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - }) + jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) diff --git a/internal/router/auth.go b/internal/router/auth.go index fdc7649..3fe1a9d 100644 --- a/internal/router/auth.go +++ b/internal/router/auth.go @@ -3,8 +3,6 @@ package router import ( "bookhoard/internal/handlers" - "github.com/golang-jwt/jwt/v5" - "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" ) @@ -16,18 +14,7 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) { e.POST("/api/auth/login", rateLimitMiddleware(cfg.AuthHandler.Login)) // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - SuccessHandler: func(c echo.Context) { - token := c.Get("user").(*jwt.Token) - claims := token.Claims.(jwt.MapClaims) - c.Set("user_id", claims["user_id"]) - c.Set("user_role", claims["user_role"]) - c.Set("user_email", claims["user_email"]) - c.Set("user_username", claims["user_username"]) - }, - }) + jwtMiddleware := createJWTMiddleware(cfg) // Create protected route group protected := e.Group("/api", jwtMiddleware) @@ -43,7 +30,7 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) { e.POST("/api/auth/logout", cfg.AuthHandler.Logout) // Auth update routes - authGroup := e.Group("/api/auth", jwtMiddleware) + authGroup := e.Group("/api/auth", createJWTMiddleware(cfg)) authGroup.PUT("/email", cfg.AuthHandler.UpdateEmail) authGroup.PUT("/username", cfg.AuthHandler.UpdateUsername) authGroup.PUT("/password", cfg.AuthHandler.UpdatePassword) diff --git a/internal/router/conflicts.go b/internal/router/conflicts.go index d873696..27976e4 100644 --- a/internal/router/conflicts.go +++ b/internal/router/conflicts.go @@ -1,17 +1,10 @@ package router -import ( - "github.com/labstack/echo-jwt/v4" -) - func registerConflictRoutes(cfg *Config) { e := cfg.Echo // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - }) + jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) diff --git a/internal/router/device.go b/internal/router/device.go index 46e5ad8..c8ed8c1 100644 --- a/internal/router/device.go +++ b/internal/router/device.go @@ -1,27 +1,10 @@ package router -import ( - "github.com/golang-jwt/jwt/v5" - "github.com/labstack/echo-jwt/v4" - "github.com/labstack/echo/v4" -) - func registerDeviceRoutes(cfg *Config) { e := cfg.Echo // JWT middleware - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - SuccessHandler: func(c echo.Context) { - token := c.Get("user").(*jwt.Token) - claims := token.Claims.(jwt.MapClaims) - c.Set("user_id", claims["user_id"]) - c.Set("user_role", claims["user_role"]) - c.Set("user_email", claims["user_email"]) - c.Set("user_username", claims["user_username"]) - }, - }) + jwtMiddleware := createJWTMiddleware(cfg) // Protected routes protected := e.Group("/api", jwtMiddleware) diff --git a/internal/router/media.go b/internal/router/media.go index 56538f9..0725010 100644 --- a/internal/router/media.go +++ b/internal/router/media.go @@ -2,18 +2,13 @@ package router import ( "bookhoard/internal/handlers" - - "github.com/labstack/echo-jwt/v4" ) func registerMediaRoutes(cfg *Config) { e := cfg.Echo // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - }) + jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) diff --git a/internal/router/queue.go b/internal/router/queue.go index bad9b8d..18e9080 100644 --- a/internal/router/queue.go +++ b/internal/router/queue.go @@ -1,7 +1,6 @@ package router import ( - "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" ) @@ -9,10 +8,7 @@ func registerQueueRoutes(cfg *Config) { e := cfg.Echo // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - }) + jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) diff --git a/internal/router/router.go b/internal/router/router.go index 4cb10ee..2fe6296 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -8,9 +8,14 @@ import ( ratelimit "bookhoard/internal/middleware" "bookhoard/internal/sync" "log" + "net/http" "time" "github.com/go-playground/validator/v10" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" echomiddleware "github.com/labstack/echo/v4/middleware" ) @@ -46,6 +51,37 @@ type Config struct { LoginTracker *ratelimit.LoginAttemptTracker } +// createJWTMiddleware creates a JWT middleware with proper user context setup +func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { + return echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte(cfg.Cfg.JWTSecret), + ContextKey: "user", + SuccessHandler: func(c echo.Context) { + token := c.Get("user").(*jwt.Token) + claims := token.Claims.(jwt.MapClaims) + c.Set("user_id", claims["user_id"]) + c.Set("user_role", claims["user_role"]) + c.Set("user_email", claims["user_email"]) + c.Set("user_username", claims["user_username"]) + + // Parse UUID from string claims + userIDStr, _ := claims["user_id"].(string) + userUUID, err := uuid.Parse(userIDStr) + if err != nil { + c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID in token"}) + return + } + + c.Set("user", database.Users{ + ID: pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}, + Email: claims["user_email"].(string), + Username: claims["user_username"].(string), + Role: claims["user_role"].(string), + }) + }, + }) +} + // RegisterRoutes registers all application routes func RegisterRoutes(cfg *Config) { e := cfg.Echo diff --git a/internal/router/sync.go b/internal/router/sync.go index b8dcc38..1a26a37 100644 --- a/internal/router/sync.go +++ b/internal/router/sync.go @@ -2,28 +2,13 @@ package router import ( "bookhoard/internal/handlers" - - "github.com/golang-jwt/jwt/v5" - "github.com/labstack/echo-jwt/v4" - "github.com/labstack/echo/v4" ) func registerSyncRoutes(cfg *Config) { e := cfg.Echo // JWT middleware for protected routes - jwtMiddleware := echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(cfg.Cfg.JWTSecret), - ContextKey: "user", - SuccessHandler: func(c echo.Context) { - token := c.Get("user").(*jwt.Token) - claims := token.Claims.(jwt.MapClaims) - c.Set("user_id", claims["user_id"]) - c.Set("user_role", claims["user_role"]) - c.Set("user_email", claims["user_email"]) - c.Set("user_username", claims["user_username"]) - }, - }) + jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware)