Server.PendingRequest/IssueCode/DenyPending port PHP
OAuthConsentController::pendingFor/OAuthCodeManager::issueCode as
app-agnostic protocol operations (08-CONTEXT.md D-08): every missing,
foreign-owner, used, expired, or already-issued pending row collapses to
the identical ErrPendingNotFound (T-08-CROSS-USER/T-08-REQUEST-LEAK).
IssueCode trusts the caller's already-computed granted scopes/collection
ids and returns the ordered redirect_to URL built through the existing
RFC 3986 encoder.
AuthCodeStore.MarkIssued gains scopes/collectionIDs/expiresAt parameters
(PHP's issueCode overwrites all three, not just code_hash/user_id) and
ClientStore gains MarkConsented, both required for D-08's consented_at
stamping and server-derived grant persistence. Options gains CodeTTL
(600s PHP-parity default) following the established Options-extension
pattern.
- Server.Token: JSON rejection before ParseForm, body-over-query precedence,
Basic-over-form client auth, exact invalid_request/unsupported_grant_type/
invalid_client/invalid_grant bodies, Cache-Control/Pragma on success only
- authenticateClient: public/confidential dispatch, constant-time secret
compare (T-08-SECRET-TIMING)
- exchangeAuthorizationCode: single WithinTx lock/consume/mint/refresh-create
covering code/client/redirect/resource/PKCE binding and single-use replay
(T-08-CODE-REPLAY), sequential and concurrent proofs
- rotateRefreshToken: grant_type=refresh_token dispatches per PHP validity
but is a deliberate invalid_grant placeholder; full rotation is 08-06
- full token_test.go behavior matrix appended alongside the RED anchor
- 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
- Server.Authorize stub returns 501
- TestPhase8RedAuthorize drives a full valid S256 request and asserts the
exact 302 /connect success contract; fails with PHASE8_RED:authorize
against the stub, verified fail-closed via check-phase8-red.sh
- Register validates redirect_uris/grant_types/response_types/auth-method
in PHP's exact order, strips control characters and caps client_name at
255 runes, and generates client_id/secret via crypto/rand base64url
- confidential clients return the raw secret once; only its sha256 hex
persists (constant-time-comparable fixed transform)
- sweep-unconsented, the atomic cap check and the create all run inside one
wristband.Backend.WithinTx transaction (T-08-DCR-FLOOD)
- 64 KiB body bound via http.MaxBytesReader collapses to the endpoint's
native invalid_client_metadata body, matching D-21
- TestPhase8RedRegistration asserts the exact public-client DCR success
contract and fails while Server.Register is a 501 stub
- adds the Backend/Tx transaction-scoped store bundle (ClientStore,
AuthCodeStore, RefreshTokenStore, AccessTokenIssuer) and wristband's own
in-memory implementation for framework-level tests (D-07)
- adds crypto.go's fixed-transform helpers (random base64url, sha256 hex,
constant-time compare, S256) and Options/Server seams for the DCR
lifetimes, cap, sweep age and 64 KiB body bound (D-03/D-21)
- Server.Metadata now writes the unwrapped 11-field PHP-parity document
through a local no-envelope, no-trailing-newline JSON writer with the
PHP Cache-Control: no-cache, private header (D-06); response types,
grant types and PKCE method stay fixed protocol constants
- TestPhase8RedMetadata now passes; TestMetadataExactBytes and
TestMetadataUsesConfiguredOptions cover byte-exact output and the four
configurable Options fields
- wristband.Server.Metadata is a compiling 501 stub; TestPhase8RedMetadata
asserts the exact unwrapped PHP metadata document, headers and status and
fails with the PHASE8_RED:metadata sentinel (D-06)
- scripts/check-phase8-red.sh implements the shared go/shell RED contract
for the rest of Phase 8: exact selected test/package failure plus sentinel,
rejecting unrelated fail actions, compile/setup failures, panics,
malformed JSON, missing/duplicate sentinels and zero selection (D-04/D-18)
Avatar bucket publish closed the last UAT blocker. Phase 7 is 8/8
verified. Next is discuss Phase 8; do not auto-advance.
Co-authored-by: Cursor <cursoragent@cursor.com>
Assembled avatar POST is 200. UAT is 12/12. AUTH-02 through AUTH-04
and I18N-02 are marked complete. Do not auto-advance.
Co-authored-by: Cursor <cursoragent@cursor.com>
All eight Phase 7 plans have summaries. Avatar bucket publish is the
UAT gap close; phase verification still has to run.
Co-authored-by: Cursor <cursoragent@cursor.com>
Serve and Handler now publish the uploads bucket; assembled avatar
POST is 200. Record the gap-closure outcome.
Co-authored-by: Cursor <cursoragent@cursor.com>
Avatar upload 500s when serve never opens storage.uploads.bucket_url.
Wire OpenBucket + Publish on the CLI boot path so the user plugin can store files.
Co-authored-by: Cursor <cursoragent@cursor.com>
Record the PHP-does-blacklist finding, the accepted Go 401 after logout,
and the 22-ported corpus so later phases do not revive the harness artifact.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replay of the user-api corpus needs lagoon::validate.* catalogs and
underscore-to-space attribute names so Go 422 bodies match Winter.
Co-authored-by: Cursor <cursoragent@cursor.com>
The six plans are in, and three of the four success criteria hold. AUTH-01 stays blocked because the 15 user API routes are still pending against the recorded PHP bodies.
Co-authored-by: Cursor <cursoragent@cursor.com>
The validation contract is signed off and the phase plan count is 6/6. Requirement checkboxes stay open while the user-api routes are still pending.
Co-authored-by: Cursor <cursoragent@cursor.com>
A minted token refreshes once, then the old jti is blacklisted. Memory and Postgres blacklists take concurrent Add and IsBlacklisted calls.
Co-authored-by: Cursor <cursoragent@cursor.com>
Record that the 15 user routes stay pending until Go matches the PHP bodies, including the HTML 500 on a bad activation code and the still-valid token after logout.
Co-authored-by: Cursor <cursoragent@cursor.com>