diff --git a/server/internal/api/scopes.go b/server/internal/api/scopes.go index 533c956..3dbd406 100644 --- a/server/internal/api/scopes.go +++ b/server/internal/api/scopes.go @@ -210,6 +210,17 @@ func RequireScopes() gin.HandlerFunc { } } +// routesOutsideAPIGroup lists routes matching the "/api/" path prefix that +// are nonetheless registered on the root router rather than the authenticated +// /api group, so they carry no session or API-token auth and are exempt from +// routeScopes. scopes.go and serverscope_test.go both read this set so the +// exception is stated once. +var routesOutsideAPIGroup = map[string]bool{ + // The ESO endpoint keeps its own bearer scheme and is deliberately + // outside the token vocabulary. + "GET /api/secrets/:group/values": true, +} + // AssertScopeMapComplete fails boot when a registered /api route has no scope. // // Without it, adding a route silently makes it unreachable by every token, and @@ -220,9 +231,9 @@ func AssertScopeMapComplete(r *gin.Engine) error { if !strings.HasPrefix(route.Path, "/api/") { continue } - // The ESO endpoint keeps its own bearer scheme and is deliberately - // outside the token vocabulary. - if route.Path == "/api/secrets/:group/values" { + // Routes registered outside the authenticated /api group (their own + // bearer scheme, not the token vocabulary) are deliberately excluded. + if routesOutsideAPIGroup[route.Method+" "+route.Path] { continue } if _, ok := routeScopes[route.Method+" "+route.Path]; !ok { diff --git a/server/internal/api/serverscope_test.go b/server/internal/api/serverscope_test.go index 35eb399..b538151 100644 --- a/server/internal/api/serverscope_test.go +++ b/server/internal/api/serverscope_test.go @@ -13,9 +13,13 @@ func TestServerScopeMapCoversEveryScopedRoute(t *testing.T) { } } for r := range serverScopedRoutes { - if _, ok := routeScopes[r]; !ok { - t.Errorf("route %q is declared in serverScopedRoutes but is not a registered route", r) + if _, ok := routeScopes[r]; ok { + continue } + if routesOutsideAPIGroup[r] { + continue + } + t.Errorf("route %q is declared in serverScopedRoutes but is not a registered route", r) } }