From 9327017da7e822d3a72a278db15ac6a2df2b249b Mon Sep 17 00:00:00 2001 From: Rokojori Date: Sun, 2 Aug 2026 23:40:55 +0200 Subject: [PATCH] docs: auth overhaul, library-ts ESM fix, rokojori-auth testing task --- workspace/boards/tasks.html | 18 ++++ .../2026/08-August/02-Saturday/index.html | 51 +++++++++- workspace/history/index.html | 2 +- workspace/outline/index.html | 97 ++++++++++++++----- 4 files changed, 143 insertions(+), 25 deletions(-) diff --git a/workspace/boards/tasks.html b/workspace/boards/tasks.html index 13f7243..cdc5df8 100644 --- a/workspace/boards/tasks.html +++ b/workspace/boards/tasks.html @@ -22,6 +22,24 @@
To Do
+ + rokojori-auth: end-to-end testing after session refresh overhaul + + The 7-phase auth session refresh overhaul (grace window, TokenUpdater, GuardedCall, + Web Locks leader election, Electron token updater, new-session endpoint, heartbeat + login) is deployed but not yet fully tested end-to-end. Verify: + + — Browser: multiple tabs open, let access token near-expire; confirm only one tab + refreshes and others get state via BroadcastChannel. + — Browser: confirm GuardedCall retries on transient network errors and blocks on expired. + — Electron: let app run >1h, confirm token is refreshed proactively without restart. + — Electron multi-instance: open two projects, confirm second instance skips login via + heartbeat and mints an independent session. + — rokojori-auth grace window: force a near-simultaneous double-refresh and confirm + the second call resolves to the same replacement pair instead of 401ing. + + + Rojo Chat: LLM Tools diff --git a/workspace/history/2026/08-August/02-Saturday/index.html b/workspace/history/2026/08-August/02-Saturday/index.html index 556e184..6d80587 100644 --- a/workspace/history/2026/08-August/02-Saturday/index.html +++ b/workspace/history/2026/08-August/02-Saturday/index.html @@ -13,7 +13,7 @@

Saturday, 2 August 2026

Session History

-

Rojo bug fixes (local mode detection, creation, system prompt). Claude API provider and Claude Code subprocess provider for rojo-chat-panel.

+

Rojo bug fixes, Claude API and Claude Code providers, auth session refresh overhaul (7 phases), library-ts ESM import fix.

@@ -118,6 +118,55 @@

+ + +
+

Auth session refresh overhaul — 7 phases

+

+ Full design doc: workspace/history/2026/08-August/02-Saturday/auth-update.page. + Root cause: two independent bugs — browser tabs racing a single-use refresh token, + and Electron never refreshing after its one-shot startup call. +

+ +
+ +
+

library-ts: .js extensions added to all browser relative imports

+

+ Roject's client is served as unbundled browser-native ESM — no bundler, Express + static serves individual .js files. All relative imports in + source/library-ts/browser/ were extension-less, causing cascading + 404s whenever any previously-unused module entered the import chain (first triggered + by TokenUpdater importing ActivityAnalyser). +

+

+ A one-shot Node.js script (fix-library-imports.js) added .js + to all relative imports across 101 files in a single pass. TypeScript with + moduleResolution: "bundler" accepts .js extensions in + source even for .ts files — clean build confirmed. +

+
+
diff --git a/workspace/history/index.html b/workspace/history/index.html index fbfd1d9..c52b571 100644 --- a/workspace/history/index.html +++ b/workspace/history/index.html @@ -21,7 +21,7 @@

Saturday, 2 August 2026

-

Rojo local-mode bug fixes: detection, creation, and system-prompt delivery (all caused by the same missing localRoot branching). Claude API provider: @anthropic-ai/sdk streaming, separate conversation history, tool use support. Claude Code subprocess provider: no API key needed, --print --output-format stream-json --verbose, --session-id/--resume for multi-turn. Settings panel: claude and claude-code endpoint types. Three bugs fixed: missing --verbose, stdin blocking, and shell: true mangling messages on Windows.

+

Rojo local-mode bug fixes (detection, creation, system-prompt). Claude API and Claude Code subprocess providers. Auth session refresh overhaul: 7 phases — rokojori-auth grace window, TokenUpdater, GuardedCall, Web Locks leader election, Electron token updater, new-session endpoint, heartbeat multi-instance login. library-ts: .js extensions added to 101 browser files for browser-native ESM compatibility.

diff --git a/workspace/outline/index.html b/workspace/outline/index.html index fa233b2..442b7be 100644 --- a/workspace/outline/index.html +++ b/workspace/outline/index.html @@ -45,38 +45,57 @@

User accounts are managed by rokojori-auth at account.rokojori.com — registration, login, JWT issuance, refresh - token rotation, roles, and account deletion. + token rotation with a grace window, roles, and account deletion.

- Token refresh: - rokojori-auth's page-level middleware (before express.static) - verifies the accessToken cookie but calls next() on - any error — no redirect on expiry. Transparent refresh for API calls is handled - by Roject's jwtMiddleware: when a TokenExpiredError - hits an /api/ route and a refreshToken cookie is present, - it calls POST AUTH_INTERNAL_HOST/api/auth/refresh server-side, sets - the new cookies on the response, decodes the new JWT into req.user, - and continues transparently. If refresh fails, the request falls through to - requireAuth which returns 401. AUTH_INTERNAL_HOST - defaults to AUTH_HOST; set it to http://localhost:3001 - in production to bypass nginx. The editor-shell checks - GET /api/auth/me on startup and redirects to / on 401. + Refresh token grace window (rokojori-auth): + refreshTokens are soft-deleted — usedAt and + replacedBy are stamped on first use rather than the row being deleted. + A concurrent second refresh within REFRESH_GRACE_TTL (10 s) resolves + to the same replacement pair instead of 401ing. This is the baseline correctness + guarantee against races; client-side leader election is an optimisation on top. +

+

+ TokenUpdater (source/auth/TokenUpdater.ts): + Central browser auth state machine. State enum: valid | refreshing | expired | network-error, + exposed via EventSlot. Triggers: a 5-minute periodic timer and + ActivityAnalyser.onActive (fires on mouse, touch, focus, and tab-visibility + restore). Each trigger pings GET /api/auth/me; jwtMiddleware + handles proactive cookie rotation server-side when within 15 min of expiry + (access token is httpOnly, so expiry is unreadable client-side). + Web Locks leader election: the first tab acquires roject-token-updater-leader + exclusively and broadcasts state to followers via BroadcastChannel. + Leader handoff is automatic when the holder tab closes. +

+

+ GuardedCall (source/auth/GuardedCall.ts): + Singleton wrapper for all API calls. Three tiers — + user: no retry, throws on failure; + editor: 3 retries at 1 s / 3 s / 8 s, then throws; + silent: same delays, never throws, console.warn on failure. + Pre-flight: blocks when state is expired; waits up to 6 s when + refreshing. Wired into Editor.ts (save = user, open = editor) + and editor-shell.ts (layout save/load = silent).

Token extraction order (extractToken in auth.ts): - Bearer header is checked before the accessToken cookie. This means an - explicit Authorization: Bearer ... header always wins — critical for - Electron (which injects tokens via onBeforeSendHeaders), API clients, - and any context where a stale browser cookie might otherwise shadow a fresh token. + Bearer header is checked before the accessToken cookie. An explicit + Authorization: Bearer … header always wins — critical for Electron + (which injects tokens via onBeforeSendHeaders) and any context where a + stale cookie might shadow a fresh token.

Clock skew — JWT_CLOCK_TOLERANCE: jwt.verify accepts a clockTolerance option (seconds). - The env var JWT_CLOCK_TOLERANCE (default 0 / unset) is read as an - integer and passed as clockTolerance when non-zero. Set to - 7200 in .env for local development to absorb clock skew - between the Windows dev machine and the production auth server. Never set this in - production — if you need it there, fix the clock instead. + Set JWT_CLOCK_TOLERANCE=7200 in .env for local dev to + absorb skew between the Windows dev machine and the production auth server. Never + set this in production — fix the clock instead. +

+

+ New-session endpoint (POST /api/auth/new-session): + requireAuth-guarded; mints a fresh independent token pair via + issueTokenPair for the authenticated user. Used by Electron to start + a new independent session from a running instance's heartbeat without re-login.

@@ -445,6 +464,27 @@ session.webRequest.onBeforeSendHeaders. Run with npm run electron:dev.

+

+ Electron token updater: runs in electron/main.ts. + Reads the access token's exp directly from the JWT payload (token is + held in the main process, not behind an httpOnly cookie). Uses a + server clock offset derived from the Date response header of the first + auth-server call (_serverClockOffsetMs) for all expiry comparisons. + Refreshes proactively when within 15 min of expiry; checks every 5 min. Network + errors on refresh are silently retried next tick; auth failures (expired/revoked + refresh token) close the main window and show the login screen. Multiple Electron + instances each run their own updater with their own independent session — no + single-instance lock. +

+

+ Heartbeat session sharing: a running instance writes + { accessToken, timestamp } to userData/session-heartbeat.json + every 10 s. A newly-starting instance reads it on launch; if ≤ 30 s old, it calls + POST /api/auth/new-session (requireAuth-guarded) to mint its own + independent token pair — skipping the login screen. If the heartbeat is stale or the + call fails, it falls through to normal login. Replaces the old plaintext + last-password.txt auto-login. +

Local filesystem access: when ROJECT_ELECTRON=true, the server mounts /api/local/ routes backed by Node.js fs @@ -553,11 +593,22 @@ build/app/library-ts/browser/. The node part is included by tsconfig.ts-node.json.

+

+ Import extension convention: all relative imports in + browser/ use explicit .js extensions + (e.g. from "../events/EventSlot.js"). Roject's client code is served + as unbundled browser-native ESM — no bundler resolves paths at build time, so the + browser fetches each module by its literal URL. Extension-less imports 404 because + Express static only serves the actual .js files. TypeScript with + moduleResolution: "bundler" accepts .js extensions in + source even when the source file is .ts. +

git submodule source/library-ts/ project references composite: true + .js extensions required