fix(realtime): in-band socket re-auth and presence reconnect docs - #1635
Conversation
Token refreshes used to force a full socket reconnect, wiping presence for every member each access-token TTL. The SDK now keeps the live connection for same-user refreshes; this adds the server half: - realtime:auth client event verifies a refreshed JWT for the same subject and updates the socket's claims without a reconnect - RealtimeAuthPayload/RealtimeAuthResponse schemas in shared-schemas - drop the restoredSubscriptions log field: metadata is created fresh on every connection, so it always logged 0 and implied a server-side restore mechanism that does not exist - document subscribe() idempotency, the presence:sync event, getPresenceState(), and reconnect/token-refresh behavior Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
WalkthroughThis PR adds in-band realtime re-authentication for sockets, with new ChangesRealtime re-authentication
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR adds live realtime socket re-authentication and updates the presence reconnect docs. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (3): Last reviewed commit: "docs: add resync/resyncs to vale vocabul..." | Re-trigger Greptile |
jwfing
left a comment
There was a problem hiding this comment.
Review: fix(realtime): in-band socket re-auth and presence reconnect docs
Summary: A clean, well-scoped, backward-compatible addition of a realtime:auth client event that refreshes a live socket's claims in-band; the implementation is sound and adequately tested, with only minor non-blocking notes.
Requirements context
No matching spec/plan found under docs/superpowers/ (the only documents there are 2026-06-29-e2e-testing-skill-{design,plan} — unrelated). Assessed against the PR description, the linked SDK PR (InsForge/InsForge-sdk-js#100), and surrounding code conventions.
Findings
Critical
(none)
Suggestion
-
Functionality — role-room membership goes stale on a role change (
backend/src/infra/socket/socket.manager.ts:424-433). At connect the socket joinsrole:${role}(line 217). On re-auth you updatesocket.data.user.roleandmetadata.role, but the socket is not moved out of the oldrole:<oldrole>room intorole:<newrole>. Today this is latent — I grepped the codebase and therole:/user:rooms are joined but never used as.to()broadcast targets, so there's no functional impact. But if a role-scoped broadcast is ever added, a socket that changed role mid-connection would silently receive the wrong room's traffic. Consider either re-joining the correct room on re-auth or adding a short comment noting these rooms are currently unused so the omission is intentional. -
Security/Functionality — existing subscriptions are not re-evaluated on a role downgrade (
backend/src/infra/socket/socket.manager.ts:396-443). In-band re-auth intentionally avoids a reconnect, so if a refreshed token reflects reduced access (e.g. a role downgrade or revoked membership), the socket keeps its already-established subscriptions and presence until it happens to reconnect. Blast radius is low:handleRealtimePublishre-authorizes on every insert (realtime-message.service), and reads were authorized at subscribe time via the RLS SELECT policy — so this only affects continued receipt on already-joined channels. Worth a note in the handler (or the docs' "Token refreshes" section) that mid-session access reduction isn't enforced until reconnect, so expectations are clear.
Information
- Software engineering — unreachable
!tokenPayload.rolebranch (backend/src/infra/socket/socket.manager.ts:404-410).tokenManager.verifyTokenalways returns a role, defaulting to'authenticated'when the claim is absent (backend/src/infra/security/token.manager.ts:287,302). So this guard can never fire and is (correctly) not covered by the new test suite. Consider removing it or dropping a comment thatverifyTokenguarantees a role. - Software engineering — test coverage. The new
backend/tests/unit/socket-realtime-auth.test.ts(4 tests) cleanly covers the meaningful paths: same-subject accept + claim update, different-subject reject (claims untouched), expired, and malformed. Mocking style and the private-handler.bindapproach match existing patterns. Good. - Functionality — docs consistency.
docs/sdks/typescript/realtime.mdx:171,304-314documentspresence:syncas an SDK-emitted event (not a serverServerEventsentry), which is consistent with the server code (nopresence:syncis emitted server-side) and with the SDK half in #100. No server change needed — noted for completeness. - Security — no regressions. Re-auth reuses the same
verifyTokenas the handshake (signature + expiry enforced), the same-subject check blocks identity swaps / privilege takeover via anon or API-key sockets (theiridis'anonymous'/'api-key', so any user JWT mismatches and is rejected), and onlysocketId/userIdare logged at debug — no token or PII leakage. No new dependencies. - Performance — no concerns.
verifyTokenis synchronous CPU work but runs roughly once per access-token TTL (~15m), not in a hot path; no new queries, loops, or blocking I/O.
Verdict
approved (informational — the human still approves via the GitHub approve flow). No Critical findings; the two Suggestions are latent/low-blast-radius and safe to address in a follow-up or wave off with a comment.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/infra/socket/socket.manager.ts`:
- Around line 389-444: The handleRealtimeAuth flow updates socket.data.user and
metadata.role, but it does not resync the socket’s role-based room membership
after a token role change. Update handleRealtimeAuth in socket.manager.ts to
remove the socket from its previous role:* room and join the new one based on
tokenPayload.role, using the existing socket state/metadata to detect the old
role. Keep the room changes synchronized with the successful re-auth path so
downgraded sockets leave privileged rooms and upgraded sockets enter them
immediately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 144659ed-1812-4470-8d70-d05820fa646e
📒 Files selected for processing (5)
backend/src/infra/socket/socket.manager.tsbackend/src/types/socket.tsbackend/tests/unit/socket-realtime-auth.test.tsdocs/sdks/typescript/realtime.mdxpackages/shared-schemas/src/realtime.schema.ts
There was a problem hiding this comment.
1 issue found across 5 files
Confidence score: 2/5
- In
backend/src/infra/socket/socket.manager.ts, refreshed tokens that change a user’s role do not reassignrole:*room membership, so a downgraded socket can keep receiving events for its old higher-privilege role; this creates a concrete authorization regression if merged as-is — on token refresh, explicitly leave stale role rooms and join the new role room (or force reconnect) before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="backend/src/infra/socket/socket.manager.ts">
<violation number="1" location="backend/src/infra/socket/socket.manager.ts:432">
P1: When the refreshed token carries a different role, the socket's `role:*` room membership is not updated — the socket remains in the room it joined at handshake. A user whose role was downgraded (e.g., from `project_admin` to `authenticated`) will keep receiving broadcasts sent to `role:project_admin` until the socket reconnects; conversely, an upgraded user won't receive broadcasts for their new role.
After updating `metadata.role`, leave the old role room and join the new one (similar to the logic in the connection handler):
```ts
if (metadata.role !== tokenPayload.role) {
void socket.leave(`role:${metadata.role}`);
metadata.role = tokenPayload.role;
void socket.join(`role:${metadata.role}`);
} else {
metadata.role = tokenPayload.role;
}
```</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…uction Review follow-ups: role:* rooms are live broadcast targets (dashboard DATA_UPDATE goes to role:project_admin), so a role-changing re-auth must move the socket between role rooms or a downgraded connection keeps receiving privileged broadcasts. Also drop the unreachable missing-role guard (verifyToken guarantees a role) and note in the docs that access reductions apply lazily to already-joined channels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the review in b1800d9: role rooms now resync on a role-changing re-auth (they are in fact live broadcast targets — dashboard DATA_UPDATE goes to role:project_admin from the database/storage/functions routes), the unreachable missing-role guard is removed since verifyToken guarantees a role, and the docs' token-refresh section now states that access reductions apply lazily to already-joined channels. Re-running the E2E gate against the updated branch. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Docs PR opened: #1637 Rewrote the TypeScript realtime SDK page title and description to be more specific and hit SEO length targets. Note: failed to automatically merge |
Server half of the realtime presence-reconnect fix (SDK half: InsForge/InsForge-sdk-js#100).
Problem
Every access-token refresh forced the SDK to bounce the socket, because the server only authenticates at handshake — there was no way to refresh a live connection's token. Combined with an SDK bug that fabricated empty presence snapshots after reconnect, every app lost presence state each token TTL.
Changes
realtime:authclient event: verifies a refreshed JWT, requires the same subject (identity changes must reconnect so rooms/presence rebuild), updates the socket's claims, and acks ok/error. Additive and backward compatible — old SDKs never send it.RealtimeAuthPayload/RealtimeAuthResponseschemas in shared-schemas.restoredSubscriptionsfield from the connect log: socket metadata is created fresh per connection, so it always logged 0 and implied a server-side restore mechanism that doesn't exist (it sent the bug reporter chasing a red herring).docs/sdks/typescript/realtime.mdx):subscribe()idempotency, thepresence:syncreserved event,getPresenceState(), and new "Reading presence state" / "Reconnects" / "Token refreshes" sections.No changes to subscribe/publish/presence behavior —
trackMemberwas already idempotent per logical member, which is what makes the SDK-side idempotent resubscribe safe.Testing
backend/tests/unit/socket-realtime-auth.test.ts(4 tests): same-subject refresh updates claims, different-subject rejected without touching claims, expired token rejected, malformed token rejected.v2.2.6-rt-presencebuilt from this branch. No agent-e2e fixture changes needed — the new event is additive and nothing asserted changed; fixture coverage forrealtime:authshould land alongside the SDK release that exercises it.Note for reviewers: earlier E2E attempts with the longer tag
v2.2.6-realtime-presence-reconnectfailed atcloud.restartbefore the image booted — insforge-cloud-backend embeds the tag in the SSM SendCommandComment, which AWS caps at 100 chars, so any test tag over 28 chars fails deterministically. Follow-up: truncate that Comment in insforge-cloud-backend.🤖 Generated with Claude Code
Note
Server half of the realtime presence-reconnect fix (SDK half: InsForge/InsForge-sdk-js#100).
Problem
Every access-token refresh forced the SDK to bounce the socket, because the server only authenticates at handshake — there was no way to refresh a live connection's token. Combined with an SDK bug that fabricated empty presence snapshots after reconnect, every app lost presence state each token TTL.
Changes
realtime:authclient event: verifies a refreshed JWT, requires the same subject (identity changes must reconnect so rooms/presence rebuild), updates the socket's claims, and acks ok/error. Additive and backward compatible — old SDKs never send it.RealtimeAuthPayload/RealtimeAuthResponseschemas in shared-schemas.restoredSubscriptionsfield from the connect log: socket metadata is created fresh per connection, so it always logged 0 and implied a server-side restore mechanism that doesn't exist (it sent the bug reporter chasing a red herring).docs/sdks/typescript/realtime.mdx):subscribe()idempotency, thepresence:syncreserved event,getPresenceState(), and new "Reading presence state" / "Reconnects" / "Token refreshes" sections.No changes to subscribe/publish/presence behavior —
trackMemberwas already idempotent per logical member, which is what makes the SDK-side idempotent resubscribe safe.Testing
backend/tests/unit/socket-realtime-auth.test.ts(4 tests): same-subject refresh updates claims, different-subject rejected without touching claims, expired token rejected, malformed token rejected.v2.2.6-rt-presencebuilt from this branch. No agent-e2e fixture changes needed — the new event is additive and nothing asserted changed; fixture coverage forrealtime:authshould land alongside the SDK release that exercises it.Note for reviewers: earlier E2E attempts with the longer tag
v2.2.6-realtime-presence-reconnectfailed atcloud.restartbefore the image booted — insforge-cloud-backend embeds the tag in the SSM SendCommandComment, which AWS caps at 100 chars, so any test tag over 28 chars fails deterministically. Follow-up: truncate that Comment in insforge-cloud-backend.🤖 Generated with Claude Code
Changes since #1635 opened
SocketManager.handleRealtimeAuth[b1800d9]Summary by CodeRabbit
New Features
realtime:auth) so sessions can stay valid without reconnecting.Bug Fixes
Documentation
presence:sync/getPresenceState(channel)usage.