From 15a306dc4a3dc781646524d3ac02c30588021116 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 25 Mar 2026 22:22:31 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20OCI=20route=20mounting=20=E2=80=94=20inte?= =?UTF-8?q?grate=20into=20authenticated=20/v2=20group?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewRouter now accepts an optional OCI handler to mount inside the authenticated /v2 route group, avoiding chi's Mount conflict on an existing path. Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/mcrsrv/main.go | 4 +--- internal/server/routes.go | 14 +++++++++++--- internal/server/routes_test.go | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cmd/mcrsrv/main.go b/cmd/mcrsrv/main.go index f0cb742..8750c6f 100644 --- a/cmd/mcrsrv/main.go +++ b/cmd/mcrsrv/main.go @@ -123,9 +123,7 @@ func runServer(configPath string) error { // Create OCI handler and HTTP router. ociHandler := oci.NewHandler(database, store, policyEngine, auditFn) - router := server.NewRouter(authClient, authClient, cfg.MCIAS.ServiceName) - // Mount OCI endpoints at /v2. - router.Mount("/v2", ociHandler.Router()) + router := server.NewRouter(authClient, authClient, cfg.MCIAS.ServiceName, ociHandler.Router()) // Mount admin REST endpoints. gcState := &server.GCState{ Collector: collector, diff --git a/internal/server/routes.go b/internal/server/routes.go index 2f2d67c..8ba0b98 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -1,10 +1,15 @@ package server -import "github.com/go-chi/chi/v5" +import ( + "net/http" + + "github.com/go-chi/chi/v5" +) // NewRouter builds the chi router with all OCI Distribution Spec -// endpoints and auth middleware wired up. -func NewRouter(validator TokenValidator, loginClient LoginClient, serviceName string) *chi.Mux { +// endpoints and auth middleware wired up. If ociRouter is non-nil, +// its routes are mounted under /v2 behind the auth middleware. +func NewRouter(validator TokenValidator, loginClient LoginClient, serviceName string, ociRouter http.Handler) *chi.Mux { r := chi.NewRouter() // Token endpoint is NOT behind RequireAuth — clients use Basic auth @@ -15,6 +20,9 @@ func NewRouter(validator TokenValidator, loginClient LoginClient, serviceName st r.Route("/v2", func(v2 chi.Router) { v2.Use(RequireAuth(validator, serviceName)) v2.Get("/", V2Handler()) + if ociRouter != nil { + v2.Mount("/", ociRouter) + } }) return r diff --git a/internal/server/routes_test.go b/internal/server/routes_test.go index dcf00b8..ffb2f08 100644 --- a/internal/server/routes_test.go +++ b/internal/server/routes_test.go @@ -15,7 +15,7 @@ func TestRoutesV2Authenticated(t *testing.T) { claims: &auth.Claims{Subject: "alice", AccountType: "user", Roles: []string{"reader"}}, } loginClient := &fakeLoginClient{token: "tok-abc", expiresIn: 3600} - router := NewRouter(validator, loginClient, "mcr-test") + router := NewRouter(validator, loginClient, "mcr-test", nil) req := httptest.NewRequest(http.MethodGet, "/v2/", nil) req.Header.Set("Authorization", "Bearer valid-token") @@ -42,7 +42,7 @@ func TestRoutesV2Unauthenticated(t *testing.T) { t.Helper() validator := &fakeValidator{claims: nil, err: auth.ErrUnauthorized} loginClient := &fakeLoginClient{} - router := NewRouter(validator, loginClient, "mcr-test") + router := NewRouter(validator, loginClient, "mcr-test", nil) req := httptest.NewRequest(http.MethodGet, "/v2/", nil) // No Authorization header. @@ -66,7 +66,7 @@ func TestRoutesTokenEndpoint(t *testing.T) { // The validator should never be called for /v2/token. validator := &fakeValidator{claims: nil, err: auth.ErrUnauthorized} loginClient := &fakeLoginClient{token: "tok-from-login", expiresIn: 1800} - router := NewRouter(validator, loginClient, "mcr-test") + router := NewRouter(validator, loginClient, "mcr-test", nil) req := httptest.NewRequest(http.MethodGet, "/v2/token", nil) req.SetBasicAuth("bob", "password")