Files
krow_backend/go-api/internal/oauth/pkce_test.go
Aravind f2aa3b3ad8
Some checks failed
CI / fixture (push) Has been cancelled
CI / test (push) Has been cancelled
mcp connection
2026-09-22 10:58:02 +05:30

108 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package oauth
import (
"errors"
"strings"
"testing"
)
// The RFC 7636 appendix B worked example. Using the spec's own vector rather
// than a value this implementation produced means the test would catch an
// encoding mistake that is self-consistent — base64 standard instead of
// base64url, say, or padded instead of raw — which a round-trip test could not.
const (
specVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
specChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
)
func TestChallengeForMatchesTheRFCVector(t *testing.T) {
if got := ChallengeFor(specVerifier); got != specChallenge {
t.Errorf("ChallengeFor(RFC 7636 verifier) = %q, want %q", got, specChallenge)
}
}
func TestVerifyChallengeAcceptsTheCorrectVerifier(t *testing.T) {
if err := VerifyChallenge(specVerifier, specChallenge, MethodS256); err != nil {
t.Errorf("the RFC's own verifier was rejected: %v", err)
}
}
func TestVerifyChallengeRejectsAWrongVerifier(t *testing.T) {
// Same length and character set, one character different. A comparison
// that was accidentally checking length or prefix would let this through.
wrong := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXX"
err := VerifyChallenge(wrong, specChallenge, MethodS256)
if !errors.Is(err, ErrVerifierMismatch) {
t.Errorf("err = %v, want ErrVerifierMismatch", err)
}
}
func TestVerifyChallengeRejectsAMissingVerifier(t *testing.T) {
if err := VerifyChallenge("", specChallenge, MethodS256); !errors.Is(err, ErrMalformedVerifier) {
t.Errorf("err = %v, want ErrMalformedVerifier", err)
}
}
// `plain` must be refused wherever it appears. It is legal in RFC 7636 and
// forbidden by OAuth 2.1 for public clients, because the challenge IS the
// verifier and anyone who stole one stole both.
func TestPlainMethodIsRejected(t *testing.T) {
for _, method := range []string{"plain", "PLAIN", "", "s256", "S512"} {
t.Run("method="+method, func(t *testing.T) {
if err := ValidateChallenge(specChallenge, method); !errors.Is(err, ErrUnsupportedChallengeMethod) {
t.Errorf("ValidateChallenge: err = %v, want ErrUnsupportedChallengeMethod", err)
}
if err := VerifyChallenge(specVerifier, specChallenge, method); !errors.Is(err, ErrUnsupportedChallengeMethod) {
t.Errorf("VerifyChallenge: err = %v, want ErrUnsupportedChallengeMethod", err)
}
})
}
}
func TestMalformedChallengeIsRejected(t *testing.T) {
for name, challenge := range map[string]string{
"empty": "",
"too short": "abc",
"too long": strings.Repeat("a", 44),
"padded base64": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM=",
"standard base64": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw+cM",
"illegal char": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw!cM",
"whitespace": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw cM",
"newline injected": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw\ncM",
} {
t.Run(name, func(t *testing.T) {
if err := ValidateChallenge(challenge, MethodS256); !errors.Is(err, ErrMalformedChallenge) {
t.Errorf("err = %v, want ErrMalformedChallenge", err)
}
})
}
}
// RFC 7636 section 4.1 constrains the verifier to 43–128 unreserved characters.
// A verifier outside that range is malformed regardless of what it hashes to.
func TestMalformedVerifierIsRejected(t *testing.T) {
for name, verifier := range map[string]string{
"too short (42)": strings.Repeat("a", 42),
"too long (129)": strings.Repeat("a", 129),
"illegal char": strings.Repeat("a", 42) + "!",
"whitespace": strings.Repeat("a", 42) + " ",
} {
t.Run(name, func(t *testing.T) {
if err := VerifyChallenge(verifier, specChallenge, MethodS256); !errors.Is(err, ErrMalformedVerifier) {
t.Errorf("err = %v, want ErrMalformedVerifier", err)
}
})
}
}
// A verifier at each end of the legal range must be accepted, or clients
// generating the maximum length would fail against this server.
func TestVerifierBoundariesAreAccepted(t *testing.T) {
for _, length := range []int{43, 128} {
verifier := strings.Repeat("a", length)
if err := VerifyChallenge(verifier, ChallengeFor(verifier), MethodS256); err != nil {
t.Errorf("a %d-character verifier was rejected: %v", length, err)
}
}
}