GT AI OS Community v2.0.33 - Add NVIDIA NIM and Nemotron agents
- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2 - Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2 - Made more general-purpose (flexible targets, expanded tools) - Added nemotron-mini-agent.csv for fast local inference via Ollama - Added nemotron-agent.csv for advanced reasoning via Ollama - Added wiki page: Projects for NVIDIA NIMs and Nemotron
This commit is contained in:
301
apps/tenant-app/CHAT-401-FIX-TEST-GUIDE.md
Normal file
301
apps/tenant-app/CHAT-401-FIX-TEST-GUIDE.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Chat 401 Error Handling - Test Guide
|
||||
|
||||
## ✅ Implementation Complete
|
||||
|
||||
All fixes have been implemented to handle expired tokens during chat interactions properly.
|
||||
|
||||
---
|
||||
|
||||
## **What Was Fixed**
|
||||
|
||||
### **Bug 1: JWT Parsing Crash** ✅
|
||||
**File:** `apps/tenant-app/src/services/auth.ts` (lines 170-198)
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
const payload = token.split('.')[1];
|
||||
const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
|
||||
// ❌ Crashes if payload is undefined
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
// Validate input
|
||||
if (!token || typeof token !== 'string') return null;
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return null;
|
||||
const payload = parts[1];
|
||||
if (!payload) return null;
|
||||
// ✅ Safe null checking before accessing properties
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Bug 2: Chat Service 401 Not Triggering Logout** ✅
|
||||
**File:** `apps/tenant-app/src/services/chat-service.ts`
|
||||
|
||||
**A. Early Detection (lines 85-108):**
|
||||
```typescript
|
||||
private getAuthHeaders(): Record<string, string> {
|
||||
// Check token validity BEFORE making request
|
||||
if (!isTokenValid()) {
|
||||
console.warn('ChatService: Token invalid/expired, triggering logout');
|
||||
// Trigger logout immediately
|
||||
import('@/stores/auth-store').then(({ useAuthStore }) => {
|
||||
useAuthStore.getState().logout('expired');
|
||||
});
|
||||
return headers; // No auth header - will get 401
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**B. 401 Response Handling (lines 140-152):**
|
||||
```typescript
|
||||
if (!response.ok) {
|
||||
// Handle 401 - session expired
|
||||
if (response.status === 401) {
|
||||
const { useAuthStore } = await import('@/stores/auth-store');
|
||||
useAuthStore.getState().logout('expired');
|
||||
throw new Error('SESSION_EXPIRED'); // Special error type
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Bug 3: Error Shown in Chat UI** ✅
|
||||
**File:** `apps/tenant-app/src/app/chat/page.tsx` (lines 1235-1266)
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
onError: (error: Error) => {
|
||||
// Shows ALL errors in chat
|
||||
const errorMessage: ChatMessage = {
|
||||
content: `Sorry, I encountered an error: ${error.message}`,
|
||||
// ...
|
||||
};
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
onError: (error: Error) => {
|
||||
// Don't show error message for session expiration
|
||||
if (error.message === 'SESSION_EXPIRED') {
|
||||
console.log('Chat: Session expired, logout triggered');
|
||||
// Clean up state
|
||||
return; // User will be redirected, don't show error
|
||||
}
|
||||
|
||||
// Show error message for other errors
|
||||
const errorMessage: ChatMessage = {
|
||||
content: `Sorry, I encountered an error: ${error.message}`,
|
||||
// ...
|
||||
};
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Quick Test (2 minutes)**
|
||||
|
||||
### **Method 1: Expire Token Before Chat**
|
||||
|
||||
1. **Login** as any user at http://localhost:3002
|
||||
2. **Go to /chat** page
|
||||
3. **Open DevTools Console** and run:
|
||||
```javascript
|
||||
// Set an expired token
|
||||
localStorage.setItem('gt2_token', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NDdhMGM1Ny1iZjJmLTQ3ODItYTZlOC0wMjA1ZTllNDE1MmUiLCJlbWFpbCI6ImRhdmlkQGd0ZWRnZS5haSIsInVzZXJfdHlwZSI6InRlbmFudF9hZG1pbiIsImV4cCI6MTc2Mjk2MzkxOSwiaWF0IjoxNzYyOTYwMzE5fQ.fake_signature');
|
||||
```
|
||||
4. **Type a message** in chat and press Enter
|
||||
5. **Expected Behavior:**
|
||||
- ✅ Console log: "ChatService: Token invalid/expired, triggering logout"
|
||||
- ✅ Immediate redirect to `/login?session_expired=true`
|
||||
- ✅ Red banner: "Your session has expired. Please log in again."
|
||||
- ❌ NO error message in chat
|
||||
- ❌ NO JWT parsing crash
|
||||
|
||||
---
|
||||
|
||||
### **Method 2: Invalid Token During Chat**
|
||||
|
||||
1. **Login** and go to **/chat**
|
||||
2. **Send one message successfully** (to verify chat works)
|
||||
3. **Open DevTools Console** and run:
|
||||
```javascript
|
||||
// Corrupt token mid-chat
|
||||
localStorage.setItem('gt2_token', 'invalid_token');
|
||||
```
|
||||
4. **Send another message**
|
||||
5. **Expected Behavior:**
|
||||
- ✅ No crash
|
||||
- ✅ Console: "ChatService: Token invalid/expired, triggering logout"
|
||||
- ✅ Redirect to login with session expired banner
|
||||
- ❌ NO "Sorry, I encountered an error: HTTP 401..." in chat
|
||||
|
||||
---
|
||||
|
||||
### **Method 3: Test JWT Parsing Protection**
|
||||
|
||||
Run in browser console after visiting any page:
|
||||
|
||||
```javascript
|
||||
// Test null token
|
||||
const auth = await import('./src/services/auth.ts');
|
||||
console.log('Null token:', auth.parseTokenPayload(null));
|
||||
// Expected: null (no crash)
|
||||
|
||||
// Test invalid token
|
||||
console.log('Invalid:', auth.parseTokenPayload('not.a.jwt'));
|
||||
// Expected: null (no crash)
|
||||
|
||||
// Test empty string
|
||||
console.log('Empty:', auth.parseTokenPayload(''));
|
||||
// Expected: null (no crash)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Error Flow (Fixed)**
|
||||
|
||||
### **Before Fix:**
|
||||
```
|
||||
User sends message with expired token
|
||||
↓
|
||||
getAuthHeaders() returns headers without checking token
|
||||
↓
|
||||
fetch() → Backend returns 401
|
||||
↓
|
||||
throw new Error("HTTP 401: ...")
|
||||
↓
|
||||
onError handler receives Error
|
||||
↓
|
||||
❌ Shows error in chat UI
|
||||
❌ JWT parsing crashes on next operation
|
||||
```
|
||||
|
||||
### **After Fix:**
|
||||
```
|
||||
User sends message with expired token
|
||||
↓
|
||||
getAuthHeaders() checks isTokenValid()
|
||||
↓
|
||||
Token invalid → logout('expired') triggered
|
||||
↓
|
||||
Still sends request (will get 401)
|
||||
↓
|
||||
401 response → logout('expired') again (defensive)
|
||||
↓
|
||||
throw new Error('SESSION_EXPIRED')
|
||||
↓
|
||||
onError handler sees SESSION_EXPIRED
|
||||
↓
|
||||
✅ Skips error message
|
||||
✅ User redirected to login
|
||||
✅ Session expired banner shown
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Console Messages to Look For**
|
||||
|
||||
### **Success Indicators:**
|
||||
|
||||
```
|
||||
ChatService: Token invalid/expired, triggering logout
|
||||
Chat: Session expired, logout triggered
|
||||
AuthGuard: Invalid or missing token, logging out
|
||||
```
|
||||
|
||||
### **Warning Messages (Expected):**
|
||||
|
||||
```
|
||||
parseTokenPayload: Invalid token (null or not string)
|
||||
parseTokenPayload: Invalid JWT format (not 3 parts)
|
||||
parseTokenPayload: Missing payload section
|
||||
```
|
||||
|
||||
### **Error Messages (Should NOT Appear):**
|
||||
|
||||
```
|
||||
❌ Failed to parse JWT payload: TypeError: Cannot read properties of undefined
|
||||
❌ Sorry, I encountered an error: HTTP 401: {"error":{"message":"Authentication required"...
|
||||
❌ 🌊 Streaming error: Error: HTTP 401: Unauthorized
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Testing Checklist**
|
||||
|
||||
- [ ] **JWT parsing handles null** - No crash on `parseTokenPayload(null)`
|
||||
- [ ] **JWT parsing handles invalid format** - No crash on malformed tokens
|
||||
- [ ] **Chat detects expired token before request** - `getAuthHeaders` triggers logout
|
||||
- [ ] **Chat handles 401 response** - Triggers logout, throws SESSION_EXPIRED
|
||||
- [ ] **Error handler skips SESSION_EXPIRED** - No error shown in chat
|
||||
- [ ] **User redirected to login** - With session expired banner
|
||||
- [ ] **Banner displays correctly** - Red alert with message
|
||||
- [ ] **URL cleans up** - `?session_expired=true` removed after 100ms
|
||||
|
||||
---
|
||||
|
||||
## **Debugging**
|
||||
|
||||
If session timeout handling still fails:
|
||||
|
||||
1. **Check console for warnings:**
|
||||
```javascript
|
||||
// Should see:
|
||||
"ChatService: Token invalid/expired, triggering logout"
|
||||
```
|
||||
|
||||
2. **Verify token monitor is running:**
|
||||
```javascript
|
||||
// After login, check every 30 seconds for automatic detection
|
||||
const store = JSON.parse(localStorage.getItem('auth-store'));
|
||||
console.log('Token monitor interval:', store.state.tokenMonitorInterval);
|
||||
```
|
||||
|
||||
3. **Check network tab:**
|
||||
- Look for POST to `/api/v1/chat/completions`
|
||||
- Should return 401 if token expired
|
||||
- Should NOT see multiple retry attempts
|
||||
|
||||
4. **Container logs:**
|
||||
```bash
|
||||
docker logs gentwo-tenant-frontend --tail 50
|
||||
docker logs gentwo-tenant-backend --tail 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Files Modified**
|
||||
|
||||
1. ✅ `apps/tenant-app/src/services/auth.ts` - JWT parsing safety
|
||||
2. ✅ `apps/tenant-app/src/services/chat-service.ts` - 401 detection
|
||||
3. ✅ `apps/tenant-app/src/app/chat/page.tsx` - SESSION_EXPIRED handling
|
||||
|
||||
**Total lines changed:** ~60 lines across 3 files
|
||||
**Risk level:** Low (defensive coding, backward compatible)
|
||||
**Status:** ✅ Complete, running in Docker with hot reload
|
||||
|
||||
---
|
||||
|
||||
## **Related Fixes**
|
||||
|
||||
This fix complements the earlier session timeout work:
|
||||
- Token monitor in auth-store (checks every 30 seconds)
|
||||
- Centralized logout method
|
||||
- AuthGuard reactive to auth state changes
|
||||
- 401 handlers in API layer and React Query
|
||||
|
||||
Together, these ensure users are **always** redirected to login when their session expires, regardless of where in the app they are or what they're doing.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 2025
|
||||
**Docker Container:** gentwo-tenant-frontend (hot reload active)
|
||||
Reference in New Issue
Block a user