Files
go-omni/auth/auth_test.go

122 lines
3.8 KiB
Go

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"
)
func TestRequireAuthAndRole(t *testing.T) {
app := omni.New()
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()
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)
}
}