Skip to content

Key OAuth2 sessions by cookie, call providers asynchronously and deprecate the stream-socket mode - #8

Merged
valzargaming merged 1 commit into
mainfrom
claude/oauth-sessions
Nov 6, 2026
Merged

valzargaming merged 1 commit into
mainfrom
claude/oauth-sessions

Conversation

@valzargaming

Copy link
Copy Markdown
Member

Requested by Val · project thread

Implements the three follow-up recommendations from the review (#4 to #7 fixed the bugs).

Problem

  • OAuth2 sessions (/dwa, /ss14wa) were stored per client IP. Two players behind one NAT shared a login, and ?user returned the previous visitor's profile.
  • The token exchange, profile and revocation calls used blocking cURL inside the event loop. Civilizationbot embeds the verifier in the same loop as its Discord gateway and HTTP server, so every provider round trip stalled the whole bot.
  • The stream-socket mode (init(null, true)) reads 1 KiB per request and parses form bodies as headers; it only ever worked for the legacy header format.

Change

  • Cookie sessions. SessionCookie mints a random 128-bit verifier_session id (HttpOnly, SameSite=Lax, 24 h idle expiry), shared by both endpoints so one browser's Discord and SS14 logins land under the same key. Sessions live in $sessions[<endpoint>][<id>], the shape Civilizationbot's Civ14\Verifier::getIPFromDiscord()/getSS14FromIP() already walk (they only use the key opaquely). Idle entries are pruned; a malformed cookie value gets a fresh id.
  • Asynchronous providers. OAuth2Authenticator is now one instance per endpoint taking an injected async HTTP client (ReactPHP Browser, 10 s timeout, no redirects, by default) and returns promises. New AsyncEndpointInterface; Server::handleReact returns the promise for such endpoints and maps a rejection to 500. The synchronous handle()/handleEndpoint() adapter still serves login, logout and cached profile reads and answers 503 for anything needing a provider call. A code is exchanged once per login (state is consumed). The Discord revocation URL is corrected to /oauth2/token/revoke (docs).
  • Deprecation. init(null, true) emits E_USER_DEPRECATED; README documents its limits.

Compatibility with Civilizationbot

setDiscordOAuth2Endpoint/setSS14OAuth2Endpoint signatures and getSessions() are unchanged; the verified-list endpoints are untouched. The only behavioural change the bot can observe is the session key (cookie id instead of IP), which its code treats as an opaque string. A test replays its correlation lookup. The Civ14 OAuth path is commented out in the bot today, so nothing live depends on it.

Verification

php scripts/lint.php                                   # Parsed 35 PHP files with PHP 8.3.6
./vendor/bin/phpunit --bootstrap vendor/autoload.php tests/isolated   # OK (40 tests, 209 assertions)

New tests/isolated/OAuth2SessionTest.php uses a canned provider (no network): per-browser cookies and states, state check before any provider call, profile isolation between browsers, Discord/SS14 correlation by cookie, expiry and forged cookies, pending promises through handleReact, the 503 adapter path, and provider failure as 500. Not verified: a real Discord/SS14 login round trip.


Generated by Claude Code

…ecate the stream-socket mode

OAuth2 sessions were stored per client IP, so two players behind one NAT
shared a login and ?user returned the previous visitor's profile. The
browser is now identified by a random HttpOnly verifier_session cookie
shared by /dwa and /ss14wa, so one browser's Discord and SS14 logins land
under the same key (Civilizationbot's Civ14 verifier correlates them by
that key), while unrelated browsers never collide. Idle sessions expire
after 24 hours and a malformed cookie value gets a fresh id.

The token exchange, profile and revocation calls used blocking cURL
inside the event loop, which stalled the whole process (verifier,
Discord gateway and HTTP server share one loop) for every provider round
trip. The authenticator now takes an asynchronous HTTP client (ReactPHP
Browser by default, 10 s timeout) and returns promises; a new
AsyncEndpointInterface lets handleReact answer when the promise settles.
The synchronous in-process adapter still serves login, logout and cached
profile reads and answers 503 for anything that needs a provider call.
A code can be exchanged once per login; the Discord revocation path is
/oauth2/token/revoke as documented.

init(null, true) (stream-socket mode) now emits a deprecation notice; the
README explains its limits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WTP2S4qKEFT8a6a6yFEL4
Copilot AI balanced review requested due to automatic review settings November 6, 2026 11:55
@valzargaming valzargaming self-assigned this Nov 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@valzargaming
valzargaming merged commit 05ddbc1 into main Nov 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants