package authn import ( "bytes" "context" "database/sql" "errors" "net/http" "net/http/httptest" "testing" ) func TestHashToken_IsSHA256AndStable(t *testing.T) { a := HashToken("s3cret") b := HashToken("s3cret") if len(a) != 32 { t.Fatalf("hash length = %d, want 32", len(a)) } if !bytes.Equal(a, b) { t.Fatal("hashing the same token twice produced different values") } if bytes.Equal(a, HashToken("s3cres")) { t.Fatal("distinct tokens hashed to the same value") } } func TestBearerFromRequest(t *testing.T) { cases := map[string]struct{ header, want string }{ "bearer": {"Bearer abc123", "abc123"}, "lowercase scheme": {"bearer abc123", "abc123"}, "mixed case scheme": {"BeArEr abc123", "abc123"}, "padded": {"Bearer abc123 ", "abc123"}, "basic is not ours": {"Basic dXNlcjpwYXNz", ""}, "no scheme": {"abc123", ""}, "absent": {"", ""}, } for name, c := range cases { t.Run(name, func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) if c.header != "" { r.Header.Set("Authorization", c.header) } if got := BearerFromRequest(r); got != c.want { t.Fatalf("BearerFromRequest = %q, want %q", got, c.want) } }) } } func TestResolveAgentToken_Accepted(t *testing.T) { store := newStubStore() want := store.add("live-token", "laptop") got, err := ResolveAgentToken(context.Background(), store, "live-token") if err != nil { t.Fatalf("ResolveAgentToken: %v", err) } if got.ID != want.ID || got.Name != "laptop" { t.Fatalf("resolved %+v, want id %d name %q", got, want.ID, "laptop") } if got.IsRevoked() { t.Fatal("live token reported as revoked") } } func TestResolveAgentToken_Revoked(t *testing.T) { store := newStubStore() store.revoke("dead-token", "cron") _, err := ResolveAgentToken(context.Background(), store, "dead-token") if !errors.Is(err, ErrRevokedToken) { t.Fatalf("error = %v, want ErrRevokedToken", err) } // A revoked token must not be reported as unknown: the operator needs to // tell "I killed this" from "this never existed". if errors.Is(err, ErrUnknownToken) { t.Fatalf("revoked token also reported as unknown: %v", err) } if !IsAuthFailure(err) { t.Fatalf("revocation must be a permanent auth failure: %v", err) } } func TestResolveAgentToken_Unknown(t *testing.T) { store := newStubStore() store.add("live-token", "laptop") _, err := ResolveAgentToken(context.Background(), store, "never-issued") if !errors.Is(err, ErrUnknownToken) { t.Fatalf("error = %v, want ErrUnknownToken", err) } if !IsAuthFailure(err) { t.Fatalf("unknown token must be a permanent auth failure: %v", err) } } // db/ may hand back a bare sql.ErrNoRows from QueryRow().Scan(); it means the // same thing as ErrUnknownToken and must not be mistaken for a store outage. func TestResolveAgentToken_SQLNoRowsIsUnknown(t *testing.T) { store := newStubStore() store.err = sql.ErrNoRows _, err := ResolveAgentToken(context.Background(), store, "whatever") if !errors.Is(err, ErrUnknownToken) { t.Fatalf("error = %v, want ErrUnknownToken", err) } } func TestResolveAgentToken_EmptyIsNoToken(t *testing.T) { store := newStubStore() _, err := ResolveAgentToken(context.Background(), store, "") if !errors.Is(err, ErrNoToken) { t.Fatalf("error = %v, want ErrNoToken", err) } if store.calls != 0 { t.Fatalf("store consulted %d times for an absent token, want 0", store.calls) } } // A store outage must never read as a bad credential: fail closed, but tell the // caller it is transient so it answers 503 and the agent retries. func TestResolveAgentToken_StoreFailureIsTransient(t *testing.T) { boom := errors.New("connection refused") store := newStubStore() store.add("live-token", "laptop") store.err = boom _, err := ResolveAgentToken(context.Background(), store, "live-token") if !errors.Is(err, boom) { t.Fatalf("error = %v, want it to wrap the store error", err) } if IsAuthFailure(err) { t.Fatalf("store failure must not be a permanent auth failure: %v", err) } } // The constant-time re-check exists so that a store which matched loosely — by // prefix, or on the wrong column — cannot authenticate anybody. func TestResolveAgentToken_HashMismatchRejected(t *testing.T) { store := newStubStore() store.put(HashToken("presented"), AgentToken{ ID: 7, Name: "sloppy-store", Hash: HashToken("something-else"), }) _, err := ResolveAgentToken(context.Background(), store, "presented") if !errors.Is(err, ErrInvalidToken) { t.Fatalf("error = %v, want ErrInvalidToken", err) } } func TestResolveAgentToken_NilStoreIsNotAnAuthFailure(t *testing.T) { _, err := ResolveAgentToken(context.Background(), nil, "live-token") if err == nil { t.Fatal("nil store must be an error") } if IsAuthFailure(err) { t.Fatalf("a wiring bug must not read as a bad credential: %v", err) } }