test(auth/csvauth): regression test for Authenticate token deadlock

Guards against the v1.2.4 bug (fixed in c32acd5) where Authenticate
held a.mux via defer for its full duration, then called
loadAndVerifyToken which also tries to acquire a.mux — deadlock on
every token auth request.

TestAuthenticateTokenNoDeadlock exercises both the bare-token
("", token) and named-username ("api", token) forms with a 1s
timeout, so a regression fails fast rather than hanging the suite.
This commit is contained in:
AJ ONeal 2026-03-23 00:22:59 -06:00
parent a4cb1e3bfd
commit a854fef67e
No known key found for this signature in database

View File

@ -5,8 +5,59 @@ import (
"fmt" "fmt"
"strings" "strings"
"testing" "testing"
"time"
) )
// TestAuthenticateTokenNoDeadlock guards against the v1.2.4 regression where
// Authenticate held a.mux via defer and then called loadAndVerifyToken, which
// also tried to acquire a.mux — causing a deadlock on all token auth requests.
// Fixed in c32acd5 (ref(auth/csvauth): don't hold mutex longer than necessary).
func TestAuthenticateTokenNoDeadlock(t *testing.T) {
var key [16]byte
a := New(key[:])
const secret = "supersecrettoken"
c := a.NewCredential(PurposeToken, "ci-bot", secret, []string{"plain"}, []string{"deploy"}, "")
if err := a.CacheCredential(*c); err != nil {
t.Fatal(err)
}
type result struct {
p any
err error
}
// Authenticate("", token) — the token-as-password form
ch := make(chan result, 1)
go func() {
p, err := a.Authenticate("", secret)
ch <- result{p, err}
}()
select {
case r := <-ch:
if r.err != nil {
t.Fatalf("Authenticate(\"\", token): unexpected error: %v", r.err)
}
case <-time.After(time.Second):
t.Fatal("Authenticate deadlocked — mutex was not released before calling loadAndVerifyToken")
}
// Authenticate("api", token) — the named-token-username form
ch2 := make(chan result, 1)
go func() {
p, err := a.Authenticate("api", secret)
ch2 <- result{p, err}
}()
select {
case r := <-ch2:
if r.err != nil {
t.Fatalf("Authenticate(\"api\", token): unexpected error: %v", r.err)
}
case <-time.After(time.Second):
t.Fatal("Authenticate deadlocked — mutex was not released before calling loadAndVerifyToken")
}
}
func TestCredentialCreationAndVerification(t *testing.T) { func TestCredentialCreationAndVerification(t *testing.T) {
type testCase struct { type testCase struct {
purpose string purpose string