fix(19.7): RA-IN-02 stream bearer re-checks carry the client address

The stateful GET stream and the stateless subscriptions/listen stream
capture the client address when they open and resolve every periodic
re-check with towel.WithClientIP, as the bearer middleware does, so a
failing re-check (a revoked admin token, for example) is logged with the
remote address. TestStreamRecheckClientIP records the address the verifier
sees on every verification of both streams.
This commit is contained in:
2026-10-23 20:19:22 +02:00
parent 58408aaa83
commit afc7510d1b
4 changed files with 69 additions and 4 deletions

View File

@@ -15,6 +15,7 @@ import (
"sync/atomic"
"time"
"git.golem15.com/golem15/summercms/modules/towel"
"github.com/modelcontextprotocol/go-sdk/auth"
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
"github.com/modelcontextprotocol/go-sdk/mcp"
@@ -425,12 +426,15 @@ func (sr *sessionRouter) persist(ctx context.Context, ls *localSession) {
// serveStream serves the standalone GET stream and re-verifies its bearer
// every recheck interval on a goroutine that holds the raw bearer only for
// the stream's lifetime; a bearer that no longer resolves to the session's
// binding ends the stream (Pitfall 10). The server's base context ends
// binding ends the stream (Pitfall 10). The re-check resolves with the
// stream's client address on the context, as the bearer middleware does, so
// a failed re-check is logged with it. The server's base context ends
// every stream at shutdown.
func (sr *sessionRouter) serveStream(w http.ResponseWriter, r *http.Request, ls *localSession) {
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
raw := bearerOf(r)
ip := sr.p.clientIP(r)
go func() {
ticker := time.NewTicker(sr.p.recheck)
defer ticker.Stop()
@@ -439,7 +443,7 @@ func (sr *sessionRouter) serveStream(w http.ResponseWriter, r *http.Request, ls
case <-ctx.Done():
return
case <-ticker.C:
id, err := sr.p.identities.resolve(ctx, raw)
id, err := sr.p.identities.resolve(towel.WithClientIP(ctx, ip), raw)
if err != nil || id.Binding != ls.binding {
logSession(logger(), "stream_closed", ls.id, ls.binding)
cancel()