package middleware import ( "errors" "net/http" "net/http/httptest" "testing" "time" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/repository" "hyapp-admin-server/internal/service" "github.com/gin-gonic/gin" ) type fakeAppAccessStore struct { access repository.AppAccess } type fakeAuthorizationStore struct { authorization repository.UserAuthorization err error } func (store fakeAuthorizationStore) CurrentAuthorizationForUser(uint) (repository.UserAuthorization, error) { return store.authorization, store.err } func (store fakeAppAccessStore) AppAccessForUser(uint) (repository.AppAccess, error) { return store.access, nil } func TestRequireAppScopeAllowsOnlySelectedApp(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(func(c *gin.Context) { c.Set(ContextUserID, uint(7)) }) router.GET("/app", RequireAppScope(fakeAppAccessStore{access: repository.AppAccess{ Mode: model.UserAppScopeModeSelected, AppCodes: []string{"lalu"}, }}), func(c *gin.Context) { c.Status(http.StatusNoContent) }) allowed := httptest.NewRequest(http.MethodGet, "/app", nil) allowed.Header.Set("X-App-Code", "LALU") allowedResponse := httptest.NewRecorder() router.ServeHTTP(allowedResponse, allowed) if allowedResponse.Code != http.StatusNoContent { t.Fatalf("allowed status = %d body=%s", allowedResponse.Code, allowedResponse.Body.String()) } for _, header := range []string{"", "huwaa"} { request := httptest.NewRequest(http.MethodGet, "/app", nil) if header != "" { request.Header.Set("X-App-Code", header) } response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusForbidden { t.Fatalf("header %q status = %d body=%s", header, response.Code, response.Body.String()) } } } func TestAuthRequiredUsesCurrentPermissionsInsteadOfJWTClaim(t *testing.T) { gin.SetMode(gin.TestMode) auth := service.NewAuthService("live-authorization-test-secret", time.Hour) // This models an access token issued before a role migration: finance audit // existed in the claim, while operations audit was absent at issue time. token, _, err := auth.GenerateAccessToken(7, "stale-name", []string{"finance-withdrawal:audit"}) if err != nil { t.Fatalf("generate access token: %v", err) } router := gin.New() router.Use(AuthRequired(auth, fakeAuthorizationStore{authorization: repository.UserAuthorization{ Username: "current-name", Status: model.UserStatusActive, Permissions: []string{"operations-withdrawal:audit"}, }})) router.GET("/finance", RequirePermission("finance-withdrawal:audit"), func(c *gin.Context) { c.Status(http.StatusNoContent) }) router.GET("/operations", RequirePermission("operations-withdrawal:audit"), func(c *gin.Context) { if CurrentUsername(c) != "current-name" { c.Status(http.StatusInternalServerError) return } c.Status(http.StatusNoContent) }) financeRequest := httptest.NewRequest(http.MethodGet, "/finance", nil) financeRequest.Header.Set("Authorization", "Bearer "+token) financeResponse := httptest.NewRecorder() router.ServeHTTP(financeResponse, financeRequest) if financeResponse.Code != http.StatusForbidden { t.Fatalf("removed JWT permission status = %d body=%s", financeResponse.Code, financeResponse.Body.String()) } operationsRequest := httptest.NewRequest(http.MethodGet, "/operations", nil) operationsRequest.Header.Set("Authorization", "Bearer "+token) operationsResponse := httptest.NewRecorder() router.ServeHTTP(operationsResponse, operationsRequest) if operationsResponse.Code != http.StatusNoContent { t.Fatalf("current database permission status = %d body=%s", operationsResponse.Code, operationsResponse.Body.String()) } } func TestAuthRequiredFailsClosedWhenAuthorizationCannotBeResolved(t *testing.T) { gin.SetMode(gin.TestMode) auth := service.NewAuthService("authorization-failure-test-secret", time.Hour) token, _, err := auth.GenerateAccessToken(7, "admin", []string{"role:manage"}) if err != nil { t.Fatalf("generate access token: %v", err) } router := gin.New() router.Use(AuthRequired(auth, fakeAuthorizationStore{err: errors.New("database unavailable")})) router.GET("/protected", func(c *gin.Context) { c.Status(http.StatusNoContent) }) request := httptest.NewRequest(http.MethodGet, "/protected", nil) request.Header.Set("Authorization", "Bearer "+token) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusInternalServerError { t.Fatalf("authorization database failure status = %d body=%s", response.Code, response.Body.String()) } }