security: cleanup obsolete token validation code (Action 5.1.1.6)

- Remove obsolete error logging in api/auth.ts that expected tokens in localStorage
- Fix tokenRefresh.ts periodic refresh to not check tokens (httpOnly cookies not accessible)
- Mark Actions 5.1.1.6-5.1.1.9 as complete in TODO list

Actions 5.1.1.7-5.1.1.9 were already completed in previous actions:
- 5.1.1.7: TokenStorage already returns null (httpOnly cookies not readable)
- 5.1.1.8: tokenRefresh already works with cookies
- 5.1.1.9: All token access goes through TokenStorage

No localStorage.getItem/setItem calls for tokens remain (only removeItem for cleanup)
This commit is contained in:
senke 2026-01-16 01:06:11 +01:00
parent 6d08664b6b
commit a4f7d4e577
3 changed files with 46 additions and 61 deletions

View file

@ -1669,32 +1669,48 @@ Critical path dependencies:
- Works with existing token refresh infrastructure
- **Rollback**: Remove periodic refresh interval
- [ ] **Action 5.1.1.6**: Clean up localStorage token references
- **Scope**: Search codebase for `localStorage.getItem('access_token')`, `localStorage.setItem('access_token')` - Remove all
- **Dependencies**: Action 5.1.1.2 complete
- [x] **Action 5.1.1.6**: Clean up localStorage token references
- **Scope**: Search codebase for `localStorage.getItem('access_token')`, `localStorage.setItem('access_token')` - Remove all
- **Dependencies**: Action 5.1.1.2 complete
- **Risk**: LOW
- **Validation**: No localStorage token access remains
- **Validation**: ✅ No localStorage token access remains:
- **Grep search**: No `localStorage.getItem('access_token')` or `localStorage.setItem('access_token')` calls found
- **Only cleanup**: Only `localStorage.removeItem()` calls remain in `tokenStorage.ts` for legacy token cleanup (intentional)
- **Removed obsolete code**: Removed error logging in `api/auth.ts` that expected tokens to be stored (lines 114-119, 234-239)
- **Fixed tokenRefresh**: Removed obsolete token check in `tokenRefresh.ts` periodic refresh (line 235)
- **Result**: All direct localStorage token access removed, only cleanup operations remain
- **Rollback**: Restore localStorage access
- [ ] **Action 5.1.1.7**: Update TokenStorage to read from cookie only
- **Scope**: `apps/web/src/services/tokenStorage.ts` - Remove localStorage, add cookie reading
- **Dependencies**: Action 5.1.1.2 complete
- [x] **Action 5.1.1.7**: Update TokenStorage to read from cookie only
- **Scope**: `apps/web/src/services/tokenStorage.ts` - Remove localStorage, add cookie reading
- **Dependencies**: Action 5.1.1.2 complete
- **Risk**: MEDIUM
- **Validation**: TokenStorage reads from cookie, no localStorage
- **Validation**: ✅ TokenStorage reads from cookie, no localStorage:
- **Already completed in Action 5.1.1.2**: TokenStorage.getAccessToken() returns null (httpOnly cookies not accessible)
- **Already completed in Action 5.1.1.2**: TokenStorage.getRefreshToken() returns null (httpOnly cookies not accessible)
- **Already completed in Action 5.1.1.2**: TokenStorage.setTokens() is a no-op (tokens set by backend in httpOnly cookies)
- **Result**: TokenStorage works with httpOnly cookies (returns null since cookies not accessible from JS)
- **Rollback**: Restore localStorage logic
- [ ] **Action 5.1.1.8**: Update tokenRefresh to work with cookies
- **Scope**: `apps/web/src/services/tokenRefresh.ts` - Update to read/write cookies instead of localStorage
- **Dependencies**: Action 5.1.1.7 complete
- [x] **Action 5.1.1.8**: Update tokenRefresh to work with cookies
- **Scope**: `apps/web/src/services/tokenRefresh.ts` - Update to read/write cookies instead of localStorage
- **Dependencies**: Action 5.1.1.7 complete
- **Risk**: MEDIUM
- **Validation**: Token refresh works with cookies
- **Validation**: ✅ Token refresh works with cookies:
- **Already completed in Action 5.1.1.3**: refreshToken() sends empty body, cookies sent automatically via withCredentials
- **Already completed in Action 5.1.1.3**: Removed token reading and expiration checks (can't check httpOnly cookies from JS)
- **Fixed**: Removed obsolete token check in periodic refresh (line 235)
- **Result**: Token refresh works entirely with httpOnly cookies
- **Rollback**: Restore localStorage logic
- [ ] **Action 5.1.1.9**: Update all token access to use TokenStorage
- **Scope**: Search for direct localStorage token access - Replace with TokenStorage methods
- **Dependencies**: Action 5.1.1.7 complete
- [x] **Action 5.1.1.9**: Update all token access to use TokenStorage
- **Scope**: Search for direct localStorage token access - Replace with TokenStorage methods
- **Dependencies**: Action 5.1.1.7 complete
- **Risk**: MEDIUM
- **Validation**: No direct localStorage token access
- **Validation**: ✅ No direct localStorage token access:
- **Grep search**: No direct `localStorage.getItem('access_token')` or `localStorage.setItem('access_token')` calls found
- **All access via TokenStorage**: All token access goes through TokenStorage methods (which return null for httpOnly cookies)
- **Result**: All token access uses TokenStorage API (no direct localStorage access)
- **Rollback**: Restore direct access
### Sub-Epic 5.2: Pre-Validation 🟢

View file

@ -106,18 +106,10 @@ export async function register(
// NOTE: Si refresh_token est vide, le backend utilise probablement des cookies httpOnly
// Dans ce cas, on stocke seulement l'access_token et on laisse le refresh se faire via cookie
if (accessToken) {
// Si refresh_token est présent, on le stocke, sinon on utilise une chaîne vide
// Le refresh se fera via cookie httpOnly si disponible
// SECURITY: Action 5.1.1.2 - Tokens are set in httpOnly cookies by backend
// TokenStorage.setTokens is now a no-op (tokens in httpOnly cookies, not accessible to JS)
TokenStorage.setTokens(accessToken, refreshToken || '');
// Vérifier que les tokens sont bien stockés (pour debug)
const storedToken = TokenStorage.getAccessToken();
if (!storedToken) {
logger.error(
'[AUTH] Failed to store token in localStorage after setTokens',
);
}
// INT-016: Initialiser le refresh proactif après register
initializeProactiveRefresh();
}
@ -226,18 +218,10 @@ export async function login(data: LoginRequest): Promise<LoginResponse> {
// NOTE: Si refresh_token est vide, le backend utilise probablement des cookies httpOnly
// Dans ce cas, on stocke seulement l'access_token et on laisse le refresh se faire via cookie
if (accessToken) {
// Si refresh_token est présent, on le stocke, sinon on utilise une chaîne vide
// Le refresh se fera via cookie httpOnly si disponible
// SECURITY: Action 5.1.1.2 - Tokens are set in httpOnly cookies by backend
// TokenStorage.setTokens is now a no-op (tokens in httpOnly cookies, not accessible to JS)
TokenStorage.setTokens(accessToken, refreshToken || '');
// Vérifier que les tokens sont bien stockés (pour debug)
const storedToken = TokenStorage.getAccessToken();
if (!storedToken) {
logger.error(
'[AUTH] Failed to store token in localStorage after setTokens',
);
}
// Stocker le flag remember_me pour référence future
if (data.remember_me) {
localStorage.setItem('remember_me', 'true');

View file

@ -231,33 +231,18 @@ function startPeriodicRefresh(): void {
}
// Set up interval to refresh every 4 minutes
// SECURITY: Action 5.1.1.2 - Can't check token expiration from httpOnly cookies, just refresh periodically
proactiveRefreshInterval = setInterval(() => {
const currentToken = TokenStorage.getAccessToken();
if (currentToken) {
// Check if token is still valid before refreshing
const payload = decodeJWT(currentToken);
if (payload?.exp) {
const expirationTime = payload.exp * 1000;
const now = Date.now();
const timeUntilExpiration = expirationTime - now;
// Only refresh if token is still valid (not expired)
if (timeUntilExpiration > 0) {
refreshToken().catch((error) => {
logger.warn('Periodic token refresh failed', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
});
} else {
// Token expired, stop periodic refresh
cancelProactiveRefresh();
}
}
} else {
// No token, stop periodic refresh
// Just attempt refresh - if it fails (401), the error handler will stop the interval
refreshToken().catch((error) => {
logger.warn('Periodic token refresh failed', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
// If refresh fails (e.g., token expired), stop periodic refresh
// The error handler in refreshToken will clear tokens and cancel refresh
cancelProactiveRefresh();
}
});
}, PROACTIVE_REFRESH_INTERVAL_MS);
}