Skip to content

Add SSR modules#75

Merged
Fermionic-Lyu merged 5 commits into
mainfrom
codex/ssr-auth-helpers
May 28, 2026
Merged

Add SSR modules#75
Fermionic-Lyu merged 5 commits into
mainfrom
codex/ssr-auth-helpers

Conversation

@Fermionic-Lyu
Copy link
Copy Markdown
Member

@Fermionic-Lyu Fermionic-Lyu commented May 27, 2026


Summary by cubic

Adds first-class SSR auth for Next.js via @insforge/sdk/ssr, with server-owned refresh cookies and browser-readable access tokens. Also adds a server-only admin client and makes Realtime SSR-safe by dynamically importing socket.io-client.

  • New Features

    • @insforge/sdk/ssr helpers: createBrowserClient(), createServerClient(), createRefreshAuthRouter(), refreshAuth(), updateSession(), and cookie utils (setAuthCookies(), clearAuthCookies(), options/getters).
    • Defaults: insforge_access_token (readable) and insforge_refresh_token (httpOnly), both expiring at JWT exp.
    • Auto env resolution (NEXT_PUBLIC_INSFORGE_URL, NEXT_PUBLIC_INSFORGE_ANON_KEY); explicit config wins.
    • Browser client pre-refreshes on missing/expiring access token and retries on AUTH_TOKEN_EXPIRED; server client reads the access-token cookie per request.
    • createAdminClient({ apiKey }) for server-only admin access (bearer token; enables server mode).
    • Realtime: dynamic import of socket.io-client to avoid SSR bundles; re-subscribes on reconnect.
    • JWT utils: getJwtExpiration(), isJwtExpiredOrExpiring().
    • Types/Errors: InsForgeAdminConfig, InsForgeErrorCode; legacy server-mode refresh error standardized to AUTH_UNAUTHORIZED.
    • Docs/build: add @insforge/sdk/ssr export and tsup entry; externalize socket.io-client; bump to 1.3.0; update @insforge/shared-schemas to ^1.1.53.
  • Migration

    • Next.js/SSR: add /api/auth/refresh via createRefreshAuthRouter(), use createBrowserClient() and createServerClient(), run updateSession() in middleware/proxy, and call setAuthCookies() after sign-in.
    • Replace isServerMode and manual refreshSession({ refreshToken }) with the new SSR helpers.
    • Provide NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY, or pass baseUrl/anonKey explicitly.

Written for commit e97eb07. Summary will update on new commits.

Review in cubic

Note

Add @insforge/sdk/ssr subpath with browser/server client factories and session refresh helpers

  • Adds a new ./ssr export subpath in src/ssr.ts exposing createBrowserClient, createServerClient, createRefreshAuthRouter, refreshAuth, updateSession, and cookie utilities for Next.js SSR integration.
  • createBrowserClient reads access tokens from cookies, pre-refreshes expiring tokens via a configurable app route, and retries once on 401 with auth error codes.
  • createServerClient builds a server-mode InsForgeClient using per-request access tokens sourced from cookies.
  • refreshAuth and createRefreshAuthRouter handle server-side token refresh via httpOnly cookies, setting Set-Cookie headers on the response and omitting refreshToken from the response body.
  • updateSession acts as middleware to proactively refresh sessions and sync cookies before a request is handled.
  • Adds createAdminClient in src/index.ts for server-only admin access using an apiKey.
  • Adds JWT utilities getJwtExpiration and isJwtExpiredOrExpiring in src/lib/jwt.ts using atob/TextDecoder to avoid a Node Buffer dependency.
  • socket.io-client is now loaded lazily via dynamic import in src/modules/realtime.ts and is marked external in the build.

Macroscope summarized e97eb07.

Summary by CodeRabbit

  • New Features

    • SSR support: browser/server client helpers, refresh route/handler, session update utilities, and automatic auth cookie management.
    • Admin client for trusted server-side operations.
    • JWT expiration helpers for token validation.
  • Documentation

    • Updated README and SDK reference with SSR/Next.js patterns, env var guidance, cookie defaults, and examples.
  • Tests

    • Added tests for SSR flows, cookie behavior, admin client validation, and JWT utilities.
  • Chores

    • Package version bumped and export mapping updated.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce431094-8f5f-4d34-aa9f-cf4e058f0f60

📥 Commits

Reviewing files that changed from the base of the PR and between f90f738 and e97eb07.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • package.json

Walkthrough

Adds a new @insforge/sdk/ssr surface (browser/server client factories, refresh/update-session flows, cookie utilities, JWT helpers), an admin client factory, typed error codes, dynamic realtime import, build/export updates, tests, and documentation changes.

Changes

SSR Feature Implementation

Layer / File(s) Summary
Documentation updates
README.md, SDK-REFERENCE.md
Docs updated to include anonKey, createAdminClient, and new @insforge/sdk/ssr usage and Next.js examples.
Build, package exports, and runtime deps
package.json, tsup.config.ts, src/modules/realtime.ts
Bumps package version, adds ./ssr export, updates dependency, marks socket.io-client external, and dynamically imports socket client at runtime.
Types and JWT helpers
src/types.ts, src/lib/jwt.ts, src/lib/__tests__/jwt.test.ts
Adds InsForgeErrorCode, InsForgeAdminConfig, deprecates isServerMode in docs, and implements JWT expiry helpers and tests.
Admin client & public exports
src/index.ts, src/client.ts, src/lib/__tests__/client.test.ts
Adds createAdminClient (requires apiKey), makes createClient config optional, and tweaks client import typing; tests validate header wiring and validation.
Auth module error code usage
src/modules/auth/auth.ts
Replaces literal refresh-token error code with shared ERROR_CODES.AUTH_UNAUTHORIZED and expands doc comment to point SSR users to new helpers.
SSR module barrel exports
src/ssr.ts
Adds central barrel exporting browser/server clients, refresh/update-session, and cookies API.
Cookie utilities and serialization
src/ssr/cookies.ts
Implements cookie defaults, types, reader/writer abstractions, JWT-derived expires, set/delete/serialize helpers, and Set-Cookie header helpers.
Server-side auth refresh
src/ssr/refresh.ts
Implements refreshAuth, error normalization, JSON parsing, cookie header writes/clears, and createRefreshAuthRouter.
Server-side client factory
src/ssr/server-client.ts
Adds createServerClient that resolves baseUrl/anonKey, derives access token from options/cookies, and returns a server-mode client.
Browser client with SSR-aware fetch
src/ssr/browser-client.ts
Implements createBrowserClient with ssrFetch handling pre-refresh, retry on refreshable errors, deduplication of concurrent refreshes, and token synchronization.
Middleware/Proxy session updater
src/ssr/update-session.ts
Adds updateSession to validate access-token freshness, conditionally refresh via refreshAuth, and sync or clear cookies on request/response stores.
SSR integration tests
src/ssr/__tests__/ssr.test.ts
Adds comprehensive tests for cookie behavior, client creation, refresh flows, refresh route, and updateSession scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jwfing
  • tonychang04

"🐇 I hopped in with a tiny key,
Cookies snug for server and me,
Tokens refreshed at break of dawn,
Docs polished, tests all drawn,
Hooray — SSR now runs with glee!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add SSR modules' directly and concisely summarizes the main objective of the PR, which is to introduce new SSR (Server-Side Rendering) authentication helpers and modules for Next.js via a new @insforge/sdk/ssr entrypoint.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ssr-auth-helpers

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/lib/__tests__/jwt.test.ts (1)

12-25: ⚡ Quick win

Add malformed-token coverage for expiry decisions.

Current tests only cover a valid payload path. Please add a case for malformed token input to lock expected refresh behavior and prevent regressions.

🤖 Prompt for 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.

In `@src/lib/__tests__/jwt.test.ts` around lines 12 - 25, Add a unit test in the
'jwt helpers' suite that passes a clearly malformed JWT string (e.g.,
'malformed.token') to getJwtExpiration and asserts the expected safe result (use
null to lock the refresh decision) so malformed tokens don't throw or produce a
Date; reference getJwtExpiration and add the test alongside the existing
jwtWithPayload-based case to prevent regressions.
🤖 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 `@package.json`:
- Line 3: Update package.json so prerelease versions follow the recommended
-dev.X format and publishing scripts use the correct dist-tags: change the
"version" value from "1.3.0-ssr.3" to a dev prerelease like "1.3.0-dev.3" (or
add a short comment in package.json/README explaining why -ssr.3 is
intentionally dev if you must keep it), and ensure the npm scripts "publish:dev"
runs npm publish --tag dev while "publish:stable" runs npm publish (defaulting
to latest) so that dev prereleases are published with the dev dist-tag and
stable releases remain on latest.

In `@src/index.ts`:
- Around line 82-90: The current check only rejects missing apiKey but allows
whitespace-only values; update the validation around config.apiKey (used before
destructuring into { apiKey, ...clientConfig } and when instantiating
InsForgeClient) to reject blank/whitespace-only strings by using something like
String(config.apiKey).trim() === '' (or trim check) and throw the same
Error('Missing apiKey. Pass apiKey to createAdminClient().') when blank so
edgeFunctionToken never receives an empty token.

In `@src/lib/jwt.ts`:
- Around line 30-37: The current isJwtExpiredOrExpiring function treats a
malformed or unparsable JWT as valid because it returns false when
getJwtExpiration(token) yields null; change this behavior so that when
getJwtExpiration(token) returns null you treat the token as expired (return
true) so refresh paths won’t be skipped. Locate isJwtExpiredOrExpiring and
adjust the early-return logic (and any callers relying on it) so malformed
tokens trigger a true result while preserving the existing leewaySeconds
expiration check for valid expiration dates.

In `@src/ssr/browser-client.ts`:
- Around line 171-204: The ssrFetch wrapper must honor the skipAuthRefresh
opt-out for both the pre-request refresh and the post-401 retry: compute a local
flag (e.g., const skip = shouldSkipRefresh(input)) at the top of ssrFetch and
use it to short-circuit any refresh logic — do not call refreshFromRoute() or
attempt to attach refreshed tokens if skip is true; likewise, after reading
errorCode, if skip is true, return the original response even if
isRefreshableErrorCode(errorCode) is true (i.e., don't perform the retry/refresh
flow). Update logic around ssrFetch, shouldSkipRefresh, refreshFromRoute,
withAuthHeader, readErrorCode, isRefreshableErrorCode and client.setAccessToken
to respect that skip flag in both the pre-request and post-401 paths.

---

Nitpick comments:
In `@src/lib/__tests__/jwt.test.ts`:
- Around line 12-25: Add a unit test in the 'jwt helpers' suite that passes a
clearly malformed JWT string (e.g., 'malformed.token') to getJwtExpiration and
asserts the expected safe result (use null to lock the refresh decision) so
malformed tokens don't throw or produce a Date; reference getJwtExpiration and
add the test alongside the existing jwtWithPayload-based case to prevent
regressions.
🪄 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: 418794ab-2543-4369-b3da-104db4a89d38

📥 Commits

Reviewing files that changed from the base of the PR and between 3c32fdb and dd1845d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • README.md
  • SDK-REFERENCE.md
  • package.json
  • src/client.ts
  • src/index.ts
  • src/lib/__tests__/client.test.ts
  • src/lib/__tests__/jwt.test.ts
  • src/lib/jwt.ts
  • src/modules/auth/auth.ts
  • src/modules/realtime.ts
  • src/ssr.ts
  • src/ssr/__tests__/ssr.test.ts
  • src/ssr/browser-client.ts
  • src/ssr/cookies.ts
  • src/ssr/refresh.ts
  • src/ssr/server-client.ts
  • src/ssr/update-session.ts
  • src/types.ts
  • tsup.config.ts

Comment thread package.json Outdated
Comment thread src/index.ts Outdated
Comment thread src/lib/jwt.ts
Comment thread src/ssr/browser-client.ts
Comment on lines +171 to +204
const ssrFetch: typeof fetch = async (input, init) => {
if (!fetchImpl) {
throw new Error(
'Fetch is not available. Please provide a fetch implementation.',
);
}
if (shouldSkipRefresh(input)) {
return fetchImpl(input, init);
}

let requestInit = init;
if (
(!accessToken && !sessionChecked) ||
isJwtExpiredOrExpiring(accessToken, options.refreshLeewaySeconds)
) {
const refreshed = await refreshFromRoute().catch(() => null);
if (refreshed?.accessToken) {
requestInit = withAuthHeader(init, refreshed.accessToken);
}
}

const response = await fetchImpl(input, requestInit);
const errorCode = await readErrorCode(response);
if (!isRefreshableErrorCode(errorCode)) {
return response;
}

const refreshed = await refreshFromRoute();
if (!refreshed?.accessToken) {
client.setAccessToken(null);
return response;
}

return fetchImpl(input, withAuthHeader(init, refreshed.accessToken));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Honor skipAuthRefresh in the SSR fetch wrapper.

ssrFetch currently refreshes/retries every refreshable 401, so auth calls that intentionally return 401s can still hit /api/auth/refresh even when the SDK request layer marked them to skip refresh. Please thread the same opt-out through both the pre-request refresh path and the post-401 retry path here.

Based on learnings: skipAuthRefresh: true must prevent legitimate auth 401s from triggering a refresh cycle.

🤖 Prompt for 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.

In `@src/ssr/browser-client.ts` around lines 171 - 204, The ssrFetch wrapper must
honor the skipAuthRefresh opt-out for both the pre-request refresh and the
post-401 retry: compute a local flag (e.g., const skip =
shouldSkipRefresh(input)) at the top of ssrFetch and use it to short-circuit any
refresh logic — do not call refreshFromRoute() or attempt to attach refreshed
tokens if skip is true; likewise, after reading errorCode, if skip is true,
return the original response even if isRefreshableErrorCode(errorCode) is true
(i.e., don't perform the retry/refresh flow). Update logic around ssrFetch,
shouldSkipRefresh, refreshFromRoute, withAuthHeader, readErrorCode,
isRefreshableErrorCode and client.setAccessToken to respect that skip flag in
both the pre-request and post-401 paths.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

3 issues found across 20 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/ssr/cookies.ts
Comment thread src/lib/jwt.ts Outdated
Comment thread src/ssr/browser-client.ts Outdated
Copy link
Copy Markdown
Member

@jwfing jwfing left a comment

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 d7c4cf7 into main May 28, 2026
7 of 8 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.

2 participants