Performance insights, Goals & Events, 2FA improvements, auth fixes #36

Merged
uz1mani merged 12 commits from staging into main 2026-02-25 19:41:07 +00:00
uz1mani commented 2026-02-25 19:39:34 +00:00 (Migrated from github.com)

Work Item

Summary

  • Add performance insights (Core Web Vitals) and Goals & Events tracking.
  • Improve 2FA: recovery codes download/regeneration and password confirmation when disabling.
  • Fix auth issues: sign-in after inactivity and frequent re-login by fixing token refresh.

Changes

Added

  • Performance insights — Core Web Vitals widget (LCP, CLS, INP) in dashboard; toggle in Site Settings → Data & Privacy.
  • Goals & Events — Custom goals via Site Settings → Goals & Events; pulse.track() in snippet; goal counts on dashboard.
  • 2FA recovery codes backup — Regenerate codes with password confirmation from Settings; download .txt file; regeneration invalidates existing codes.

Fixed

  • Sign-in after inactivity — Middleware no longer redirects away from login due to stale refresh cookies; only valid access token triggers redirect, so OAuth sign-in completes when session is expired.
  • Frequent re-login — Access token refresh on next page load when it expires (15 min inactivity); users stay signed in for up to 30 days.
  • 2FA disable security — Disabling 2FA requires password confirmation; derived password sent to backend to prevent session hijacking.

Test Plan

  • Enable performance insights in Site Settings → Data & Privacy; confirm Core Web Vitals widget in dashboard.
  • Add a goal in Site Settings → Goals & Events; call pulse.track() from snippet; confirm goal counts on dashboard.
  • Enable 2FA; regenerate recovery codes; confirm .txt download; verify old codes are invalid.
  • Leave session idle 15+ minutes; reload page; confirm auto sign-in without re-authenticating.
  • Sign out and wait; click "Sign in"; confirm OAuth completes without being redirected away.
  • Confirm password is required when disabling 2FA.
## Work Item ## Summary - Add performance insights (Core Web Vitals) and Goals & Events tracking. - Improve 2FA: recovery codes download/regeneration and password confirmation when disabling. - Fix auth issues: sign-in after inactivity and frequent re-login by fixing token refresh. ## Changes **Added** - **Performance insights** — Core Web Vitals widget (LCP, CLS, INP) in dashboard; toggle in Site Settings → Data & Privacy. - **Goals & Events** — Custom goals via Site Settings → Goals & Events; `pulse.track()` in snippet; goal counts on dashboard. - **2FA recovery codes backup** — Regenerate codes with password confirmation from Settings; download `.txt` file; regeneration invalidates existing codes. **Fixed** - **Sign-in after inactivity** — Middleware no longer redirects away from login due to stale refresh cookies; only valid access token triggers redirect, so OAuth sign-in completes when session is expired. - **Frequent re-login** — Access token refresh on next page load when it expires (15 min inactivity); users stay signed in for up to 30 days. - **2FA disable security** — Disabling 2FA requires password confirmation; derived password sent to backend to prevent session hijacking. ## Test Plan - [x] Enable performance insights in Site Settings → Data & Privacy; confirm Core Web Vitals widget in dashboard. - [x] Add a goal in Site Settings → Goals & Events; call `pulse.track()` from snippet; confirm goal counts on dashboard. - [x] Enable 2FA; regenerate recovery codes; confirm `.txt` download; verify old codes are invalid. - [x] Leave session idle 15+ minutes; reload page; confirm auto sign-in without re-authenticating. - [x] Sign out and wait; click "Sign in"; confirm OAuth completes without being redirected away. - [x] Confirm password is required when disabling 2FA.
greptile-apps[bot] commented 2026-02-25 19:43:13 +00:00 (Migrated from github.com)

Greptile Summary

This PR delivers several improvements: automatic token refresh to fix frequent re-login issues, middleware fix for post-inactivity sign-in, password-confirmed 2FA disable/recovery code regeneration, WebAuthn/passkey support wiring, and changelog/dependency updates.

  • Auth flow refactoring: The OAuth callback now supports two paths — a full PKCE+state flow for app-initiated login, and a relaxed "session-authorized" flow for the Ciphera auth hub that bypasses both state validation and PKCE. This introduces a CSRF risk that should be addressed before merging.
  • Token refresh on init: AuthProvider now attempts a token refresh when the access token is expired but a refresh token exists, fixing the 15-minute inactivity logout. The fetch call should be wrapped in try/catch to prevent unhandled errors from leaving the app in a permanent loading state.
  • Middleware fix: Correctly changes the auth-only route redirect to check only access_token instead of any session cookie, preventing stale refresh tokens from blocking login.
  • 2FA security: disable2FA and regenerateRecoveryCodes now require a derived password, preventing session-hijacking attacks from stripping 2FA.
  • WebAuthn: New lib/api/webauthn.ts with clean register/list/delete passkey operations wired into ProfileSettings.

Confidence Score: 2/5

  • This PR introduces an OAuth CSRF vulnerability by skipping state validation for the auth-hub flow that should be addressed before merging.
  • The middleware fix and token refresh logic are sound, and the 2FA password confirmation is a good security improvement. However, the OAuth callback changes introduce a significant security concern: bypassing both CSRF state validation and PKCE for the "session-authorized" flow creates an exploitable login CSRF vector. The unhandled fetch error in AuthProvider could also cause UX issues. These issues lower confidence despite the otherwise solid changes.
  • Pay close attention to app/auth/callback/page.tsx (CSRF risk from skipped state validation) and lib/auth/context.tsx (unhandled fetch error in refresh logic).

Important Files Changed

Filename Overview
app/auth/callback/page.tsx Removes direct token passing, adds dual-path OAuth callback: full PKCE+state for app-initiated flows, and a relaxed path for auth-hub-initiated flows that skips state/PKCE validation — introduces CSRF risk for the latter path.
app/actions/auth.ts Allows null code_verifier for session-authorized flow, sending empty string to auth server. This effectively disables PKCE for the auth-hub flow, which is a deliberate trade-off but reduces security.
lib/auth/context.tsx Adds automatic token refresh on init when access_token is expired but refresh_token exists, fixing the 15-minute inactivity logout. Implementation is sound and uses existing /api/auth/refresh route.
middleware.ts Changes auth-only route redirect to check only access_token (not refresh_token), correctly fixing the stale-cookie redirect loop that prevented sign-in after inactivity.
lib/api/2fa.ts Adds password confirmation parameter to disable2FA and regenerateRecoveryCodes, improving security by requiring re-authentication for sensitive 2FA operations.
lib/api/webauthn.ts New WebAuthn/Passkey API client with register, list, and delete operations using @simplewebauthn/browser. Clean implementation with proper URL encoding for credential deletion.
components/settings/ProfileSettings.tsx Passes new webauthn API functions (registerPasskey, listPasskeys, deletePasskey) to the shared UI component. Straightforward prop wiring.
CHANGELOG.md Adds changelog entries for performance insights, goals & events, 2FA recovery codes, and bug fixes. Minor wording improvements to existing entries.
package.json Version bump to 0.11.1-alpha, updates @ciphera-net/ui to ^0.0.64, adds @simplewebauthn/browser ^13.2.2 for WebAuthn support.
package-lock.json Lock file updates reflecting new @simplewebauthn/browser dependency and @ciphera-net/ui version bump.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant MW as Middleware
    participant AP as AuthProvider
    participant RefreshAPI as Refresh Endpoint
    participant AuthSrv as Auth Server
    participant CB as OAuth Callback

    Note over User,AuthSrv: Flow 1: Auto-refresh on page load (fixes inactivity logout)
    User->>Browser: Navigate to protected page
    Browser->>MW: Request with refresh cookie only
    MW->>Browser: Allow through (has refresh cookie)
    Browser->>AP: init()
    AP->>AP: getSession returns null
    AP->>RefreshAPI: POST refresh request
    RefreshAPI->>AuthSrv: Forward refresh token
    AuthSrv-->>RefreshAPI: New tokens
    RefreshAPI-->>AP: 200 OK (cookies updated)
    AP->>AP: getSession returns user
    AP-->>Browser: User authenticated

    Note over User,AuthSrv: Flow 2: Auth-hub sign-in (no PKCE or state)
    User->>AuthSrv: Sign in via hub
    AuthSrv->>CB: Redirect with auth code
    CB->>CB: No stored state/verifier - skip checks
    CB->>AuthSrv: Exchange code (no verifier)
    AuthSrv-->>CB: Tokens issued
    CB-->>Browser: Logged in

Last reviewed commit: 2cb8ffd

<h3>Greptile Summary</h3> This PR delivers several improvements: automatic token refresh to fix frequent re-login issues, middleware fix for post-inactivity sign-in, password-confirmed 2FA disable/recovery code regeneration, WebAuthn/passkey support wiring, and changelog/dependency updates. - **Auth flow refactoring**: The OAuth callback now supports two paths — a full PKCE+state flow for app-initiated login, and a relaxed "session-authorized" flow for the Ciphera auth hub that bypasses both state validation and PKCE. This introduces a CSRF risk that should be addressed before merging. - **Token refresh on init**: `AuthProvider` now attempts a token refresh when the access token is expired but a refresh token exists, fixing the 15-minute inactivity logout. The `fetch` call should be wrapped in try/catch to prevent unhandled errors from leaving the app in a permanent loading state. - **Middleware fix**: Correctly changes the auth-only route redirect to check only `access_token` instead of any session cookie, preventing stale refresh tokens from blocking login. - **2FA security**: `disable2FA` and `regenerateRecoveryCodes` now require a derived password, preventing session-hijacking attacks from stripping 2FA. - **WebAuthn**: New `lib/api/webauthn.ts` with clean register/list/delete passkey operations wired into `ProfileSettings`. <h3>Confidence Score: 2/5</h3> - This PR introduces an OAuth CSRF vulnerability by skipping state validation for the auth-hub flow that should be addressed before merging. - The middleware fix and token refresh logic are sound, and the 2FA password confirmation is a good security improvement. However, the OAuth callback changes introduce a significant security concern: bypassing both CSRF state validation and PKCE for the "session-authorized" flow creates an exploitable login CSRF vector. The unhandled fetch error in AuthProvider could also cause UX issues. These issues lower confidence despite the otherwise solid changes. - Pay close attention to `app/auth/callback/page.tsx` (CSRF risk from skipped state validation) and `lib/auth/context.tsx` (unhandled fetch error in refresh logic). <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | app/auth/callback/page.tsx | Removes direct token passing, adds dual-path OAuth callback: full PKCE+state for app-initiated flows, and a relaxed path for auth-hub-initiated flows that skips state/PKCE validation — introduces CSRF risk for the latter path. | | app/actions/auth.ts | Allows null code_verifier for session-authorized flow, sending empty string to auth server. This effectively disables PKCE for the auth-hub flow, which is a deliberate trade-off but reduces security. | | lib/auth/context.tsx | Adds automatic token refresh on init when access_token is expired but refresh_token exists, fixing the 15-minute inactivity logout. Implementation is sound and uses existing /api/auth/refresh route. | | middleware.ts | Changes auth-only route redirect to check only access_token (not refresh_token), correctly fixing the stale-cookie redirect loop that prevented sign-in after inactivity. | | lib/api/2fa.ts | Adds password confirmation parameter to disable2FA and regenerateRecoveryCodes, improving security by requiring re-authentication for sensitive 2FA operations. | | lib/api/webauthn.ts | New WebAuthn/Passkey API client with register, list, and delete operations using @simplewebauthn/browser. Clean implementation with proper URL encoding for credential deletion. | | components/settings/ProfileSettings.tsx | Passes new webauthn API functions (registerPasskey, listPasskeys, deletePasskey) to the shared UI component. Straightforward prop wiring. | | CHANGELOG.md | Adds changelog entries for performance insights, goals & events, 2FA recovery codes, and bug fixes. Minor wording improvements to existing entries. | | package.json | Version bump to 0.11.1-alpha, updates @ciphera-net/ui to ^0.0.64, adds @simplewebauthn/browser ^13.2.2 for WebAuthn support. | | package-lock.json | Lock file updates reflecting new @simplewebauthn/browser dependency and @ciphera-net/ui version bump. | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant Browser participant MW as Middleware participant AP as AuthProvider participant RefreshAPI as Refresh Endpoint participant AuthSrv as Auth Server participant CB as OAuth Callback Note over User,AuthSrv: Flow 1: Auto-refresh on page load (fixes inactivity logout) User->>Browser: Navigate to protected page Browser->>MW: Request with refresh cookie only MW->>Browser: Allow through (has refresh cookie) Browser->>AP: init() AP->>AP: getSession returns null AP->>RefreshAPI: POST refresh request RefreshAPI->>AuthSrv: Forward refresh token AuthSrv-->>RefreshAPI: New tokens RefreshAPI-->>AP: 200 OK (cookies updated) AP->>AP: getSession returns user AP-->>Browser: User authenticated Note over User,AuthSrv: Flow 2: Auth-hub sign-in (no PKCE or state) User->>AuthSrv: Sign in via hub AuthSrv->>CB: Redirect with auth code CB->>CB: No stored state/verifier - skip checks CB->>AuthSrv: Exchange code (no verifier) AuthSrv-->>CB: Tokens issued CB-->>Browser: Logged in ``` <sub>Last reviewed commit: 2cb8ffd</sub>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-25 19:43:19 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

10 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

<sub>10 files reviewed, 3 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-25 19:43:18 +00:00

Empty code_verifier disables PKCE protection

When codeVerifier is null (session-authorized flow), this sends code_verifier: '' to the token endpoint. For PKCE to provide any security, the auth server must reject token requests with an empty verifier when the authorization request included a code_challenge. If the auth server does not enforce this (i.e., it accepts empty verifiers), then PKCE is effectively optional, and authorization code interception attacks become possible for the auth-hub flow.

Please verify that the auth server either:

  1. Rejects empty code_verifier when a code_challenge was provided during authorization, or
  2. Treats this as a non-PKCE flow and applies alternative protections (e.g., confidential client authentication).

If the auth server silently accepts the empty verifier, this is a security gap.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/actions/auth.ts
Line: 36-49

Comment:
**Empty `code_verifier` disables PKCE protection**

When `codeVerifier` is `null` (session-authorized flow), this sends `code_verifier: ''` to the token endpoint. For PKCE to provide any security, the auth server must reject token requests with an empty verifier when the authorization request included a `code_challenge`. If the auth server does not enforce this (i.e., it accepts empty verifiers), then PKCE is effectively optional, and authorization code interception attacks become possible for the auth-hub flow.

Please verify that the auth server either:
1. Rejects empty `code_verifier` when a `code_challenge` was provided during authorization, or
2. Treats this as a non-PKCE flow and applies alternative protections (e.g., confidential client authentication).

If the auth server silently accepts the empty verifier, this is a security gap.

How can I resolve this? If you propose a fix, please make it concise.
**Empty `code_verifier` disables PKCE protection** When `codeVerifier` is `null` (session-authorized flow), this sends `code_verifier: ''` to the token endpoint. For PKCE to provide any security, the auth server must reject token requests with an empty verifier when the authorization request included a `code_challenge`. If the auth server does not enforce this (i.e., it accepts empty verifiers), then PKCE is effectively optional, and authorization code interception attacks become possible for the auth-hub flow. Please verify that the auth server either: 1. Rejects empty `code_verifier` when a `code_challenge` was provided during authorization, or 2. Treats this as a non-PKCE flow and applies alternative protections (e.g., confidential client authentication). If the auth server silently accepts the empty verifier, this is a security gap. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/actions/auth.ts Line: 36-49 Comment: **Empty `code_verifier` disables PKCE protection** When `codeVerifier` is `null` (session-authorized flow), this sends `code_verifier: ''` to the token endpoint. For PKCE to provide any security, the auth server must reject token requests with an empty verifier when the authorization request included a `code_challenge`. If the auth server does not enforce this (i.e., it accepts empty verifiers), then PKCE is effectively optional, and authorization code interception attacks become possible for the auth-hub flow. Please verify that the auth server either: 1. Rejects empty `code_verifier` when a `code_challenge` was provided during authorization, or 2. Treats this as a non-PKCE flow and applies alternative protections (e.g., confidential client authentication). If the auth server silently accepts the empty verifier, this is a security gap. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-25 19:43:17 +00:00

Skipping state validation opens a CSRF vector

When storedState or codeVerifier are absent (the "session-authorized flow"), both the OAuth state check and PKCE verification are bypassed entirely. This makes the callback vulnerable to login CSRF: an attacker can craft a link like /auth/callback?code=ATTACKER_CODE and trick a victim into visiting it, logging the victim into the attacker's account (a classic OAuth login CSRF attack).

The state parameter exists specifically to prevent this. Even for the auth-hub-initiated flow, the hub should be able to pass a state value that Pulse can verify (e.g., a server-side nonce or a signed token), or Pulse should generate and store its own state before redirecting to the hub.

Consider one of these mitigations:

  1. Have the auth hub include a state parameter that Pulse can validate (e.g. a signed value from the hub).
  2. Generate and store a state nonce on Pulse's side before the hub redirect, then validate it in this callback.
  3. At minimum, if no state is present, verify the code was issued very recently (short TTL) and for the correct redirect_uri on the server side, and document the accepted risk.
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/auth/callback/page.tsx
Line: 59-69

Comment:
**Skipping state validation opens a CSRF vector**

When `storedState` or `codeVerifier` are absent (the "session-authorized flow"), both the OAuth `state` check and PKCE verification are bypassed entirely. This makes the callback vulnerable to **login CSRF**: an attacker can craft a link like `/auth/callback?code=ATTACKER_CODE` and trick a victim into visiting it, logging the victim into the attacker's account (a classic OAuth login CSRF attack).

The `state` parameter exists specifically to prevent this. Even for the auth-hub-initiated flow, the hub should be able to pass a `state` value that Pulse can verify (e.g., a server-side nonce or a signed token), or Pulse should generate and store its own `state` before redirecting to the hub.

Consider one of these mitigations:
1. Have the auth hub include a `state` parameter that Pulse can validate (e.g. a signed value from the hub).
2. Generate and store a `state` nonce on Pulse's side before the hub redirect, then validate it in this callback.
3. At minimum, if no `state` is present, verify the `code` was issued very recently (short TTL) and for the correct `redirect_uri` on the server side, and document the accepted risk.

How can I resolve this? If you propose a fix, please make it concise.
**Skipping state validation opens a CSRF vector** When `storedState` or `codeVerifier` are absent (the "session-authorized flow"), both the OAuth `state` check and PKCE verification are bypassed entirely. This makes the callback vulnerable to **login CSRF**: an attacker can craft a link like `/auth/callback?code=ATTACKER_CODE` and trick a victim into visiting it, logging the victim into the attacker's account (a classic OAuth login CSRF attack). The `state` parameter exists specifically to prevent this. Even for the auth-hub-initiated flow, the hub should be able to pass a `state` value that Pulse can verify (e.g., a server-side nonce or a signed token), or Pulse should generate and store its own `state` before redirecting to the hub. Consider one of these mitigations: 1. Have the auth hub include a `state` parameter that Pulse can validate (e.g. a signed value from the hub). 2. Generate and store a `state` nonce on Pulse's side before the hub redirect, then validate it in this callback. 3. At minimum, if no `state` is present, verify the `code` was issued very recently (short TTL) and for the correct `redirect_uri` on the server side, and document the accepted risk. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/auth/callback/page.tsx Line: 59-69 Comment: **Skipping state validation opens a CSRF vector** When `storedState` or `codeVerifier` are absent (the "session-authorized flow"), both the OAuth `state` check and PKCE verification are bypassed entirely. This makes the callback vulnerable to **login CSRF**: an attacker can craft a link like `/auth/callback?code=ATTACKER_CODE` and trick a victim into visiting it, logging the victim into the attacker's account (a classic OAuth login CSRF attack). The `state` parameter exists specifically to prevent this. Even for the auth-hub-initiated flow, the hub should be able to pass a `state` value that Pulse can verify (e.g., a server-side nonce or a signed token), or Pulse should generate and store its own `state` before redirecting to the hub. Consider one of these mitigations: 1. Have the auth hub include a `state` parameter that Pulse can validate (e.g. a signed value from the hub). 2. Generate and store a `state` nonce on Pulse's side before the hub redirect, then validate it in this callback. 3. At minimum, if no `state` is present, verify the `code` was issued very recently (short TTL) and for the correct `redirect_uri` on the server side, and document the accepted risk. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -115,1 +124,4 @@
}
}
if (session) {
greptile-apps[bot] (Migrated from github.com) commented 2026-02-25 19:43:19 +00:00

Silent refresh failure may hide errors

When the refresh request fails (non-ok response or network error), the code silently falls through to the else branch which sets user to null. This is functionally correct, but a fetch exception (e.g., network failure) will propagate as an unhandled error from the init function, which could cause the loading state to never be set to false, leaving the app in a permanent loading state.

Consider wrapping the refresh attempt in a try/catch:

        if (!session && typeof window !== 'undefined') {
          try {
            const refreshRes = await fetch('/api/auth/refresh', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              credentials: 'include',
            })
            if (refreshRes.ok) {
              session = await getSessionAction()
            }
          } catch {
            // Refresh failed (network error) — fall through to no-session handling
          }
        }
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/auth/context.tsx
Line: 116-125

Comment:
**Silent refresh failure may hide errors**

When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state.

Consider wrapping the refresh attempt in a try/catch:

```suggestion
        if (!session && typeof window !== 'undefined') {
          try {
            const refreshRes = await fetch('/api/auth/refresh', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              credentials: 'include',
            })
            if (refreshRes.ok) {
              session = await getSessionAction()
            }
          } catch {
            // Refresh failed (network error) — fall through to no-session handling
          }
        }
```

How can I resolve this? If you propose a fix, please make it concise.
**Silent refresh failure may hide errors** When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state. Consider wrapping the refresh attempt in a try/catch: ```suggestion if (!session && typeof window !== 'undefined') { try { const refreshRes = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', }) if (refreshRes.ok) { session = await getSessionAction() } } catch { // Refresh failed (network error) — fall through to no-session handling } } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/auth/context.tsx Line: 116-125 Comment: **Silent refresh failure may hide errors** When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state. Consider wrapping the refresh attempt in a try/catch: ```suggestion if (!session && typeof window !== 'undefined') { try { const refreshRes = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', }) if (refreshRes.ok) { session = await getSessionAction() } } catch { // Refresh failed (network error) — fall through to no-session handling } } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
Sign in to join this conversation.
No description provided.