From 3d75ca049b16e426a01329322aac9496a59a7017 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 1 May 2026 11:13:12 +0200 Subject: [PATCH] fix(auth): don't panic on /token/test with API token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JWT skipper bypassed validation entirely for /token/test when the bearer was an API token, leaving "user" unset in the context. CheckToken then type-asserted it to *jwt.Token and panicked. Validate the API token in the skipper but skip the route permission check (since /token/test is not exposed in the API token route registry, no token can hold explicit permission for it). Drop the now-redundant JWT assertion in CheckToken — auth has already passed by the time the handler runs. --- pkg/routes/api/v1/token_check.go | 11 ++--------- pkg/routes/api_tokens.go | 11 ++++------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/pkg/routes/api/v1/token_check.go b/pkg/routes/api/v1/token_check.go index cd4d3492d..42286b19e 100644 --- a/pkg/routes/api/v1/token_check.go +++ b/pkg/routes/api/v1/token_check.go @@ -19,20 +19,13 @@ package v1 import ( "net/http" - "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" - "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v5" ) -// CheckToken checks prints a message if the token is valid or not. Currently only used for testing purposes. +// CheckToken returns 418 if the bearer token is valid. Used for testing. func CheckToken(c *echo.Context) error { - - user := c.Get("user").(*jwt.Token) - - log.Debugf("token valid: %t", user.Valid) - - return c.JSON(418, models.Message{Message: "🍵"}) + return c.JSON(http.StatusTeapot, models.Message{Message: "🍵"}) } // TestToken returns a simple test message. Used for testing purposes. diff --git a/pkg/routes/api_tokens.go b/pkg/routes/api_tokens.go index 40f48fec7..0e29911c9 100644 --- a/pkg/routes/api_tokens.go +++ b/pkg/routes/api_tokens.go @@ -46,11 +46,8 @@ func SetupTokenMiddleware() echo.MiddlewareFunc { for _, s := range authHeader { if strings.HasPrefix(s, "Bearer "+models.APITokenPrefix) { - if c.Request().URL.Path == "/api/v1/token/test" { - return true - } - - err := checkAPITokenAndPutItInContext(s, c) + skipRouteCheck := c.Request().URL.Path == "/api/v1/token/test" + err := checkAPITokenAndPutItInContext(s, c, skipRouteCheck) return err == nil } } @@ -71,14 +68,14 @@ func SetupTokenMiddleware() echo.MiddlewareFunc { }) } -func checkAPITokenAndPutItInContext(tokenHeaderValue string, c *echo.Context) error { +func checkAPITokenAndPutItInContext(tokenHeaderValue string, c *echo.Context, skipRouteCheck bool) error { token, u, err := auth.ValidateAPITokenString(strings.TrimPrefix(tokenHeaderValue, "Bearer ")) if err != nil { log.Debugf("[auth] API token validation failed: %v", err) return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } - if !models.CanDoAPIRoute(c, token) { + if !skipRouteCheck && !models.CanDoAPIRoute(c, token) { log.Debugf("[auth] Tried authenticating with token %d but it does not have permission to do this route", token.ID) return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") }