feat(auth): every accepted request names the path that granted it
Closes #60. Closes #58 (fixed in mcp-chassis v0.7.0, consumed here). Three auth paths reach this server and none of them said so on success. PassthroughMiddleware logged nothing at all, success or failure. While verifying #59 end to end there was no way to confirm from the logs whether calls were using the new PAT pass-through — the caller's own Gitea PAT, the caller's own identity — or falling through to the shared JWT/static path. The only available reasoning was inference from which failures were absent. INFO mcp auth accepted auth_path=passthrough caller=alice token_fp=... INFO mcp auth accepted auth_path=jwt caller=mathiasbq token_fp=... INFO mcp auth accepted auth_path=static token_fp=... The first line matters more than the other two. Pass-through is the path where a request acts as a SPECIFIC Gitea user; an audit trail that cannot say which identity performed a write is not an audit trail. chassisauth.Fingerprint, not a local equivalent. The chassis emits token_fp on its own accepted and rejected lines, and two different fingerprints for the same token cannot be correlated — which is the only reason the field exists. Exported from the chassis in v0.7.1 for exactly this. A token that is not a valid PAT still falls through SILENTLY. The chassis owns the verdict, and logging here as well would double-count every static-token request, since all of them reach this middleware first. Asserted, because "logs nothing on the failure path" is indistinguishable from "was never called". Also lands #58 by consuming mcp-chassis v0.7.0: a JWT presented to a server with no JWT auth configured now reports jwt_auth_not_configured rather than static_token_mismatch. That log line is what sent a debugging session into re-running the OAuth flow while the credentials were valid the whole time. Verified: task check EXIT=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,12 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
chassisauth "git.d-ma.be/mathias/mcp-chassis/auth"
|
||||
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||
)
|
||||
|
||||
@@ -25,11 +28,34 @@ func PassthroughMiddleware(validator TokenValidator, onValid, fallback http.Hand
|
||||
token, hasBearer := strings.CutPrefix(authz, "Bearer ")
|
||||
if hasBearer && token != "" {
|
||||
if login, ok := validator.ValidateToken(r.Context(), token); ok {
|
||||
// gitea-mcp#60. This middleware logged nothing at all, so there
|
||||
// was no way to tell a PAT pass-through request from one that
|
||||
// fell through to the shared JWT/static path — only inference
|
||||
// from which failures were absent.
|
||||
//
|
||||
// It matters more here than on the other two paths: this is
|
||||
// where a request acts as a SPECIFIC Gitea user. An audit trail
|
||||
// that cannot say which identity performed a write is not an
|
||||
// audit trail.
|
||||
//
|
||||
// chassisauth.Fingerprint, not a local equivalent: the chassis
|
||||
// emits the same field on its own accepted/rejected lines, and
|
||||
// two different fingerprints for one token cannot be correlated.
|
||||
slog.Info("mcp auth accepted",
|
||||
"auth_path", "passthrough",
|
||||
"caller", login,
|
||||
"token_fp", chassisauth.Fingerprint(token),
|
||||
"path", r.URL.Path,
|
||||
)
|
||||
ctx := withCaller(r.Context(), login)
|
||||
ctx = gitea.WithToken(ctx, token)
|
||||
onValid.ServeHTTP(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
// A token that is not a valid PAT falls through SILENTLY. The
|
||||
// chassis owns the verdict; logging here too would double-count
|
||||
// every static-token request, since all of them reach this
|
||||
// middleware first.
|
||||
}
|
||||
fallback.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user