Commit 4aaebc50 authored by Steven's avatar Steven

fix(web): skip GetCurrentUser on init when no token is stored

When no token exists in sessionStorage, AuthContext.initialize() was
still calling GetCurrentUser, triggering the auth interceptor to attempt
RefreshToken and retry — producing a burst of 5+ auth API calls in under
a second that reverse proxies with rate limiting (e.g. CrowdSec) flag as
brute force.

Add hasStoredToken() to auth-state and bail out of initialize() early
when there is definitively no session to restore. The refresh flow for
expired tokens is preserved since hasStoredToken() checks for presence
regardless of expiry.

Fixes #5647
parent b8bca6ba
...@@ -62,6 +62,19 @@ export const isTokenExpired = (bufferMs: number = 30000): boolean => { ...@@ -62,6 +62,19 @@ export const isTokenExpired = (bufferMs: number = 30000): boolean => {
return new Date() >= new Date(tokenExpiresAt.getTime() - bufferMs); return new Date() >= new Date(tokenExpiresAt.getTime() - bufferMs);
}; };
// Returns true if a token exists in sessionStorage, even if it is expired.
// Used to decide whether to attempt GetCurrentUser on app init — if no token
// was ever stored, the user is definitively not logged in and there is nothing
// to refresh, so we can skip the network round-trip entirely.
export const hasStoredToken = (): boolean => {
if (accessToken) return true;
try {
return !!sessionStorage.getItem(SESSION_TOKEN_KEY);
} catch {
return false;
}
};
export const clearAccessToken = (): void => { export const clearAccessToken = (): void => {
accessToken = null; accessToken = null;
tokenExpiresAt = null; tokenExpiresAt = null;
......
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 } from "@/auth-state"; import { clearAccessToken, hasStoredToken } from "@/auth-state";
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/connect"; import { authServiceClient, 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";
...@@ -52,6 +52,21 @@ export function AuthProvider({ children }: { children: ReactNode }) { ...@@ -52,6 +52,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.
// Skip the network call — there is nothing to refresh and no session to restore.
if (!hasStoredToken()) {
setState({
currentUser: undefined,
userGeneralSetting: undefined,
userWebhooksSetting: undefined,
shortcuts: [],
isInitialized: true,
isLoading: false,
});
return;
}
try { try {
const { user: currentUser } = await authServiceClient.getCurrentUser({}); const { user: currentUser } = await authServiceClient.getCurrentUser({});
......
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