calcomblock/router_paths_test.go
Alex Dunmow 3967fa955a fix: rebuild admin claims from trusted headers — v2.0.4
Context does not cross the wasm ABI, so requireAdmin's
auth.GetUserFromContext only sees claims if the guest rebuilds them from
the host-stamped X-Bn-Verified-* headers. 2.0.3 never mounted
auth.TrustedHeaderMiddleware, so every admin endpoint (settings save,
event-types, test, rotate) returned 401 for real admins — the settings
panel could not store the Cal.com API key. Mount the middleware on the
router and pin the contract with a header-driven test (viewer headers
must reach requireAdmin and 403, never 401): the existing admin tests
injected claims straight into context, which is exactly the path wasm
does not have.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:03:28 +08:00

64 lines
2.6 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 main
import (
"net/http"
"net/http/httptest"
"testing"
"git.dev.alexdunmow.com/block/pluginsdk/auth"
"git.dev.alexdunmow.com/block/pluginsdk/plugin"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
// TestRouterMatchesAbsolutePluginPaths pins the wasm routing contract: the
// host forwards the FULL request path (/api/plugins/calcomblock/...) to the
// guest with no prefix strip, so every route must be registered absolute.
// Relative registrations 404 the plugin's entire HTTP surface at runtime —
// exactly the regression that shipped in 2.0.02.0.2.
func TestRouterMatchesAbsolutePluginPaths(t *testing.T) {
router, ok := NewCalcomRouter(plugin.CoreServices{}).(chi.Routes)
if !ok {
t.Fatal("NewCalcomRouter no longer returns a chi router")
}
for _, tc := range []struct{ method, path string }{
{http.MethodGet, "/api/plugins/calcomblock/reset"},
{http.MethodGet, "/api/plugins/calcomblock/slots"},
{http.MethodGet, "/api/plugins/calcomblock/date-grid"},
{http.MethodPost, "/api/plugins/calcomblock/book"},
{http.MethodPost, "/api/plugins/calcomblock/cancel"},
{http.MethodGet, "/api/plugins/calcomblock/event-types"},
} {
rctx := chi.NewRouteContext()
if !router.Match(rctx, tc.method, tc.path) {
t.Errorf("%s %s does not match any route — must be registered at the absolute plugin path", tc.method, tc.path)
}
}
}
// TestRouterRebuildsClaimsFromTrustedHeaders pins the wasm identity contract:
// context does not cross the ABI, so the ONLY way requireAdmin can see the
// caller is for the router to rebuild claims from the host-stamped
// X-Bn-Verified-* headers (auth.TrustedHeaderMiddleware). 2.0.3 shipped
// without it — every admin endpoint 401'd for real admins. A viewer-role
// header must reach requireAdmin and be rejected 403 (claims seen), never
// 401 (claims lost); no handler runs, so empty CoreServices is safe.
func TestRouterRebuildsClaimsFromTrustedHeaders(t *testing.T) {
router := NewCalcomRouter(plugin.CoreServices{})
req := httptest.NewRequest(http.MethodGet, "/api/plugins/calcomblock/settings", nil)
req.Header.Set(auth.HeaderVerifiedUserID, uuid.NewString())
req.Header.Set(auth.HeaderVerifiedEmail, "viewer@example.test")
req.Header.Set(auth.HeaderVerifiedRole, "viewer")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code == http.StatusUnauthorized {
t.Fatalf("viewer trusted headers yielded 401 — TrustedHeaderMiddleware is not mounted, claims never reach requireAdmin")
}
if rec.Code != http.StatusForbidden {
t.Errorf("viewer trusted headers: expected 403 from requireAdmin, got %d (body %q)", rec.Code, rec.Body.String())
}
}