146 lines
3.6 KiB
Go
146 lines
3.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
const (
|
|
keyFull = "full-access-key-0000000000000000000000"
|
|
keyScoped = "scoped-key-1111111111111111111111111111"
|
|
)
|
|
|
|
func testStore() *Store {
|
|
return NewStore([]APIKey{
|
|
{Name: "full", Key: keyFull},
|
|
{Name: "dev", Key: keyScoped, Scope: Scope{Collections: []string{"Secrets - DEV"}}},
|
|
})
|
|
}
|
|
|
|
func TestStoreMatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := testStore()
|
|
|
|
tests := []struct {
|
|
name string
|
|
provided string
|
|
wantOK bool
|
|
wantName string
|
|
wantScope Scope
|
|
}{
|
|
{"full access key", keyFull, true, "full", Scope{}},
|
|
{"scoped key", keyScoped, true, "dev", Scope{Collections: []string{"Secrets - DEV"}}},
|
|
{"unknown key", "nope-nope-nope-nope-nope-nope-nope-nope", false, "", Scope{}},
|
|
{"empty provided", "", false, "", Scope{}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, ok := store.Match(tt.provided)
|
|
if ok != tt.wantOK {
|
|
t.Fatalf("Match ok = %v, want %v", ok, tt.wantOK)
|
|
}
|
|
if !tt.wantOK {
|
|
return
|
|
}
|
|
if got.Name != tt.wantName {
|
|
t.Errorf("Match name = %q, want %q", got.Name, tt.wantName)
|
|
}
|
|
if !reflect.DeepEqual(got.Scope, tt.wantScope) {
|
|
t.Errorf("Match scope = %+v, want %+v", got.Scope, tt.wantScope)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStoreMatchEmptyStore(t *testing.T) {
|
|
t.Parallel()
|
|
if _, ok := NewStore(nil).Match(keyFull); ok {
|
|
t.Error("empty store should not match any key")
|
|
}
|
|
}
|
|
|
|
func TestScopeIsEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
if !(Scope{}).IsEmpty() {
|
|
t.Error("zero scope should be empty")
|
|
}
|
|
if (Scope{Organizations: []string{"x"}}).IsEmpty() {
|
|
t.Error("scope with organizations should not be empty")
|
|
}
|
|
if (Scope{Collections: []string{"x"}}).IsEmpty() {
|
|
t.Error("scope with collections should not be empty")
|
|
}
|
|
}
|
|
|
|
func TestMiddleware(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := fiber.New()
|
|
app.Use(Middleware(testStore()))
|
|
app.Get("/", func(c *fiber.Ctx) error {
|
|
scope, ok := ScopeFromCtx(c)
|
|
if !ok {
|
|
return c.Status(fiber.StatusInternalServerError).SendString("no scope")
|
|
}
|
|
if scope.IsEmpty() {
|
|
return c.SendString("full")
|
|
}
|
|
return c.SendString("scoped")
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
header string
|
|
wantStatus int
|
|
wantBody string
|
|
}{
|
|
{"missing header", "", http.StatusUnauthorized, "missing authorization header"},
|
|
{"malformed header", "Token abc", http.StatusUnauthorized, "invalid authorization header format"},
|
|
{"unknown key", "Bearer wrong-key", http.StatusUnauthorized, "invalid api key"},
|
|
{"full access key", "Bearer " + keyFull, http.StatusOK, "full"},
|
|
{"scoped key", "Bearer " + keyScoped, http.StatusOK, "scoped"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
|
|
if tt.header != "" {
|
|
req.Header.Set("Authorization", tt.header)
|
|
}
|
|
resp, err := app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != tt.wantStatus {
|
|
t.Errorf("status = %d, want %d", resp.StatusCode, tt.wantStatus)
|
|
}
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if !strings.Contains(string(body), tt.wantBody) {
|
|
t.Errorf("body = %q, want substring %q", body, tt.wantBody)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopeFromCtxAbsent(t *testing.T) {
|
|
t.Parallel()
|
|
app := fiber.New()
|
|
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
defer app.ReleaseCtx(ctx)
|
|
if _, ok := ScopeFromCtx(ctx); ok {
|
|
t.Error("ScopeFromCtx should report false when no scope set")
|
|
}
|
|
}
|