diff --git a/server/middleware/auth.go b/server/middleware/auth.go index a29b354..2512a2c 100644 --- a/server/middleware/auth.go +++ b/server/middleware/auth.go @@ -142,6 +142,21 @@ func parseAndValidateJWT(tokenString string, jwtSecret string) (*Claims, bool) { return claims, true } +// UserIDFromCookie extracts the user ID from the sb_token cookie without +// requiring authentication. Returns "" if no valid token is found. +// Used by unauthenticated routes that want optional user context. +func UserIDFromCookie(c *gin.Context, jwtSecret string) string { + cookie, err := c.Cookie("sb_token") + if err != nil || cookie == "" { + return "" + } + claims, ok := parseAndValidateJWT(cookie, jwtSecret) + if !ok { + return "" + } + return claims.UserID +} + // ─── Auth middleware ───────────────────────────────────────── // Auth returns a Gin middleware that validates JWT bearer tokens and diff --git a/server/pages/pages_surfaces.go b/server/pages/pages_surfaces.go index a3e7088..2a027dd 100644 --- a/server/pages/pages_surfaces.go +++ b/server/pages/pages_surfaces.go @@ -6,6 +6,8 @@ import ( "net/http" "github.com/gin-gonic/gin" + + "switchboard-core/middleware" ) // SeedSurfaces writes core surface manifests to the registry table. @@ -92,9 +94,13 @@ func (e *Engine) EnabledSurfaceIDs() []string { // DefaultSurfaceRedirect returns a handler for GET / that redirects to the // configured default surface, falling back to the first enabled extension // surface, then /welcome. +// +// This route has no auth middleware, so we opportunistically read the JWT +// from the cookie to check user preferences. If the cookie is missing or +// invalid, user preference is skipped (falls through to global default). func (e *Engine) DefaultSurfaceRedirect() gin.HandlerFunc { return func(c *gin.Context) { - userID := c.GetString("user_id") + userID := middleware.UserIDFromCookie(c, e.cfg.JWTSecret) target := e.resolveDefaultSurface(c.Request.Context(), userID) c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+target) }