refactor(10.2-01): nest framework packages under modules

- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
Jakub Zych
2026-09-28 02:21:02 +02:00
parent ac1f6d14f4
commit 5e50b166ef
277 changed files with 303 additions and 303 deletions

View File

@@ -0,0 +1,151 @@
package wristband
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// This file closes the last named-Go-evidence gaps 08-10-PLAN.md Task 1's
// 103-method PHP audit found in wristband: cases the existing 08-01..08-09
// test files exercised only partially, or not at all. See
// .planning/phases/08-oauth2-1-authorization-server/08-PHP-TEST-MAP.md for
// the full map; each test below is named after (and comments cite) the PHP
// method it closes.
// TestPhase08Coverage is the 08-10-PLAN.md Task 1 focused-verify aggregator
// (`go test ./wristband -run '^Test(Phase08Coverage|PHPTestMap)'`): each
// individually-named test in this file also runs directly and is the
// evidence 08-PHP-TEST-MAP.md cites by name, but this wrapper gives the
// task's own prescribed fast command something to match.
func TestPhase08Coverage(t *testing.T) {
t.Run("TestRegisterLoopbackHTTPIsAccepted", TestRegisterLoopbackHTTPIsAccepted)
t.Run("TestRegisterJavascriptURIIsRejected", TestRegisterJavascriptURIIsRejected)
t.Run("TestRegisterErrorBodyHasNoSecretOrStackTrace", TestRegisterErrorBodyHasNoSecretOrStackTrace)
t.Run("TestTokenPlainPKCEIsRejectedEvenWhenVerifierEqualsChallenge", TestTokenPlainPKCEIsRejectedEvenWhenVerifierEqualsChallenge)
}
// TestRegisterLoopbackHTTPIsAccepted ports
// OAuthRegisterTest::test_loopback_http_is_accepted: an http:// redirect
// URI on 127.0.0.1 or localhost is not rejected the way any other
// http:// URI is.
func TestRegisterLoopbackHTTPIsAccepted(t *testing.T) {
for _, host := range []string{"127.0.0.1", "localhost"} {
t.Run(host, func(t *testing.T) {
srv := newTestServer(newMemoryBackend())
body := `{"redirect_uris":["http://` + host + `:8080/callback"]}`
req := httptest.NewRequest(http.MethodPost, "/oauth/mcp/register", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
srv.Register(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status = %d, want %d (body=%s)", rec.Code, http.StatusCreated, rec.Body.String())
}
})
}
}
// TestRegisterJavascriptURIIsRejected ports the "javascript uris" half of
// OAuthRegisterTest::test_http_non_loopback_and_javascript_uris_are_rejected
// (the http-non-loopback half is TestRegisterRedirectURIBounds/http-non-loopback).
func TestRegisterJavascriptURIIsRejected(t *testing.T) {
srv := newTestServer(newMemoryBackend())
body := `{"redirect_uris":["javascript:alert(1)"]}`
req := httptest.NewRequest(http.MethodPost, "/oauth/mcp/register", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
srv.Register(rec, req)
// javascript: has no host component, so it fails the same "not a valid
// URL" branch a hostless URI does (rejectRedirectURI), not the
// scheme-specific message -- still rejected, never accepted.
assertRegisterError(t, rec, http.StatusBadRequest, "invalid_redirect_uri",
"Redirect URI is not a valid URL: javascript:alert(1)")
}
// TestRegisterErrorBodyHasNoSecretOrStackTrace ports
// OAuthRegisterTest::test_error_body_has_no_secret_or_stack: every DCR
// error path returns exactly {"error","error_description"} -- no stray
// field, no client_secret, no Go internal type/path/stack leakage -- and a
// still-later valid registration is unaffected by the earlier failures.
func TestRegisterErrorBodyHasNoSecretOrStackTrace(t *testing.T) {
backend := newMemoryBackend()
srv := newTestServer(backend)
badBodies := []string{
`{not json`,
`{"redirect_uris":[]}`,
`{"redirect_uris":["not-a-url"]}`,
`{"redirect_uris":["https://client.example.test/cb"],"token_endpoint_auth_method":"bogus"}`,
}
for _, body := range badBodies {
req := httptest.NewRequest(http.MethodPost, "/oauth/mcp/register", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
srv.Register(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("body %q: status = %d, want %d", body, rec.Code, http.StatusBadRequest)
}
var got map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatalf("body %q: decode: %v", body, err)
}
if len(got) != 2 {
t.Fatalf("body %q: error body has %d fields, want exactly 2 (error, error_description): %v", body, len(got), got)
}
if _, ok := got["error"]; !ok {
t.Fatalf("body %q: missing error field: %v", body, got)
}
if _, ok := got["error_description"]; !ok {
t.Fatalf("body %q: missing error_description field: %v", body, got)
}
raw := rec.Body.String()
for _, forbidden := range []string{"client_secret", "runtime error", "goroutine", ".go:", "panic"} {
if strings.Contains(raw, forbidden) {
t.Fatalf("body %q: error response leaked %q: %s", body, forbidden, raw)
}
}
}
// A still-later valid registration is unaffected by the prior failures
// (no partial state, no leaked cap consumption).
okReq := httptest.NewRequest(http.MethodPost, "/oauth/mcp/register", strings.NewReader(
`{"redirect_uris":["https://client.example.test/cb"]}`))
okReq.Header.Set("Content-Type", "application/json")
okRec := httptest.NewRecorder()
srv.Register(okRec, okReq)
if okRec.Code != http.StatusCreated {
t.Fatalf("valid registration after failures: status = %d, want %d (body=%s)", okRec.Code, http.StatusCreated, okRec.Body.String())
}
}
// TestTokenPlainPKCEIsRejectedEvenWhenVerifierEqualsChallenge ports
// security/OAuthRefreshRotationTest::test_plain_pkce_is_rejected_even_when_verifier_equals_challenge.
// Authorize itself never persists a "plain" code_challenge_method
// (TestPKCEChallengeMethodMustBeS256), so this seeds a code row directly to
// prove the defense also holds at the token endpoint: verifyPkce rejects
// any non-S256 method outright, never falling back to a naive
// verifier == stored-challenge string compare.
func TestTokenPlainPKCEIsRejectedEvenWhenVerifierEqualsChallenge(t *testing.T) {
backend := newMemoryBackend()
srv := newTestServer(backend)
insertTokenTestClient(backend, "cli-plain-exchange", "none", nil, nil)
const verifier = "same-value-used-as-both-verifier-and-challenge-01234"
rawCode, _ := insertTokenTestCode(t, backend, "cli-plain-exchange", verifier, func(rec *AuthCodeRecord) {
rec.CodeChallengeMethod = "plain"
})
form := validExchangeForm(rawCode, verifier, "cli-plain-exchange")
req := tokenRequest(form, "")
rec := httptest.NewRecorder()
srv.Token(rec, req)
assertTokenError(t, rec, http.StatusBadRequest, "invalid_grant")
backend.mu.Lock()
defer backend.mu.Unlock()
if len(backend.tokens) != 0 {
t.Fatal("a plain-method exchange must not mint an access token even when verifier equals the stored challenge")
}
}