123 lines
3.9 KiB
Go
123 lines
3.9 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"
|
|
"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)
|
|
}
|
|
}
|