auth-bundle-1 Phase 0: extract internal/auth/ from middleware package

Bundle 1 / Phase 0: pure refactor splitting auth surface out of internal/api/middleware so Bundle 2 (OIDC + sessions) and the broader RBAC primitive (roles, permissions, scoped grants) have a clean home.

Moved to internal/auth/: NamedAPIKey, HashAPIKey, AuthConfig, NewAuthWithNamedKeys, NewAuth, UserKey, AdminKey, GetUser, IsAdmin. Added testfixtures.go (WithActor / WithAdmin / WithActorAdmin) so handler tests don't construct context manually.

Stayed in internal/api/middleware/: RequestID, Logging, NewLogging, Recovery, RateLimitConfig, NewRateLimiter (now imports auth.GetUser for per-user keying per audit Category C), CORSConfig, NewCORS, ContentType, CORS, GetRequestID, responseWriter, Chain, audit middleware (now imports auth.GetUser).

Updated 22 caller files across cmd/, internal/api/handler/, internal/api/middleware/, internal/mcp/. Existing m008_admin_gate_test.go now scans for auth.IsAdmin( substring; Phase 3 will further evolve to track auth.RequirePermission. Behavior unchanged: all handler / middleware / service / connector / cmd / mcp tests pass with no test-logic edits, only import-path renames.

Phase 0 exit criteria: internal/auth/ exists with 6 files; middleware.go went 575 -> 422 lines (auth-related ~150 lines moved out); grep -rE 'middleware\.(GetUser|IsAdmin|UserKey|AdminKey|NamedAPIKey|HashAPIKey|NewAuth)' returns 0 hits; context.WithValue(.*middleware.UserKey/AdminKey) returns 0 hits; go vet ./... clean; go test -short ./... green across all packages tested.

Branch: dev/auth-bundle-1. Per cowork/auth-bundle-1-prompt.md, do not merge to master without (1) make verify green, (2) >= 2 external testers confirm, (3) >= 90% coverage on internal/auth/ in .github/coverage-thresholds.yml.
This commit is contained in:
shankar0123
2026-05-09 15:51:31 +00:00
parent aad95d315b
commit 69f860171e
32 changed files with 397 additions and 283 deletions
+20 -19
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/certctl-io/certctl/internal/api/middleware"
"github.com/certctl-io/certctl/internal/auth"
"github.com/certctl-io/certctl/internal/service"
)
@@ -81,7 +82,7 @@ func TestAdminSCEPIntune_AdminExplicitFalse_Returns403(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -106,8 +107,8 @@ func TestAdminSCEPIntune_AdminPermitted_ForwardsActor(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -160,7 +161,7 @@ func TestAdminSCEPIntuneReload_AdminExplicitFalse_Returns403(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(`{"path_id":"corp"}`))
req.ContentLength = int64(len(`{"path_id":"corp"}`))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, false)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, false)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -181,8 +182,8 @@ func TestAdminSCEPIntuneReload_AdminPermitted_ForwardsActor(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(body))
req.ContentLength = int64(len(body))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -211,7 +212,7 @@ func TestAdminSCEPIntuneReload_AdminPermitted_ForwardsActor(t *testing.T) {
func TestAdminSCEPIntuneStats_RejectsNonGetMethod(t *testing.T) {
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/stats", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.Stats(w, req)
@@ -223,7 +224,7 @@ func TestAdminSCEPIntuneStats_RejectsNonGetMethod(t *testing.T) {
func TestAdminSCEPIntuneReload_RejectsNonPostMethod(t *testing.T) {
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/reload-trust", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -236,7 +237,7 @@ func TestAdminSCEPIntuneStats_PropagatesServiceError(t *testing.T) {
svc := &fakeAdminSCEPIntuneService{statsErr: errors.New("registry walk failed")}
h := NewAdminSCEPIntuneHandler(svc)
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.Stats(w, req)
@@ -251,7 +252,7 @@ func TestAdminSCEPIntuneReload_ProfileNotFound_Returns404(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(`{"path_id":"nonexistent"}`))
req.ContentLength = int64(len(`{"path_id":"nonexistent"}`))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -266,7 +267,7 @@ func TestAdminSCEPIntuneReload_IntuneDisabled_Returns409(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(`{"path_id":"iot"}`))
req.ContentLength = int64(len(`{"path_id":"iot"}`))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -281,7 +282,7 @@ func TestAdminSCEPIntuneReload_BadReloadPropagates500(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(`{"path_id":"corp"}`))
req.ContentLength = int64(len(`{"path_id":"corp"}`))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -294,7 +295,7 @@ func TestAdminSCEPIntuneReload_EmptyBodyTargetsLegacyRoot(t *testing.T) {
svc := &fakeAdminSCEPIntuneService{}
h := NewAdminSCEPIntuneHandler(svc)
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -312,7 +313,7 @@ func TestAdminSCEPIntuneReload_RejectsMalformedJSON(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
strings.NewReader(bad))
req.ContentLength = int64(len(bad))
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ReloadTrust(w, req)
@@ -379,7 +380,7 @@ func TestAdminSCEPProfiles_AdminExplicitFalse_Returns403(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -417,8 +418,8 @@ func TestAdminSCEPProfiles_AdminPermitted_ForwardsActor(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@@ -461,7 +462,7 @@ func TestAdminSCEPProfiles_AdminPermitted_ForwardsActor(t *testing.T) {
func TestAdminSCEPProfiles_RejectsNonGetMethod(t *testing.T) {
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/profiles", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.Profiles(w, req)
@@ -474,7 +475,7 @@ func TestAdminSCEPProfiles_PropagatesServiceError(t *testing.T) {
svc := &fakeAdminSCEPIntuneService{profilesErr: errors.New("registry walk failed")}
h := NewAdminSCEPIntuneHandler(svc)
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.Profiles(w, req)