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
This commit is contained in:
2026-02-06 11:54:14 -05:00
parent 6784c25b2e
commit b948d29b5e
8 changed files with 44 additions and 76 deletions
+1 -8
View File
@@ -1,17 +1,10 @@
package router package router
import (
"github.com/labstack/echo-jwt/v4"
)
func registerAnalyticsRoutes(cfg *Config) { func registerAnalyticsRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
SigningKey: []byte(cfg.Cfg.JWTSecret),
ContextKey: "user",
})
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
+2 -15
View File
@@ -3,8 +3,6 @@ package router
import ( import (
"bookhoard/internal/handlers" "bookhoard/internal/handlers"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/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)) e.POST("/api/auth/login", rateLimitMiddleware(cfg.AuthHandler.Login))
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
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"])
},
})
// Create protected route group // Create protected route group
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
@@ -43,7 +30,7 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) {
e.POST("/api/auth/logout", cfg.AuthHandler.Logout) e.POST("/api/auth/logout", cfg.AuthHandler.Logout)
// Auth update routes // Auth update routes
authGroup := e.Group("/api/auth", jwtMiddleware) authGroup := e.Group("/api/auth", createJWTMiddleware(cfg))
authGroup.PUT("/email", cfg.AuthHandler.UpdateEmail) authGroup.PUT("/email", cfg.AuthHandler.UpdateEmail)
authGroup.PUT("/username", cfg.AuthHandler.UpdateUsername) authGroup.PUT("/username", cfg.AuthHandler.UpdateUsername)
authGroup.PUT("/password", cfg.AuthHandler.UpdatePassword) authGroup.PUT("/password", cfg.AuthHandler.UpdatePassword)
+1 -8
View File
@@ -1,17 +1,10 @@
package router package router
import (
"github.com/labstack/echo-jwt/v4"
)
func registerConflictRoutes(cfg *Config) { func registerConflictRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
SigningKey: []byte(cfg.Cfg.JWTSecret),
ContextKey: "user",
})
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
+1 -18
View File
@@ -1,27 +1,10 @@
package router package router
import (
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
)
func registerDeviceRoutes(cfg *Config) { func registerDeviceRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware // JWT middleware
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
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"])
},
})
// Protected routes // Protected routes
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
+1 -6
View File
@@ -2,18 +2,13 @@ package router
import ( import (
"bookhoard/internal/handlers" "bookhoard/internal/handlers"
"github.com/labstack/echo-jwt/v4"
) )
func registerMediaRoutes(cfg *Config) { func registerMediaRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
SigningKey: []byte(cfg.Cfg.JWTSecret),
ContextKey: "user",
})
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
+1 -5
View File
@@ -1,7 +1,6 @@
package router package router
import ( import (
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@@ -9,10 +8,7 @@ func registerQueueRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
SigningKey: []byte(cfg.Cfg.JWTSecret),
ContextKey: "user",
})
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)
+36
View File
@@ -8,9 +8,14 @@ import (
ratelimit "bookhoard/internal/middleware" ratelimit "bookhoard/internal/middleware"
"bookhoard/internal/sync" "bookhoard/internal/sync"
"log" "log"
"net/http"
"time" "time"
"github.com/go-playground/validator/v10" "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" "github.com/labstack/echo/v4"
echomiddleware "github.com/labstack/echo/v4/middleware" echomiddleware "github.com/labstack/echo/v4/middleware"
) )
@@ -46,6 +51,37 @@ type Config struct {
LoginTracker *ratelimit.LoginAttemptTracker 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 // RegisterRoutes registers all application routes
func RegisterRoutes(cfg *Config) { func RegisterRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
+1 -16
View File
@@ -2,28 +2,13 @@ package router
import ( import (
"bookhoard/internal/handlers" "bookhoard/internal/handlers"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
) )
func registerSyncRoutes(cfg *Config) { func registerSyncRoutes(cfg *Config) {
e := cfg.Echo e := cfg.Echo
// JWT middleware for protected routes // JWT middleware for protected routes
jwtMiddleware := echojwt.WithConfig(echojwt.Config{ jwtMiddleware := createJWTMiddleware(cfg)
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"])
},
})
protected := e.Group("/api", jwtMiddleware) protected := e.Group("/api", jwtMiddleware)