Enhancement: Multi-device login — 3 concurrent sessions with LRU eviction - #491
Open
baltinerdist wants to merge 13 commits into
Open
Enhancement: Multi-device login — 3 concurrent sessions with LRU eviction#491baltinerdist wants to merge 13 commits into
baltinerdist wants to merge 13 commits into
Conversation
…rotate/destroy + LRU evict) Adds CreateSession/ValidateSessionByToken/RotateSession/DestroySession plus private evictLruSessions and MAX_SESSIONS_PER_USER=3 to Authorization, backing the new ork_session table (Task 1). No existing call sites rewired yet (Task 3+). Fixed a bug in the task brief's verbatim code: DataSet() returns a YapoResultSet, so Next()/field reads must happen on that returned object, not on $DB directly (confirmed via class.YapoResultSet.php and existing call-site convention, e.g. class.StateOfAmtgard.php). Committed via the sanctioned --no-verify procedure (user-approved 2026-06-03, see feedback_authorization_skip_commit.md): the repo's pre-commit hook unconditionally strips class.Authorization.php from every commit regardless of what's staged, so the local-only true|| login bypass was removed first, the file staged in full and verified 0 occurrences of 'true ||', then committed with --no-verify. The bypass is restored immediately after this commit.
Replaces the raw ork_mundane.token comparison with ValidateSessionByToken() so the stale-session check works against the new multi-device ork_session table. Also drops a dead trailing $DB->Clear() call left without a global $DB in scope after removing the raw query, which was fatal-erroring every authenticated request.
Fresh login now calls CreateSession() instead of overwriting the single ork_mundane.token column, so multiple devices can hold live sessions concurrently (LRU-evicted beyond the 3-session cap). The token-refresh branch rotates the existing session via RotateSession() (falling back to CreateSession() if the old token was already evicted/expired), and IsAuthorized_h() now validates against ork_session via ValidateSessionByToken() instead of the mundane token column. mundane.token/token_expires are kept in sync as a vestigial "last token" pointer only.
Route the IDP/OAuth login flow through CreateSession (Task 2) in both idpAuthorize() and createIdpLink(), so "Login with Amtgard" sessions get the same 3-concurrent-device cap and LRU eviction as password login (Task 3). ork_mundane.token remains a vestigial pointer only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
Today logout() only cleared the PHP session while the DB session row lingered, allowing the token to be replayed. Now logout deletes the current device's ork_session row before clearing session vars, via Authorization::DestroySession, so the slot frees up immediately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
…cache removal; standardize self-reg token_expires to Y-m-d Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
…, CSPRNG rotate, DRY) - IsAuthorized_h no longer short-circuits on the PHP-session cache, so an LRU-evicted/expired token is rejected on every request (incl. skip-listed AJAX controllers), not just on full page loads. - CreateSession verifies the INSERT landed, retries once, logs + returns '' on hard failure; all login callers treat '' as a login failure. - RotateSession now mints the new token from the CSPRNG (was derived from the old token). - DRY the vestigial-token write into persistVestigialToken(); standardize token_expires to Y-m-d. TOKEN_LENGTH constant; evict/observability logtrace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
…(multi-device) The Authorize_h token-refresh branch keyed on the single ork_mundane.token column, so only the most-recently-logged-in device could re-auth. It now validates via ValidateSessionByToken against ork_session, so any of a user's up-to-3 live device tokens re-authenticates — completing multi-device parity for SOAP/mobile clients. Expiry is enforced per-session (ork_session.expires) instead of the single token_expires column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces ORK3's single-device login (one
ork_mundane.tokenslot — a new login silently kicked the previous device) with a multi-session model backed by a nework_sessiontable: up to 3 concurrent sessions per user, with least-recently-active (LRU) eviction when a 4th device logs in.All login entry points create sessions; the per-request check and the JSON-API authorizer validate tokens against
ork_sessionon every request; logout tears down its own session.ork_mundane.tokenis retained as a dual-written vestigial pointer so the change is revertible in one file.What changed
ork_sessiontable (db-migrations/2026-07-13-add-ork-session-table.sql) — one row per live token; uniquetoken, indexed onmundane_id/expires.Authorization::CreateSession()— cap 3, LRU eviction, and a verified INSERT (retries once, logs, and fails the login rather than returning a token that can't authenticate).class.Controller.php) and JSON-API auth (IsAuthorized_h) now validate againstork_sessionon every request (the prior PHP-session auth cache no longer short-circuits), so eviction/expiry takes effect immediately — including on AJAX endpoints.ork_sessionrow and clears the vestigial pointer to prevent replay.Deploy
Run the migration once:
The migration backfills currently-active tokens (unexpired only), so existing logged-in users are not kicked on deploy. It is idempotent (
INSERT IGNORE).Testing
No unit-test harness exists in this repo; verified via curl +
mariadbintegration against the local Docker app:session_replaced)Coverage
All auth entry points are multi-device: password + IDP/OAuth + self-registration (fresh login), the per-request/JSON-API validation, logout, and the SOAP/mobile token-refresh path (
Authorize_htoken branch, which now validates againstork_sessioninstead of the singleork_mundane.token).ork_mundane.tokenremains only as a vestigial rollback pointer.🤖 Generated with Claude Code
https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ