Просмотр исходного кода

Issue #257 Fix:
- Changed the 401 handling in request() to only clear the auth token when the error message indicates the token is actually invalid
- Previously: ANY 401 response cleared the token (could cause cascading failures)
- Now: Only clears on specific messages like "Token has expired", "Could not validate credentials", etc.
- "Authentication required" (which might be a timing issue) no longer clears the token

Closes #257

maziggy 3 месяцев назад
Родитель
Сommit
85598346f7

+ 25 - 2
frontend/src/__tests__/api/client.test.ts

@@ -95,11 +95,11 @@ describe('API Client Auth Header', () => {
     expect(capturedHeaders!.get('Authorization')).toBeNull();
   });
 
-  it('clears token on 401 Unauthorized response', async () => {
+  it('clears token on 401 with invalid token message', async () => {
     server.use(
       http.get('/api/v1/settings/spoolman', () => {
         return HttpResponse.json(
-          { detail: 'Not authenticated' },
+          { detail: 'Could not validate credentials' },
           { status: 401 }
         );
       })
@@ -117,6 +117,29 @@ describe('API Client Auth Header', () => {
     expect(getAuthToken()).toBeNull();
     expect(localStorageMock.removeItem).toHaveBeenCalledWith('auth_token');
   });
+
+  it('does not clear token on 401 with generic auth error', async () => {
+    server.use(
+      http.get('/api/v1/settings/spoolman', () => {
+        return HttpResponse.json(
+          { detail: 'Authentication required' },
+          { status: 401 }
+        );
+      })
+    );
+
+    setAuthToken('valid-token');
+    expect(getAuthToken()).toBe('valid-token');
+
+    try {
+      await api.getSpoolmanSettings();
+    } catch {
+      // Expected to throw
+    }
+
+    // Token should NOT be cleared for generic auth errors (might be timing issue)
+    expect(getAuthToken()).toBe('valid-token');
+  });
 });
 
 describe('FormData requests include auth header', () => {

+ 16 - 5
frontend/src/api/client.ts

@@ -39,16 +39,27 @@ async function request<T>(
   });
 
   if (!response.ok) {
-    // Handle 401 Unauthorized - clear token and redirect to login
-    if (response.status === 401) {
-      setAuthToken(null);
-      // Don't throw here - let the auth context handle redirect
-    }
     const error = await response.json().catch(() => ({}));
     const detail = error.detail;
     const message = typeof detail === 'string'
       ? detail
       : (detail ? JSON.stringify(detail) : `HTTP ${response.status}`);
+
+    // Handle 401 Unauthorized - only clear token if it's actually invalid
+    // Don't clear on "Authentication required" which might be a timing issue
+    if (response.status === 401) {
+      const invalidTokenMessages = [
+        'Could not validate credentials',
+        'Token has expired',
+        'User not found or inactive',
+        'Invalid API key',
+        'API key has expired',
+      ];
+      if (invalidTokenMessages.some(m => message.includes(m))) {
+        setAuthToken(null);
+      }
+    }
+
     throw new Error(message);
   }
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-DzQ3-JjU.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-DLGvpmyc.js"></script>
+    <script type="module" crossorigin src="/assets/index-DzQ3-JjU.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-fdAEMOwp.css">
   </head>
   <body>

Некоторые файлы не были показаны из-за большого количества измененных файлов