While verifying #59 (v0.12.0/v0.12.1) end to end, I could not confirm from server logs which auth path a given /mcp request actually took. chassisauth.BearerMiddleware only logs on rejection (mcp auth rejected reason=... status=401); PassthroughMiddleware logs nothing at all, success or failure.
Concretely: after re-authing the claude.ai connector, I wanted to confirm whether the resulting calls were using the new PAT pass-through (caller's own Gitea PAT, own identity) or falling through to the existing JWT/shared-token path. The only way to reason about it was inference (a JWT would fail ValidateToken against Gitea and fall through) — there was no direct signal in the logs either way.
Proposed fix
Add one structured log line per successful /mcp auth, distinguishing the path taken, e.g.:
INFO mcp auth accepted path=passthrough caller=alice token_fp=...
INFO mcp auth accepted path=jwt caller=mathiasbq token_fp=...
INFO mcp auth accepted path=static token_fp=...
token_fp should be a fingerprint/hash, never the raw token (matches the existing rejection log's token_fp convention).
Acceptance criteria
A test asserting PassthroughMiddleware logs path=passthrough with the resolved caller on a successful PAT validation.
A test (or manual verification) confirming the existing JWT/static success paths in mcp-chassis's BearerMiddleware emit an equivalent path=jwt/path=static accepted line — may require a small mcp-chassis change if that library doesn't expose a success hook today.
Refs
gitea-mcp#59 (PAT pass-through), v0.12.0/v0.12.1
gitea-mcp#57/#58 (related: existing rejection-path logging had its own correctness bugs)
## Problem
While verifying #59 (v0.12.0/v0.12.1) end to end, I could not confirm from server logs which auth path a given `/mcp` request actually took. `chassisauth.BearerMiddleware` only logs on *rejection* (`mcp auth rejected reason=... status=401`); `PassthroughMiddleware` logs nothing at all, success or failure.
Concretely: after re-authing the claude.ai connector, I wanted to confirm whether the resulting calls were using the new PAT pass-through (caller's own Gitea PAT, own identity) or falling through to the existing JWT/shared-token path. The only way to reason about it was inference (a JWT would fail `ValidateToken` against Gitea and fall through) — there was no direct signal in the logs either way.
## Proposed fix
Add one structured log line per successful `/mcp` auth, distinguishing the path taken, e.g.:
```
INFO mcp auth accepted path=passthrough caller=alice token_fp=...
INFO mcp auth accepted path=jwt caller=mathiasbq token_fp=...
INFO mcp auth accepted path=static token_fp=...
```
`token_fp` should be a fingerprint/hash, never the raw token (matches the existing rejection log's `token_fp` convention).
## Acceptance criteria
- A test asserting `PassthroughMiddleware` logs `path=passthrough` with the resolved caller on a successful PAT validation.
- A test (or manual verification) confirming the existing JWT/static success paths in `mcp-chassis`'s `BearerMiddleware` emit an equivalent `path=jwt`/`path=static` accepted line — may require a small `mcp-chassis` change if that library doesn't expose a success hook today.
## Refs
- gitea-mcp#59 (PAT pass-through), v0.12.0/v0.12.1
- gitea-mcp#57/#58 (related: existing rejection-path logging had its own correctness bugs)
All three paths now announce themselves on success:
INFO mcp auth accepted auth_path=passthrough caller=alice token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=jwt caller=mathiasbq token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=static token_fp=... remote=... path=/mcp
token_fp is a truncated SHA-256, never the raw token — the convention this issue asked to match.
Both acceptance criteria
A test asserting PassthroughMiddleware logs path=passthrough with the resolved caller on a successful PAT validation.
internal/auth/passthrough_logging_test.go. It also asserts the PAT appears in no field of the line, not just that it is absent from token_fp.
A test (or manual verification) confirming the existing JWT/static success paths in mcp-chassis's BearerMiddleware emit an equivalent line — may require a small mcp-chassis change if that library doesn't expose a success hook today.
It did require one. mcp-chassis v0.7.0 adds accept() alongside the existing deny(), tested there in auth/bearer_logging_test.go. No hook needed in the end — the middleware owns both verdicts, so it logs both.
Two decisions worth recording
The field is auth_path, not path.path was already taken by the request path in every existing line. Reusing it would have made the accepted and rejected lines mean different things by the same key, which is worse than a slightly longer name.
Fingerprint is exported from the chassis rather than reimplemented here. Pass-through runs ahead of BearerMiddleware, so its line comes from a different package. Two different fingerprints for the same token cannot be correlated — which is the only reason the field exists. mcp-chassis v0.7.1 exports it.
Pass-through still logs nothing on failure, deliberately. A token that is not a valid PAT falls through to the chassis, which owns that verdict. Logging here too would double-count every static-token request, since all of them reach this middleware first. Asserted with its own test, because "logs nothing on the failure path" is otherwise indistinguishable from "was never called".
Why this one was worth more than it looks
The other two paths tell you how a request authenticated. Pass-through tells you who — it is the path where a request acts as a specific Gitea user against a real forge. An audit trail that cannot say which identity performed a write is not an audit trail, and until now that line did not exist at all.
## Done. `v0.15.0` here, `mcp-chassis v0.7.0`/`v0.7.1` underneath.
All three paths now announce themselves on success:
```
INFO mcp auth accepted auth_path=passthrough caller=alice token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=jwt caller=mathiasbq token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=static token_fp=... remote=... path=/mcp
```
`token_fp` is a truncated SHA-256, never the raw token — the convention this issue asked to match.
### Both acceptance criteria
> A test asserting `PassthroughMiddleware` logs `path=passthrough` with the resolved caller on a successful PAT validation.
`internal/auth/passthrough_logging_test.go`. It also asserts the PAT appears in **no** field of the line, not just that it is absent from `token_fp`.
> A test (or manual verification) confirming the existing JWT/static success paths in `mcp-chassis`'s `BearerMiddleware` emit an equivalent line — **may require a small `mcp-chassis` change if that library doesn't expose a success hook today**.
It did require one. `mcp-chassis v0.7.0` adds `accept()` alongside the existing `deny()`, tested there in `auth/bearer_logging_test.go`. No hook needed in the end — the middleware owns both verdicts, so it logs both.
### Two decisions worth recording
**The field is `auth_path`, not `path`.** `path` was already taken by the request path in every existing line. Reusing it would have made the accepted and rejected lines mean different things by the same key, which is worse than a slightly longer name.
**`Fingerprint` is exported from the chassis rather than reimplemented here.** Pass-through runs ahead of `BearerMiddleware`, so its line comes from a different package. Two different fingerprints for the same token cannot be correlated — which is the only reason the field exists. `mcp-chassis v0.7.1` exports it.
**Pass-through still logs nothing on failure, deliberately.** A token that is not a valid PAT falls through to the chassis, which owns that verdict. Logging here too would double-count every static-token request, since all of them reach this middleware first. Asserted with its own test, because "logs nothing on the failure path" is otherwise indistinguishable from "was never called".
### Why this one was worth more than it looks
The other two paths tell you *how* a request authenticated. Pass-through tells you *who* — it is the path where a request acts as a specific Gitea user against a real forge. An audit trail that cannot say which identity performed a write is not an audit trail, and until now that line did not exist at all.
Closing.
---
_Created via git-mcp on behalf of @mathias_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
While verifying #59 (v0.12.0/v0.12.1) end to end, I could not confirm from server logs which auth path a given
/mcprequest actually took.chassisauth.BearerMiddlewareonly logs on rejection (mcp auth rejected reason=... status=401);PassthroughMiddlewarelogs nothing at all, success or failure.Concretely: after re-authing the claude.ai connector, I wanted to confirm whether the resulting calls were using the new PAT pass-through (caller's own Gitea PAT, own identity) or falling through to the existing JWT/shared-token path. The only way to reason about it was inference (a JWT would fail
ValidateTokenagainst Gitea and fall through) — there was no direct signal in the logs either way.Proposed fix
Add one structured log line per successful
/mcpauth, distinguishing the path taken, e.g.:token_fpshould be a fingerprint/hash, never the raw token (matches the existing rejection log'stoken_fpconvention).Acceptance criteria
PassthroughMiddlewarelogspath=passthroughwith the resolved caller on a successful PAT validation.mcp-chassis'sBearerMiddlewareemit an equivalentpath=jwt/path=staticaccepted line — may require a smallmcp-chassischange if that library doesn't expose a success hook today.Refs
Done.
v0.15.0here,mcp-chassis v0.7.0/v0.7.1underneath.All three paths now announce themselves on success:
token_fpis a truncated SHA-256, never the raw token — the convention this issue asked to match.Both acceptance criteria
internal/auth/passthrough_logging_test.go. It also asserts the PAT appears in no field of the line, not just that it is absent fromtoken_fp.It did require one.
mcp-chassis v0.7.0addsaccept()alongside the existingdeny(), tested there inauth/bearer_logging_test.go. No hook needed in the end — the middleware owns both verdicts, so it logs both.Two decisions worth recording
The field is
auth_path, notpath.pathwas already taken by the request path in every existing line. Reusing it would have made the accepted and rejected lines mean different things by the same key, which is worse than a slightly longer name.Fingerprintis exported from the chassis rather than reimplemented here. Pass-through runs ahead ofBearerMiddleware, so its line comes from a different package. Two different fingerprints for the same token cannot be correlated — which is the only reason the field exists.mcp-chassis v0.7.1exports it.Pass-through still logs nothing on failure, deliberately. A token that is not a valid PAT falls through to the chassis, which owns that verdict. Logging here too would double-count every static-token request, since all of them reach this middleware first. Asserted with its own test, because "logs nothing on the failure path" is otherwise indistinguishable from "was never called".
Why this one was worth more than it looks
The other two paths tell you how a request authenticated. Pass-through tells you who — it is the path where a request acts as a specific Gitea user against a real forge. An audit trail that cannot say which identity performed a write is not an audit trail, and until now that line did not exist at all.
Closing.
Created via git-mcp on behalf of @mathias