feat(08-03): implement exact authorize validation and pending creation in wristband

- Server.Authorize ports OAuthAuthorizeController::authorize's exact
  validation order: usable client, exact redirect, response_type=code,
  code_challenge_method=S256, challenge length, scope parsing/ceiling
  truncation, resource check, then opaque pending-request creation
- Unknown client/unregistered redirect are local text/plain 400s with no
  Location; every later failure is an ordered RFC3986 redirect with
  error/error_description/iss[/state], built via a dedicated encoder
  (never url.Values.Encode, which sorts keys and space-encodes as '+')
- Options gains Resource and PendingRequestTTL (both PHP-parity defaults)
  so authorize's resource check and 600s pending expiry are configurable
This commit is contained in:
Jakub Zych
2026-09-23 20:08:37 +02:00
parent 787e612ab3
commit 90752beb87
3 changed files with 875 additions and 6 deletions

View File

@@ -57,6 +57,17 @@ type Options struct {
// RegisterMaxBodyBytes bounds the RFC 7591 registration request body
// before JSON decoding (D-21, T-08-DCR-FLOOD). PHP default: 65536 (64 KiB).
RegisterMaxBodyBytes int64
// Resource is the expected RFC 8707 resource indicator value authorize
// checks an optional resource query parameter against (PHP
// config('fonoteka.mcp.resource'), D-03). PHP default:
// "https://mcp.plytarium.com/mcp".
Resource string
// PendingRequestTTL is how long a pre-consent pending authorization row
// created by authorize stays valid (PHP
// OAuthCodeManager::PENDING_TTL_SECONDS, D-03). PHP default: 600s.
PendingRequestTTL time.Duration
}
// DefaultOptions returns PHP-parity defaults for every metadata option
@@ -70,6 +81,8 @@ func DefaultOptions() Options {
DCRClientCap: 200,
DCRUnconsentedSweepAge: 24 * time.Hour,
RegisterMaxBodyBytes: 65536,
Resource: "https://mcp.plytarium.com/mcp",
PendingRequestTTL: 600 * time.Second,
}
}