package auth_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "git.yuntuo.hk/k2safe/go-omni" "git.yuntuo.hk/k2safe/go-omni/auth" "github.com/gin-gonic/gin" ) func TestRequireAuthAndRole(t *testing.T) { app := omni.New(omni.WithMode(gin.TestMode)) authenticator := auth.StaticAuthenticator{"good-token": {UserID: "user-1", Roles: []string{"user"}}} private := app.Group("/private", auth.RequireAuth(authenticator)) private.GET("/me", func(ctx *omni.Context) error { principal, ok := auth.FromContext(ctx) if !ok { t.Fatal("principal was not attached") } return ctx.OK(principal) }) private.GET("/admin", func(ctx *omni.Context) error { return ctx.OK(map[string]string{"ok": "true"}) }, auth.RequireRole("admin")) tests := []struct { name, authorization, path, code string status int }{ {"missing", "", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized}, {"malformed", "Token good-token", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized}, {"invalid", "Bearer bad-token", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized}, {"valid", "Bearer good-token", "/private/me", "OK", http.StatusOK}, {"forbidden", "Bearer good-token", "/private/admin", "FORBIDDEN", http.StatusForbidden}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, test.path, nil) if test.authorization != "" { request.Header.Set("Authorization", test.authorization) } app.ServeHTTP(recorder, request) if recorder.Code != test.status { t.Fatalf("expected status %d, got %d: %s", test.status, recorder.Code, recorder.Body.String()) } var body struct { Code string `json:"code"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body.Code != test.code { t.Fatalf("expected code %q, got %q", test.code, body.Code) } }) } } func TestOptionalAuth(t *testing.T) { app := omni.New(omni.WithMode(gin.TestMode)) app.GET("/public", func(ctx *omni.Context) error { principal, authenticated := auth.FromContext(ctx) return ctx.OK(map[string]any{"authenticated": authenticated, "userId": principal.UserID}) }, auth.OptionalAuth(auth.StaticAuthenticator{"token": {UserID: "user-1"}})) for _, test := range []struct { name, token string wantAuth bool status int }{ {"anonymous", "", false, http.StatusOK}, {"authenticated", "Bearer token", true, http.StatusOK}, {"invalid", "Bearer bad", false, http.StatusUnauthorized}, } { t.Run(test.name, func(t *testing.T) { recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/public", nil) if test.token != "" { request.Header.Set("Authorization", test.token) } app.ServeHTTP(recorder, request) if recorder.Code != test.status { t.Fatalf("expected status %d, got %d", test.status, recorder.Code) } if test.wantAuth { var body struct { Data struct { Authenticated bool `json:"authenticated"` } `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if !body.Data.Authenticated { t.Fatal("expected authenticated response") } } }) } } func TestStaticAuthenticatorDoesNotExposeMutableSlices(t *testing.T) { authenticator := auth.StaticAuthenticator{"token": {UserID: "user-1", Roles: []string{"admin"}, Claims: map[string]any{"tenant": "a"}}} principal, err := authenticator.Authenticate(context.Background(), "token") if err != nil { t.Fatal(err) } principal.Roles[0] = "changed" principal.Claims["tenant"] = "changed" again, err := authenticator.Authenticate(context.Background(), "token") if err != nil { t.Fatal(err) } if !again.HasRole("admin") || again.Claims["tenant"] != "a" { t.Fatalf("static authenticator returned mutable state: %+v", again) } }