From fdb4a0eaa590081d68c1dc9ee078e5134021b723 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:33:42 +0700 Subject: [PATCH] fix(auth): preserve shared signed-session challenge identity --- go_backend/extension_signed_session.go | 28 ++++------- go_backend/extension_signed_session_test.go | 52 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/go_backend/extension_signed_session.go b/go_backend/extension_signed_session.go index 63c30d9a..2f41672d 100644 --- a/go_backend/extension_signed_session.go +++ b/go_backend/extension_signed_session.go @@ -180,15 +180,17 @@ func (c *signedSessionCoordinator) clearChallenge() { c.pendingExtensionIDs = nil } -func (c *signedSessionCoordinator) rememberChallenge(extensionID, authURL, callbackURL, callbackState string) { +func (c *signedSessionCoordinator) rememberChallenge(request *PendingAuthRequest) { if c.pendingExtensionIDs == nil { c.pendingExtensionIDs = make(map[string]struct{}) } - c.authURL = authURL - c.callbackURL = callbackURL - c.callbackState = callbackState - c.challengeCreatedAt = time.Now() - c.pendingExtensionIDs[extensionID] = struct{}{} + c.authURL = request.AuthURL + c.callbackURL = request.CallbackURL + c.callbackState = request.State + // Reusers must register the exact same challenge identity, including its + // original timestamp. A fresh time both breaks nonce sharing and extends TTL. + c.challengeCreatedAt = request.CreatedAt + c.pendingExtensionIDs[request.ExtensionID] = struct{}{} } func (c *signedSessionCoordinator) activeChallenge() bool { @@ -1285,12 +1287,7 @@ func (r *extensionRuntime) startSignedSessionVerificationLocked( if pending := GetPendingAuthRequest(r.extensionID); pending != nil { if time.Since(pending.CreatedAt) < pendingAuthRequestTTL && strings.TrimSpace(pending.AuthURL) != "" { - coordinator.rememberChallenge( - r.extensionID, - pending.AuthURL, - pending.CallbackURL, - pending.State, - ) + coordinator.rememberChallenge(pending) return pending.AuthURL, nil } ClearPendingAuthRequest(r.extensionID) @@ -1367,12 +1364,7 @@ func (r *extensionRuntime) startSignedSessionVerificationLocked( if registerErr := registerPendingAuthRequest(request); registerErr != nil { finalErr = registerErr } else { - coordinator.rememberChallenge( - r.extensionID, - bootstrap.AuthURL, - bootstrap.CallbackURL, - bootstrap.CallbackState, - ) + coordinator.rememberChallenge(request) authURL = bootstrap.AuthURL } } diff --git a/go_backend/extension_signed_session_test.go b/go_backend/extension_signed_session_test.go index 1524d734..294b6e46 100644 --- a/go_backend/extension_signed_session_test.go +++ b/go_backend/extension_signed_session_test.go @@ -437,6 +437,58 @@ func TestParallelSignedSessionPreflightSharesOneBootstrap(t *testing.T) { if got := calls.Load(); got != 1 { t.Fatalf("parallel preflight bootstrap calls = %d, want 1", got) } + pendingA := GetPendingAuthRequest("provider-a") + pendingB := GetPendingAuthRequest("provider-b") + if pendingA == nil || pendingB == nil || pendingA.State != pendingB.State || + !pendingA.CreatedAt.Equal(pendingB.CreatedAt) { + t.Fatal("shared bootstrap must preserve the same nonce and creation time") + } + if _, err := ConsumeExtensionCallbackState(pendingA.State); err != nil { + t.Fatalf("shared callback cannot be consumed: %v", err) + } + if GetPendingAuthRequest("provider-a") != nil || GetPendingAuthRequest("provider-b") != nil { + t.Fatal("consuming a shared callback must clear every alias") + } + if _, err := ConsumeExtensionCallbackState(pendingA.State); err == nil { + t.Fatal("shared callback replay was accepted") + } +} + +func TestRememberSignedSessionChallengePreservesOriginalLifetime(t *testing.T) { + request := &PendingAuthRequest{ + ExtensionID: "remember-original", AuthURL: "https://auth.example.com/challenge", + CallbackURL: "spotiflac://callback", State: "remember-original-state", + CreatedAt: time.Now().Add(-2 * time.Minute), + } + if err := registerPendingAuthRequest(request); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + ClearPendingAuthRequest("remember-original") + ClearPendingAuthRequest("remember-shared") + }) + coordinator := &signedSessionCoordinator{} + coordinator.rememberChallenge(request) + if !coordinator.challengeCreatedAt.Equal(request.CreatedAt) { + t.Fatal("remembering an existing challenge reset its TTL") + } + runtime := &extensionRuntime{extensionID: "remember-shared"} + coordinator.mu.Lock() + _, err := runtime.startSignedSessionVerificationLocked(SignedSessionConfig{}, coordinator, "test") + coordinator.mu.Unlock() + if err != nil { + t.Fatalf("reusing a remembered challenge: %v", err) + } + shared := GetPendingAuthRequest("remember-shared") + if shared == nil || !shared.CreatedAt.Equal(request.CreatedAt) { + t.Fatal("shared request lost the original challenge timestamp") + } + expired := *request + expired.CreatedAt = time.Now().Add(-pendingAuthRequestTTL - time.Second) + coordinator.rememberChallenge(&expired) + if coordinator.activeChallenge() { + t.Fatal("remembering an expired challenge made it active again") + } } func TestParallelSignedSessionPreflightSharesBootstrapFailure(t *testing.T) {