package middleware import ( "context" "errors" "net/http/httptest" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" "gitlab.com/mbugroup/lti-api.git/internal/apikeys" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier" userValidation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations" ) type stubUserService struct { user *entity.User err error } func (s *stubUserService) GetAll(_ *fiber.Ctx, _ *userValidation.Query) ([]entity.User, int64, error) { return nil, 0, nil } func (s *stubUserService) GetOne(_ *fiber.Ctx, _ uint) (*entity.User, error) { return s.user, s.err } func (s *stubUserService) CreateOne(_ *fiber.Ctx, _ *userValidation.Create) (*entity.User, error) { return nil, nil } func (s *stubUserService) UpdateOne(_ *fiber.Ctx, _ *userValidation.Update, _ uint) (*entity.User, error) { return nil, nil } func (s *stubUserService) DeleteOne(_ *fiber.Ctx, _ uint) error { return nil } func (s *stubUserService) GetBySSOUserID(_ *fiber.Ctx, _ uint) (*entity.User, error) { return s.user, s.err } type stubAPIKeyAuthenticator struct { principal *apikeys.Principal err error } func (s *stubAPIKeyAuthenticator) Authenticate(_ context.Context, _ string, _ string) (*apikeys.Principal, error) { return s.principal, s.err } func TestAuthAllowsAPIKeyOnGet(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ principal: &apikeys.Principal{ Name: "dashboard", Permissions: []string{"perm.read"}, LocationIDs: []uint{3, 5}, }, }) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { scope, err := ResolveLocationScope(c, nil) if err != nil { return err } return c.JSON(fiber.Map{ "principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType, "restrict": scope.Restrict, "ids": scope.IDs, }) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } } func TestAuthRejectsAPIKeyOnPost(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ principal: &apikeys.Principal{ Name: "dashboard", Permissions: []string{"perm.write"}, }, }) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Post("/reports", Auth(&stubUserService{}), RequirePermissions("perm.write"), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest(fiber.MethodPost, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.StatusCode) } } func TestAuthRejectsInvalidAPIKey(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInvalidAPIKey}) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.StatusCode) } } func TestAuthRejectsInactiveAPIKey(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInactiveKey}) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.StatusCode) } } func TestAuthRejectsMissingPermission(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ principal: &apikeys.Principal{ Name: "dashboard", Permissions: []string{"perm.other"}, }, }) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusForbidden { t.Fatalf("expected 403, got %d", resp.StatusCode) } } func TestAuthAllowsBearerOnGet(t *testing.T) { previousVerify := verifyAccessTokenFunc previousProfile := fetchProfileFunc defer func() { verifyAccessTokenFunc = previousVerify fetchProfileFunc = previousProfile }() verifyAccessTokenFunc = func(_ string) (*sso.VerificationResult, error) { return &sso.VerificationResult{ UserID: 1, Claims: &sso.AccessTokenClaims{ RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now().UTC()), }, }, }, nil } fetchProfileFunc = func(_ context.Context, _ string, _ *sso.VerificationResult) (*sso.UserProfile, error) { return &sso.UserProfile{ Permissions: []sso.Permission{{Name: "perm.read"}}, LocationIDs: []uint{7}, }, nil } app := fiber.New() app.Get("/reports", Auth(&stubUserService{user: &entity.User{Id: 9, Name: "API User"}}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType}) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("Authorization", "Bearer test-token") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } } func TestAuthReturnsServerErrorWhenAPIKeyVerifierFailsUnexpectedly(t *testing.T) { SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: errors.New("boom")}) defer SetAPIKeyAuthenticator(nil) app := fiber.New() app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) req.Header.Set("X-API-Key", "lti_dev_prefix_secret") resp, err := app.Test(req) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.StatusCode != fiber.StatusInternalServerError { t.Fatalf("expected 500, got %d", resp.StatusCode) } }