The legacy v5 callback path /api/oidc/callback carries no provider ID,
so extractOIDCProviderID hard-maps it to the legacy-oidc sentinel.
Providers created in the v6 UI get a UUID ID, and when their IdP is
still registered with the legacy redirect URI the callback missed the
provider lookup and returned provider_not_found with no log, breaking
SSO login (authentik and similar).
Peek the pending authorization state across providers to recover the
provider that actually initiated the flow, re-validate it, and proceed.
State stays single-use (peek never consumes), unknown/disabled/expired/
replayed all still fail closed, and the 4-part provider_mismatch guard
is unchanged. The fail-closed path now logs instead of failing silently.
Refs #1533, #1535
When offline_access scope is configured, Pulse now stores and uses
OIDC refresh tokens to automatically extend sessions. Sessions remain
valid as long as the IdP allows token refresh (typically 30-90 days).
Changes:
- Store OIDC tokens (refresh token, expiry, issuer) alongside sessions
- Automatically refresh tokens when access token nears expiry
- Invalidate session if IdP revokes access (forces re-login)
- Add background token refresh with concurrency protection
- Persist OIDC tokens across restarts
Related to #854
Fixed three P1 goroutine/memory leaks that prevent proper resource cleanup:
1. Recovery Tokens goroutine leak
- Cleanup routine runs forever without stop mechanism
- Added stopCleanup channel and Stop() method
- Cleanup loop now uses select with stopCleanup case
2. Rate Limiter goroutine leak
- Cleanup routine runs forever without stop mechanism
- Added stopCleanup channel and Stop() method
- Changed from 'for range ticker.C' to select with stopCleanup case
3. OIDC Service memory leak (DoS vector)
- Abandoned OIDC flows never cleaned up
- State entries accumulate unboundedly
- Added cleanup routine with 5-minute ticker
- Periodically removes expired state entries (10min TTL)
- Added Stop() method for proper shutdown
All three follow consistent pattern:
- Add stopCleanup chan struct{} field
- Initialize in constructor
- Use select with ticker and stopCleanup cases
- Close channel in Stop() method to signal goroutine exit
Impact:
- Prevents goroutine leaks during service restarts/reloads
- Prevents memory exhaustion from abandoned OIDC login attempts
- Enables proper cleanup in tests and graceful shutdown