Skip to content

fix(web): preserve manual auth across hard reloads#2602

Merged
rmusser01 merged 1 commit into
devfrom
codex/fix-webui-auth-hard-reload
Jul 4, 2026
Merged

fix(web): preserve manual auth across hard reloads#2602
rmusser01 merged 1 commit into
devfrom
codex/fix-webui-auth-hard-reload

Conversation

@rmusser01

@rmusser01 rmusser01 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep manually entered single-user auth available across hard reloads in the same browser session by rehydrating runtime auth from sessionStorage after tldwConfig is scrubbed.
  • Count runtime auth material in the WebUI shell auth gate so header/sidebar chrome remains authenticated.
  • Add regression coverage for the second hard reload path and runtime-credential shell gate.

Fixes #2590.

Change summary

This fixes #2590 by preserving manually entered single-user auth in browser session storage after the hardened bootstrap removes the key from localStorage-backed tldwConfig. The implementation uses sessionStorage rather than reintroducing persistent localStorage secrets, so users remain authenticated across hard reloads in the same browser session while the durable tldwConfig stays scrubbed. The shell gate now recognizes runtime auth because the visible WebUI shell should follow the same auth state used by API requests.

Test Plan

  • bunx vitest run __tests__/extension/runtime-bootstrap.test.ts __tests__/app/app-layout.test.tsx --config vitest.config.ts
  • git diff --check HEAD

Bandit skipped: frontend TypeScript only.


Summary by cubic

Preserves manually entered single-user auth across hard reloads in the same browser session by rehydrating runtime auth from sessionStorage after tldwConfig is scrubbed. Keeps the header/sidebar authenticated without restoring secrets to localStorage. Fixes #2590.

  • Bug Fixes
    • Add a sessionStorage bridge for manual single-user API keys; rehydrate on later hard reloads and when tldwConfig has a blank/placeholder apiKey; clear the session key on env opt-out or when switching to multi-user.
    • Count runtime-override credentials (getRuntimeApiKey/getRuntimeApiBearer) in the WebUI shell gate so header and sidebar remain visible.
    • Add regression tests for second hard reload, blank-key rehydrate, mode-switch session clear, and the runtime-credential shell gate.

Written for commit 626447b. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Runtime bootstrap now caches a manually entered single-user API key in sessionStorage and rehydrates it after credential scrubbing, preserving auth across a second hard reload. The shell auth gate in _app.tsx treats a present runtime API key/bearer as authenticated. Tests and a task record accompany the change.

Changes

Runtime auth persistence fix

Layer / File(s) Summary
Session-backed API key persistence in runtime bootstrap
apps/tldw-frontend/extension/shims/runtime-bootstrap.ts
Adds RUNTIME_SESSION_SINGLE_USER_API_KEY, readRuntimeSessionApiKey()/writeRuntimeSessionApiKey() helpers, and updated env-seeding branching that derives a manual single-user key from stored config or session storage, sets/clears the runtime override, and clears session storage when opted out or not in single-user mode.
Shell auth gate recognizes runtime credentials
apps/tldw-frontend/pages/_app.tsx
Imports getRuntimeApiBearer/getRuntimeApiKey and expands envAuthed in refreshAuthState to also be true when a runtime API key or bearer token is present.
Regression tests and task documentation
apps/tldw-frontend/__tests__/app/app-layout.test.tsx, apps/tldw-frontend/__tests__/extension/runtime-bootstrap.test.ts, backlog/tasks/task-12127 - ...md
Adds a mocked runtime API key test verifying shell chrome renders when authenticated via runtime override, a two-bootstrap "hard reload" test confirming the manual key persists via sessionStorage while scrubbed from tldwConfig, and a task file documenting the problem, acceptance criteria, design, and verification.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant AppShell as "_app.tsx"
  participant RuntimeBootstrap
  participant SessionStorage
  participant TldwConfig

  Browser->>RuntimeBootstrap: First load, bootstrap()
  RuntimeBootstrap->>TldwConfig: read stored apiKey
  RuntimeBootstrap->>TldwConfig: scrub apiKey
  RuntimeBootstrap->>SessionStorage: writeRuntimeSessionApiKey(manualKey)
  RuntimeBootstrap->>RuntimeBootstrap: set in-memory runtime override

  Browser->>RuntimeBootstrap: Second hard reload, bootstrap()
  RuntimeBootstrap->>SessionStorage: readRuntimeSessionApiKey()
  SessionStorage-->>RuntimeBootstrap: manualKey
  RuntimeBootstrap->>RuntimeBootstrap: restore runtime override
  Browser->>AppShell: refreshAuthState()
  AppShell->>RuntimeBootstrap: getRuntimeApiKey()/getRuntimeApiBearer()
  RuntimeBootstrap-->>AppShell: manualKey present
  AppShell-->>Browser: render authenticated shell (header + sidebar)
Loading

Poem

Hop, hop, reload—the key was gone,
but I tucked it in session before dawn. 🐰
Now header and sidebar both stay in view,
no more re-typing, just carrots and you.
A rabbit's small stash, saved for round two! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes match #2590 by persisting runtime auth in sessionStorage and counting runtime auth in the shell gate.
Out of Scope Changes check ✅ Passed The added task record and tests are directly related to the auth persistence fix and do not appear out of scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately summarizes the main change: preserving manual auth across hard reloads.
Description check ✅ Passed The description covers the change, rationale, and test plan, and includes the key validation details despite not matching the template headings exactly.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-webui-auth-hard-reload

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request addresses an issue where manually entered single-user WebUI authentication is lost on a second hard reload. It introduces a sessionStorage-backed runtime auth bridge to persist the API key across reloads within the same browser session, even after tldwConfig is scrubbed. Additionally, the shell auth gate is updated to recognize these runtime credentials, and corresponding regression tests are added. Feedback on the implementation suggests relaxing the fallback condition to sessionStorage by checking for the absence of a valid existingSingleUserKey rather than strictly checking if existing.apiKey is undefined, which could fail if the key is an empty string or placeholder.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/tldw-frontend/extension/shims/runtime-bootstrap.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix WebUI: preserve manual single-user auth across hard reloads

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Persist manually entered single-user API key for the current browser session via sessionStorage.
• Rehydrate runtime auth after hardened bootstrap scrubs tldwConfig from localStorage.
• Treat runtime auth as “authenticated” for the WebUI shell and add regression tests.
Diagram

graph TD
  A["Hard reload"] --> B["runtime-bootstrap.ts"] --> C{"Choose auth"} --> D["runtime-auth override"] --> E["authStorage getters"] --> F["_app shell gate"]
  B --> G[("localStorage tldwConfig\n(scrubbed)")]
  B --> H[("sessionStorage\nruntime key")]
  C --> H --> D

  subgraph Legend
    direction LR
    _proc["Module/process"] ~~~ _dec{"Decision"} ~~~ _store[("Browser storage")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep manual key only in memory
  • ➕ No storage of secrets in Web Storage APIs
  • ➕ Simplest security story
  • ➖ Does not survive any hard reload (fails the reported regression)
  • ➖ Poor UX: forces re-entry after refresh/crash
2. Store manual key back into localStorage (tldwConfig)
  • ➕ Survives browser restarts and multiple reloads
  • ➕ Simpler rehydration path
  • ➖ Reintroduces durable clear-text secrets (explicitly avoided by scrubber)
  • ➖ Expands compromise window beyond a single session
3. Server-issued session cookie / token exchange
  • ➕ Avoids storing raw API keys in the browser
  • ➕ Can be hardened with HttpOnly/SameSite and rotation
  • ➖ Requires backend/API changes and a token-issuance flow
  • ➖ Higher implementation and operational complexity

Recommendation: Proceed with the PR’s sessionStorage bridge approach. It meets the UX requirement (survive hard reloads within the same browser session) while preserving the security intent of scrubbing durable localStorage config. The added shell-gate runtime checks align UI chrome state with the auth actually used for requests.

Files changed (5) +170 / -8

Bug fix (2) +58 / -8
runtime-bootstrap.tsBridge manual single-user key into sessionStorage for reload rehydration +49/-6

Bridge manual single-user key into sessionStorage for reload rehydration

• Introduces sessionStorage read/write helpers for a session-only single-user API key, with normalization and placeholder filtering. Updates env/config seeding to rehydrate runtime overrides from sessionStorage after localStorage scrubbing, and clears the session key when auth mode isn’t single-user or env auth is opted out.

apps/tldw-frontend/extension/shims/runtime-bootstrap.ts

_app.tsxInclude runtime auth in the WebUI shell auth gate +9/-2

Include runtime auth in the WebUI shell auth gate

• Expands the app-shell authentication gate to consider runtime API key and bearer material in addition to build-time/env auth. This keeps header/sidebar chrome consistent with runtime-authenticated request behavior.

apps/tldw-frontend/pages/_app.tsx

Tests (2) +63 / -0
app-layout.test.tsxAssert shell chrome treats runtime auth as authenticated +31/-0

Assert shell chrome treats runtime auth as authenticated

• Mocks runtime API key retrieval and adds a regression test ensuring header/sidebar remain visible when credentials exist only as a runtime override. Resets the runtime mock between tests to avoid leakage.

apps/tldw-frontend/tests/app/app-layout.test.tsx

runtime-bootstrap.test.tsRegression test: manual key survives a second hard reload +32/-0

Regression test: manual key survives a second hard reload

• Clears sessionStorage during teardown and adds a test that simulates two consecutive module bootstraps. Verifies the manual single-user key is preserved for the session while the persisted tldwConfig remains scrubbed.

apps/tldw-frontend/tests/extension/runtime-bootstrap.test.ts

Documentation (1) +49 / -0
task-12127 - Fix-WebUI-auth-persistence-after-runtime-credential-scrub.mdAdd task record for #2590 auth persistence fix +49/-0

Add task record for #2590 auth persistence fix

• Adds a backlog/task markdown entry documenting the issue, acceptance criteria, implementation notes, and verification steps for preserving manual auth across hard reloads without restoring durable secrets.

backlog/tasks/task-12127 - Fix-WebUI-auth-persistence-after-runtime-credential-scrub.md

@qodo-code-review

qodo-code-review Bot commented Jul 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 74 rules

Grey Divider


Action required

1. Session rehydrate blocked ✓ Resolved 🐞 Bug ≡ Correctness
Description
seedTldwConfigFromEnv only rehydrates the session-stored single-user API key when
existing.apiKey is strictly undefined, so a persisted apiKey: "" prevents restoring runtime
auth on hard reload. This can reintroduce hard-reload auth loss for users who saved Settings while
the API key field was blank.
Code

apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[R545-560]

+    const existingAuthMode = existing?.authMode || "single-user"
+    const rawExistingSingleUserKey =
+      existingAuthMode === "single-user" && typeof existing?.apiKey === "string"
        ? normalizeApiKey(existing.apiKey)
        : null
+    const existingSingleUserKey =
+      rawExistingSingleUserKey && !isPlaceholderApiKey(rawExistingSingleUserKey)
+        ? rawExistingSingleUserKey
+        : null
+    const sessionSingleUserKey =
+      existing &&
+      existingAuthMode === "single-user" &&
+      typeof existing.apiKey === "undefined"
+        ? readRuntimeSessionApiKey()
+        : null
+    const manualSingleUserKey = existingSingleUserKey || sessionSingleUserKey
Evidence
The bootstrap’s session fallback only runs when existing.apiKey is undefined. The Settings save
handler can persist apiKey as an empty string (default form value + `config.apiKey =
values.apiKey), and updateConfig` persists that value; after that, bootstrap will skip reading the
session key and runtime auth won’t be restored.

apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[540-573]
apps/packages/ui/src/components/Option/Settings/tldw.tsx[155-184]
apps/packages/ui/src/components/Option/Settings/tldw.tsx[823-831]
apps/packages/ui/src/services/tldw/TldwApiClient.ts[1747-1752]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The sessionStorage fallback for manual single-user keys is gated on `typeof existing.apiKey === "undefined"`. If `tldwConfig` contains `apiKey: ""` (or another non-valid/placeholder string), the session key is not read, so runtime auth is not restored after a hard reload.

## Issue Context
Some config write paths can persist `apiKey` as an empty string (e.g., Settings form save), which makes `typeof existing.apiKey === "string"` and blocks the session fallback even though the manual key may still be present in sessionStorage.

## Fix Focus Areas
- apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[545-573]

## Suggested fix approach
- Compute `existingSingleUserKey` as you already do.
- Change the session fallback condition to trigger whenever we are in `single-user` mode and there is **no valid non-placeholder key** in config (not only when the property is missing), e.g.:
 - `const sessionSingleUserKey = existing && existingAuthMode === "single-user" && !existingSingleUserKey ? readRuntimeSessionApiKey() : null`
- Optionally, if you need to preserve an explicit “user cleared key” intent, handle that explicitly (e.g., on an explicit logout/clear action) rather than relying on `apiKey: ""` being present.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. SessionStorage errors swallowed ✓ Resolved 📘 Rule violation ☼ Reliability
Description
readRuntimeSessionApiKey and writeRuntimeSessionApiKey catch all exceptions and then
return/continue without logging or rethrowing, which can hide real runtime failures. This makes auth
bootstrap behavior harder to debug and may mask unexpected errors in production.
Code

apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[R338-362]

+const readRuntimeSessionApiKey = (): string | null => {
+  if (typeof window === "undefined") return null
+  try {
+    const key = normalizeApiKey(
+      window.sessionStorage.getItem(RUNTIME_SESSION_SINGLE_USER_API_KEY)
+    )
+    return key && !isPlaceholderApiKey(key) ? key : null
+  } catch {
+    return null
+  }
+}
+
+const writeRuntimeSessionApiKey = (value?: string | null): void => {
+  if (typeof window === "undefined") return
+  const key = normalizeApiKey(value)
+  try {
+    if (key && !isPlaceholderApiKey(key)) {
+      // codeql[js/clear-text-storage-of-sensitive-data]: sessionStorage keeps a user-entered single-user key only for the current browser session after localStorage tldwConfig is scrubbed.
+      window.sessionStorage.setItem(RUNTIME_SESSION_SINGLE_USER_API_KEY, key)
+    } else {
+      window.sessionStorage.removeItem(RUNTIME_SESSION_SINGLE_USER_API_KEY)
+    }
+  } catch {
+    // Best-effort only; runtime auth still works for the current load.
+  }
Evidence
PR Compliance ID 224227 forbids silently swallowing exceptions without logging, rethrowing, or a
narrowly scoped, justified suppression. The added code uses broad catch { ... } blocks that return
null or do nothing, suppressing any thrown error from sessionStorage access.

Rule 224227: Do not silently swallow exceptions
apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[338-362]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Broad `catch { ... }` blocks in sessionStorage access silently swallow exceptions.

## Issue Context
The compliance rule requires exceptions to be logged/re-raised, or (if intentionally ignored) to be narrowly scoped and clearly justified. Current code suppresses all exceptions in `readRuntimeSessionApiKey` and `writeRuntimeSessionApiKey`, which can mask unexpected failures.

## Fix Focus Areas
- apps/tldw-frontend/extension/shims/runtime-bootstrap.ts[338-362]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread apps/tldw-frontend/extension/shims/runtime-bootstrap.ts
Comment thread apps/tldw-frontend/extension/shims/runtime-bootstrap.ts Outdated
@rmusser01 rmusser01 force-pushed the codex/fix-webui-auth-hard-reload branch from a19d2ee to 040a37f Compare July 4, 2026 00:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/tldw-frontend/extension/shims/runtime-bootstrap.ts`:
- Around line 571-572: Add a focused regression test for the new session-key
clearing branch in runtime-bootstrap.ts. Extend runtime-bootstrap.test.ts to
exercise the `else if (existing && existingAuthMode !== "single-user")` path in
`runtimeBootstrap`, and assert that `writeRuntimeSessionApiKey(null)` is called
when the auth mode switches away from single-user while an existing session is
present. Keep the existing single-user rehydration coverage intact and add a
separate case specifically for the mode-switch clear behavior.
- Around line 545-572: The manual single-user key precedence logic in
runtime-bootstrap is correct but too dense; extract the derivation into a small
helper such as deriveManualSingleUserKey(existing, envAuthOptedOut) so the main
branch is easier to read. Move the
existingAuthMode/rawExistingSingleUserKey/existingSingleUserKey/sessionSingleUserKey/manualSingleUserKey
decision flow into that helper, then keep the
setRuntimeApiKey/setRuntimeSingleUserApiKeyOverride/writeRuntimeSessionApiKey
branch focused on applying the result.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 02b35937-641e-4d68-916e-a978811ecb34

📥 Commits

Reviewing files that changed from the base of the PR and between f2d9be9 and a19d2ee.

📒 Files selected for processing (5)
  • apps/tldw-frontend/__tests__/app/app-layout.test.tsx
  • apps/tldw-frontend/__tests__/extension/runtime-bootstrap.test.ts
  • apps/tldw-frontend/extension/shims/runtime-bootstrap.ts
  • apps/tldw-frontend/pages/_app.tsx
  • backlog/tasks/task-12127 - Fix-WebUI-auth-persistence-after-runtime-credential-scrub.md

Comment thread apps/tldw-frontend/extension/shims/runtime-bootstrap.ts Outdated
Comment thread apps/tldw-frontend/extension/shims/runtime-bootstrap.ts
Fixes #2590 by keeping manually entered single-user auth available in sessionStorage after tldwConfig is scrubbed. Count runtime auth material in the WebUI shell gate and add regression coverage for the second hard reload path.
@rmusser01 rmusser01 force-pushed the codex/fix-webui-auth-hard-reload branch from 040a37f to 626447b Compare July 4, 2026 00:25
@rmusser01 rmusser01 merged commit 638aad2 into dev Jul 4, 2026
17 of 18 checks passed
@rmusser01 rmusser01 deleted the codex/fix-webui-auth-hard-reload branch July 4, 2026 00:57
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.

1 participant