Skip to content

fix(realtime): in-band socket re-auth and presence reconnect docs - #1635

Merged
Fermionic-Lyu merged 3 commits into
mainfrom
claude/stoic-moore-1938f4
Jul 7, 2026
Merged

fix(realtime): in-band socket re-auth and presence reconnect docs#1635
Fermionic-Lyu merged 3 commits into
mainfrom
claude/stoic-moore-1938f4

Conversation

@Fermionic-Lyu

@Fermionic-Lyu Fermionic-Lyu commented Jul 6, 2026

Copy link
Copy Markdown
Member

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

  • New realtime:auth client 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 / RealtimeAuthResponse schemas in shared-schemas.
  • Removed the restoredSubscriptions field 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 (docs/sdks/typescript/realtime.mdx): subscribe() idempotency, the presence:sync reserved event, getPresenceState(), and new "Reading presence state" / "Reconnects" / "Token refreshes" sections.

No changes to subscribe/publish/presence behavior — trackMember was already idempotent per logical member, which is what makes the SDK-side idempotent resubscribe safe.

Testing

  • New unit suite 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.
  • Existing realtime unit suites pass; shared-schemas builds; eslint clean on changed files.
  • Deterministic Fixture E2E: passing run with image tag v2.2.6-rt-presence built from this branch. No agent-e2e fixture changes needed — the new event is additive and nothing asserted changed; fixture coverage for realtime:auth should land alongside the SDK release that exercises it.

Note for reviewers: earlier E2E attempts with the longer tag v2.2.6-realtime-presence-reconnect failed at cloud.restart before the image booted — insforge-cloud-backend embeds the tag in the SSM SendCommand Comment, 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

  • New realtime:auth client 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 / RealtimeAuthResponse schemas in shared-schemas.
  • Removed the restoredSubscriptions field 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 (docs/sdks/typescript/realtime.mdx): subscribe() idempotency, the presence:sync reserved event, getPresenceState(), and new "Reading presence state" / "Reconnects" / "Token refreshes" sections.

No changes to subscribe/publish/presence behavior — trackMember was already idempotent per logical member, which is what makes the SDK-side idempotent resubscribe safe.

Testing

  • New unit suite 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.
  • Existing realtime unit suites pass; shared-schemas builds; eslint clean on changed files.
  • Deterministic Fixture E2E: passing run with image tag v2.2.6-rt-presence built from this branch. No agent-e2e fixture changes needed — the new event is additive and nothing asserted changed; fixture coverage for realtime:auth should land alongside the SDK release that exercises it.

Note for reviewers: earlier E2E attempts with the longer tag v2.2.6-realtime-presence-reconnect failed at cloud.restart before the image booted — insforge-cloud-backend embeds the tag in the SSM SendCommand Comment, 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

  • Implemented in-band role transitions during socket re-authentication in SocketManager.handleRealtimeAuth [b1800d9]
  • Added test coverage for socket room transitions during re-authentication with role changes [b1800d9]
  • Clarified lazy enforcement of access reductions for live realtime connections in documentation [b1800d9]
  • Added vocabulary terms to Mintlify acceptance list [e8b8b26]

Summary by CodeRabbit

  • New Features

    • Added realtime in-band authentication refresh (realtime:auth) so sessions can stay valid without reconnecting.
  • Bug Fixes

    • Realtime auth now verifies identity consistency and rejects expired/malformed/invalid tokens with clear failure responses.
    • Automatically switches role-based channels when the user’s role changes during auth refresh.
  • Documentation

    • Updated TypeScript realtime SDK docs for idempotent resubscription behavior and clarified presence:sync/getPresenceState(channel) usage.

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>
@mintlify

mintlify Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
InsForge-docs 🟢 Ready View Preview Jul 6, 2026, 8:13 AM

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds in-band realtime re-authentication for sockets, with new realtime:auth schemas and event typing, a socket handler that refreshes authenticated user state, unit tests for token and role handling, and SDK docs for presence sync and state access.

Changes

Realtime re-authentication

Layer / File(s) Summary
Realtime auth payload and response schemas
packages/shared-schemas/src/realtime.schema.ts
Adds realtimeAuthPayloadSchema and discriminated realtimeAuthResponseSchema with inferred RealtimeAuthPayload/RealtimeAuthResponse types.
REALTIME_AUTH client event enum
backend/src/types/socket.ts
Adds REALTIME_AUTH = 'realtime:auth' to ClientEvents.
SocketManager handleRealtimeAuth implementation
backend/src/infra/socket/socket.manager.ts
Imports the new auth types, registers the REALTIME_AUTH handler, implements token verification and subject matching, updates socket.data.user and role room membership, and trims the connect log payload.
Unit tests for handleRealtimeAuth
backend/tests/unit/socket-realtime-auth.test.ts
Mocks dependencies and covers successful refresh, role-room transitions, subject mismatch, and invalid-token cases.
TypeScript SDK realtime docs updates
docs/sdks/typescript/realtime.mdx, docs/styles/config/vocabularies/Mintlify/accept.txt
Documents idempotent subscribe(), presence:sync, getPresenceState(channel), token-refresh-related presence behavior, and adds the accepted vocabulary entries needed by the docs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A bunny hops through the socket glow,
With a fresh new token, the claims still flow.
Presence stays tidy, the rooms all align,
Re-auth in a whisper: “you’re still just fine.”
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: realtime socket re-authentication and presence reconnect documentation updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/stoic-moore-1938f4

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds live realtime socket re-authentication and updates the presence reconnect docs. The main changes are:

  • Added a realtime:auth client event for refreshed JWTs.
  • Kept socket identity fixed during live re-auth.
  • Updated socket claims and role-room membership after role changes.
  • Added shared schemas for realtime auth payloads and responses.
  • Added unit coverage for same-user refreshes, role changes, and invalid tokens.
  • Expanded TypeScript realtime docs for presence sync, reconnects, and token refreshes.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
backend/src/infra/socket/socket.manager.ts Adds live realtime re-auth and updates role-room membership when refreshed claims change role.
backend/tests/unit/socket-realtime-auth.test.ts Adds focused tests for successful refreshes, role-room changes, subject mismatch, expired tokens, and malformed tokens.
packages/shared-schemas/src/realtime.schema.ts Adds realtime auth request and response schemas for the new socket ack flow.
docs/sdks/typescript/realtime.mdx Documents realtime presence state, reconnect behavior, and token refresh behavior.

Reviews (3): Last reviewed commit: "docs: add resync/resyncs to vale vocabul..." | Re-trigger Greptile

Comment thread backend/src/infra/socket/socket.manager.ts
Comment thread docs/sdks/typescript/realtime.mdx

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 joins role:${role} (line 217). On re-auth you update socket.data.user.role and metadata.role, but the socket is not moved out of the old role:<oldrole> room into role:<newrole>. Today this is latent — I grepped the codebase and the role: / 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: handleRealtimePublish re-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.role branch (backend/src/infra/socket/socket.manager.ts:404-410). tokenManager.verifyToken always 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 that verifyToken guarantees 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 .bind approach match existing patterns. Good.
  • Functionality — docs consistency. docs/sdks/typescript/realtime.mdx:171,304-314 documents presence:sync as an SDK-emitted event (not a server ServerEvents entry), which is consistent with the server code (no presence:sync is 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 verifyToken as the handshake (signature + expiry enforced), the same-subject check blocks identity swaps / privilege takeover via anon or API-key sockets (their id is 'anonymous'/'api-key', so any user JWT mismatches and is rejected), and only socketId/userId are logged at debug — no token or PII leakage. No new dependencies.
  • Performance — no concerns. verifyToken is 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 484c1b1 and 5c03025.

📒 Files selected for processing (5)
  • backend/src/infra/socket/socket.manager.ts
  • backend/src/types/socket.ts
  • backend/tests/unit/socket-realtime-auth.test.ts
  • docs/sdks/typescript/realtime.mdx
  • packages/shared-schemas/src/realtime.schema.ts

Comment thread backend/src/infra/socket/socket.manager.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 reassign role:* 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

Comment thread backend/src/infra/socket/socket.manager.ts
…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>
@Fermionic-Lyu

Copy link
Copy Markdown
Member Author

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>

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM - approved.

@Fermionic-Lyu
Fermionic-Lyu merged commit f7f9a54 into main Jul 7, 2026
18 checks passed
@Fermionic-Lyu
Fermionic-Lyu deleted the claude/stoic-moore-1938f4 branch July 7, 2026 04:27
@mintlify

mintlify Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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

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.

2 participants