feat(08-05): add wristband consent issue/deny operations

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.
This commit is contained in:
Jakub Zych
2026-09-23 20:59:24 +02:00
parent a459f74897
commit a1fa9c6f44
5 changed files with 341 additions and 2 deletions

View File

@@ -90,6 +90,11 @@ type ClientStore interface {
// RegistrationIP), still-unconsented clients created before olderThan.
// Artisan-issued clients (nil RegistrationIP) are never swept (D-19).
SweepUnconsented(ctx context.Context, olderThan time.Time) error
// MarkConsented stamps ConsentedAt once for clientID unless it is
// already set (idempotent; PHP OAuthConsentController::store's "if
// ($client->consented_at === null)" guard, 08-05-PLAN.md D-08). A
// consented client is never later swept by SweepUnconsented.
MarkConsented(ctx context.Context, clientID string) error
}
// AuthCodeStore persists pending/issued authorization rows. ByCodeHashForUpdate
@@ -100,7 +105,12 @@ type AuthCodeStore interface {
CreatePending(ctx context.Context, rec *AuthCodeRecord) error
ByRequestID(ctx context.Context, requestID string) (*AuthCodeRecord, error)
ByCodeHashForUpdate(ctx context.Context, codeHash string) (*AuthCodeRecord, error)
MarkIssued(ctx context.Context, id uint, codeHash string, userID uint) error
// MarkIssued turns a pending row into an issued authorization code
// (PHP OAuthCodeManager::issueCode, 08-05-PLAN.md D-08): it nulls
// RequestID, sets CodeHash/UserID, overwrites Scopes/CollectionIDs
// with the consent-granted values (never the originally requested
// ones), and extends ExpiresAt to the fresh code TTL.
MarkIssued(ctx context.Context, id uint, codeHash string, userID uint, scopes []string, collectionIDs []uint, expiresAt time.Time) error
MarkUsed(ctx context.Context, id uint) error
}