Repository navigation
Add cached OAuth tokens and connected account renewal to Saloon - #72
Conversation
Define cached token acquisition and optional encrypted OAuth account storage using the existing authentication and cache APIs. Specify expiry, credential identity, scope metadata, shared renewal claims, conditional persistence, and bounded recovery from transient provider failures. Document partition hooks, model customization, transaction and cancellation behavior, database-load expectations, and focused verification. Keep provider-side replay limits explicit and retain application control of credentials and business-request retries.
PHPStan 2.3.2 rejects a correctly narrowed generic value in the existing Data collection cast. The unchanged cast and an isolated reproducer pass on 2.3.1 and fail on 2.3.2. Pin the development requirement so fresh installs and CI use the verified analyzer. Keep the Data implementation and its type contracts intact, and track removal of the pin once a fixed release passes the source and type-fixture analyses. Validation: the full source analysis and type fixtures pass with PHPStan 2.3.1.
Move the existing Retry-After parser into the HTTP namespace so account renewal can honor provider delays without duplicating the rate-limit implementation. Preserve numeric delays, strict HTTP-date parsing, obsolete date formats and leap-second handling. Keep the rate limiter limit and its missing-or-invalid default in the rate-limit caller; the shared parser returns the provider delay without applying that policy. Validation: formatting, full static analysis and the Saloon suite, including the existing rate-limit cases, pass.
Provide cached authentication for independently reacquirable grants and durable account authentication for rotating user grants. Keep provider credentials, request context and OAuth client selection application-owned. CachedTokenAuthenticator retains the actual authenticator and a fixed early renewal deadline. ClientCredentialsGrant builds a hashed identity from the connector, resolved endpoint, credentials, scopes and caller contribution. Concurrent callers share active acquisitions and failures within a worker; completed tokens use the selected cache without a worker-wide token registry. Add an opt-in OAuth accounts migration, encrypted model, owner relation and authenticator. Shared credential caching uses committed invalidation and preserves custom models, query builders and connection identities. A boot-time partition resolver isolates storage and cache identity without retaining the current account or partition in worker state. Deferred invalidation uses the captured key even when application context is restored before commit. Coordinate renewal with existing locks and conditional database claims. Revision and claim checks prevent late responses from overwriting reconnects or successor attempts. Store replacements before use, preserve omitted scopes and refresh tokens, and surface an explicit reconnect-required exception for expired credentials that cannot renew. Retry only classified provider failures within a small bounded budget; local persistence failures never repeat the provider request. Release idle database connections before provider calls and waits, reject renewal inside caller transactions, and preserve cancellation when cleanup or reporting also fails. Extend OAuth metadata and grant factories with nullable reported scopes. Fix request modifiers to accept all declared callable forms. Register cache serialization classes, migration command discovery and testing-state cleanup. Cover serialized token reuse, grant identity, concurrent acquisition and partition isolation, committed cache invalidation, encryption, bounded retries, cancellation, database-pool release and late-result ownership. Exercise lost Redis leases and abandoned database claims with real stores, and include these tests in the existing Redis, Redis Cluster and Valkey jobs. Validation: formatting, full PHPStan source and type-fixture analysis, the complete Saloon suite and the real Redis integration tests pass.
Replace token acquisition on every request with examples for cached client credentials and custom username/password token endpoints. Explain grant identity, store selection, fixed renewal deadlines, custom authenticator serialization and the limits of explicit invalidation. Document opt-in account persistence, encrypted credentials, custom models, Socialite adapters, cache invalidation, partition context and coordinated renewal. Describe reconnect-required errors, bounded retries, transaction restrictions, cancellation and ambiguous remote refresh outcomes. Record the scope metadata contract and factory signature change in the porting guide and package README. Mark the implementation plan verified and ready for a framework PR, retaining the separate integration boundary.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
PR Summary by QodoAdd cached OAuth tokens and connected account renewal to Saloon
AI Description
Diagram
High-Level Assessment
Files changed (40)
|
Code Review by Qodo
1.
|
[High impact] The PR appears safe to merge, with all four previous findings addressed and no new actionable issues found. SummaryAdds reusable OAuth tokens and connected accounts with encrypted credentials and coordinated renewal.
No new actionable issues were found. Runtime tests were not run during this review. Reviews (2) · Last reviewed commit: "Clarify OAuth account transaction and re..." · Reviewed by Greptile |
Pin the Database and Foundation development requirements to PHPStan 2.3.1, matching the root manifest while the 2.3.2 generic loop-narrowing regression remains unresolved. Record all three manifests in the removal task so the ranges are restored together. Keep the package manifest consistency check intact; its failure exposed the incomplete pin. Validation: Composer manifest checks and full source and type-fixture analysis pass.
Validate the current partition before normal saves and deletes, and reject partition changes on existing accounts. Keep application deleting observers active. Normalize write aliases only in cache and renewal identity while preserving the requested connection for transaction ownership. Store computed renewal deadlines and callback expiry instants through the normal numeric date casts so non-UTC applications do not shift them. Check caller transactions before joining a renewal; allow unclaimed, unexpired tokens to remain usable during the early-renewal window without contacting the provider. Preserve the original provider rejection when an account is concurrently deleted. Conditionally persist rotated credentials even when the returned access token has already expired, then reject its use without an immediate retry. Late results still cannot overwrite a reconnect or a replacement claim. Add regressions for partition mutations, model observers, write-alias invalidation, non-UTC dates, transactional waiters, deferred early renewal, concurrent deletion and expired rotated results. Formatting, full source and type analysis, the Saloon suite and shared Redis renewal integration pass.
Document use of unexpired, unclaimed credentials inside caller transactions and rejection before waiting when renewal is required. Explain why an expired access result is not used even though its rotated refresh credentials are retained, including the exception applications receive. Describe partition checks on normal saves and deletes and the prohibition on moving an existing account between partitions. Align the implementation plan with date-cast conventions, write-alias identity, conditional credential persistence and the current PR review state.
|
The latest commits address the account isolation, expiry and renewal findings:
The split-package PHPStan constraints now match the root pin, fixing the manifest consistency failure. Formatting, source and type analysis, the Saloon suite, manifest checks and Redis renewal integration pass locally. CI is running on the updated branch. Each inline finding has a reply explaining the change or why the existing behavior remains intentional. |
Saloon can request OAuth tokens, but applications currently have to build token reuse, account persistence and coordinated refresh themselves. This adds two opt-in paths: cached authentication for grants that can be acquired again, and connected OAuth accounts for user grants with rotating refresh tokens.
Cached access tokens
Client-credentials connectors can return a reusable authenticator:
The cache key distinguishes the connector, resolved token endpoint, client credentials and requested scopes. Credentials are hashed before they enter the key. An optional key contribution covers provider-specific inputs such as an audience. Fresh connector instances can reuse the same cached token, while concurrent callers in one worker share an active acquisition and its result or failure.
CachedTokenAuthenticatoralso accepts a resolver for other token endpoints. It stores the actual authenticator, preserving custom authentication behavior, and calculates a fixed renewal deadline when a token is acquired. There is no worker-wide registry of completed tokens. Callingforget()clears that instance and its shared cache entry; it does not revoke the provider token or clear other live instances.Connected OAuth accounts
Applications that need durable user grants can generate the
oauth_accountsmigration and addHasOAuthAccountsto an owning model. Access and refresh tokens use encrypted casts and are hidden from model serialization. The migration is not installed automatically, and credential caching is disabled by default.Account authentication reads current credentials rather than keeping a private token copy. Optional shared caching avoids database reads on ordinary calls. Saves and deletes invalidate committed entries, including when application context is restored before a transaction commits. Custom account models retain their connection, table, scopes and query builder.
A boot-time partition resolver supports applications that divide accounts by tenant or another ownership boundary. Account identity includes the partition, and authentication checks the active context before reading or using credentials. The application still owns its model's query scoping.
Renewal and failure handling
Renewal combines existing cache locks with a conditional database claim. The replacement is stored before it is used. Credential revisions and claim ownership prevent a late response from overwriting a reconnect or a newer attempt. Idle database connections are released before provider calls and waits, and renewal is rejected inside a caller transaction on the account connection.
Transient provider failures receive a small, bounded retry budget. Each attempt rereads current credentials. Longer
Retry-Afterdelays are left to the caller, local persistence failures do not trigger another provider call, and cancellation preserves the original exception even if cleanup fails. An expired token without a refresh token throwsOAuthAccountReconnectRequiredException; rejected OAuth grants retain the provider's exception and response.These measures do not promise exactly one remote request after a crash or an ambiguous network failure. Abandoned attempts can be recovered, and a provider that rejects reuse of a rotated refresh token can still require reconnection.
Other changes
OAuthAuthenticator,AccessTokenAuthenticatorand both grant factories. Custom implementations must providegetScopes(): ?arrayand accept the factory's new optional scopes argument.nullpreserves unreported scope metadata; an explicit empty array replaces it.Retry-Afterparser between OAuth renewal and rate limiting without changing rate-limit policy.Validation
Formatting, full source and type-fixture analysis, the Saloon suite and Redis integration pass. Coverage includes serialized token reuse, grant identity, concurrent partitions, committed invalidation, cancellation, connection-pool release, reconnects and late refresh results. The Redis tests exercise lost cache locks and abandoned database claims and are included in the Redis, Redis Cluster and Valkey workflows.
Note
Add cached OAuth tokens and connected account renewal to Saloon
CachedTokenAuthenticatorfor client-credentials flows: tokens are cached locally and in a shared cache store, keyed by connector, endpoint, client credentials, scopes, and an optional caller key. Concurrent workers share one token acquisition through the newTokenAcquisitioncoordinator.OAuthAccountmodel with encrypted credentials, theHasOAuthAccountstrait, themake:oauth-accounts-tablemigration command, andOAuthAccountAuthenticator. TheOAuthAccountsservice handles token retrieval, lock-coordinated renewal with credential revisions, refresh-claim fencing, bounded provider retries, and optional Redis-backed credential caching (disabled by default).getScopes()on theOAuthAuthenticatorcontract), and grant factories receive an optional scopes argument. Request modifiers now accept any callable form, including array callables. Retry-After parsing moved fromHasRateLimitsto a sharedRetryAfterclass with no behavior change.OAuthAuthenticatorimplementations must add agetScopes()method, and grant-specific authenticator factories receive a new optional scopes parameter. Retry-After parsing moved out ofHasRateLimits.resolveRateLimitCooldown, which retains the 60-second fallback and maximum-cooldown check.Macroscope summarized f4b8272.