Unverified Commit 551ee1d8 authored by fiatcode's avatar fiatcode Committed by GitHub

fix(auth): recover session via refresh cookie when localStorage is empty (#5748)

parent be00abe8
...@@ -61,11 +61,10 @@ export const getAccessToken = (): string | null => { ...@@ -61,11 +61,10 @@ export const getAccessToken = (): string | null => {
accessToken = storedToken; accessToken = storedToken;
tokenExpiresAt = expiresAt; tokenExpiresAt = expiresAt;
} }
// Do NOT remove expired tokens here. Callers such as InstanceContext.initialize() // Do NOT remove expired tokens here. getRequestToken() in connect.ts calls
// run concurrently with AuthContext.initialize() via Promise.all. If we eagerly // hasStoredToken() to decide whether to attempt a refresh — if we eagerly delete
// delete the expired token from localStorage, hasStoredToken() (called synchronously // the expired token, it returns null immediately, skipping the refresh and sending
// inside AuthContext.initialize()) finds nothing and skips the refresh attempt, // the request without credentials.
// logging the user out even when the refresh-token cookie is still valid.
// clearAccessToken() handles proper cleanup after a confirmed auth failure or logout. // clearAccessToken() handles proper cleanup after a confirmed auth failure or logout.
} }
} catch (e) { } catch (e) {
......
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react"; import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
import { clearAccessToken, hasStoredToken } from "@/auth-state"; import { clearAccessToken, getAccessToken } from "@/auth-state";
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/connect"; import { authServiceClient, refreshAccessToken, shortcutServiceClient, userServiceClient } from "@/connect";
import { userKeys } from "@/hooks/useUserQueries"; import { userKeys } from "@/hooks/useUserQueries";
import type { Shortcut } from "@/types/proto/api/v1/shortcut_service_pb"; import type { Shortcut } from "@/types/proto/api/v1/shortcut_service_pb";
import type { User, UserSetting_GeneralSetting, UserSetting_WebhooksSetting } from "@/types/proto/api/v1/user_service_pb"; import type { User, UserSetting_GeneralSetting, UserSetting_WebhooksSetting } from "@/types/proto/api/v1/user_service_pb";
...@@ -53,9 +53,21 @@ export function AuthProvider({ children }: { children: ReactNode }) { ...@@ -53,9 +53,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const initialize = useCallback(async () => { const initialize = useCallback(async () => {
setState((prev) => ({ ...prev, isLoading: true })); setState((prev) => ({ ...prev, isLoading: true }));
// If there is no stored token at all, the user is not authenticated. // Try to get or refresh the access token.
// Skip the network call — there is nothing to refresh and no session to restore. // This handles PWA isolated storage scenarios (e.g., iOS Safari) where localStorage
if (!hasStoredToken()) { // may be empty but a valid HTTP-only refresh token cookie still exists.
// getAccessToken() returns a cached token or loads from localStorage if valid.
if (!getAccessToken()) {
try {
await refreshAccessToken();
} catch {
// Refresh failed - no valid session
}
}
// If we still don't have a token after refresh attempt, skip getCurrentUser call
// to avoid unnecessary network request for unauthenticated users.
if (!getAccessToken()) {
setState({ setState({
currentUser: undefined, currentUser: undefined,
userGeneralSetting: undefined, userGeneralSetting: undefined,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment