feat(06-01): grow router verbs, factories, and guard registry

- Add Post/Put/Patch/Delete on pact.Router and surf Router/Group
- Resolve name:param middleware via RegisterMiddlewareFactory
- Add bouncer.Registry with Guard, CredentialGuard, UnauthorizedWriter
- Re-express jwt as NewJWTGuard without changing Middleware bodies
This commit is contained in:
Jakub Zych
2026-09-19 18:59:12 +02:00
parent ecab09c37a
commit d376b1be2d
9 changed files with 678 additions and 14 deletions

25
bouncer/guard.go Normal file
View File

@@ -0,0 +1,25 @@
package bouncer
import "net/http"
// Guard resolves the caller's Principal for r, or an error describing why not.
type Guard interface {
Authenticate(r *http.Request) (*Principal, error)
}
// CredentialGuard resolves the Principal AND its underlying credential (e.g.
// *models.ApiToken) in one pass -- a DB-backed guard must never verify twice
// per request (RESEARCH.md Pitfall 5: last_used_at must stamp once).
type CredentialGuard interface {
AuthenticateCredential(r *http.Request) (*Principal, any, error)
}
// UnauthorizedWriter lets a guard write its own failure response. jwtGuard
// implements this (reusing write401's {"error":true,"message":...} shape).
// TokenGuard does NOT implement it: PHP's TokenScope, not ApiTokenGuard, owns
// the {"error":"Invalid token"} 401 body (D-08) -- Registry.Middleware must
// pass an unauthenticated request through untouched when a guard has no
// UnauthorizedWriter, leaving denial to downstream middleware.
type UnauthorizedWriter interface {
WriteUnauthorized(w http.ResponseWriter, err error)
}