[INT-CLEANUP-001] Remove all unused API service files (offline-storage.ts, secure-auth.ts)

This commit is contained in:
senke 2025-12-26 09:16:27 +01:00
parent c171d66b0c
commit 6d1e3cea3d
4 changed files with 11 additions and 567 deletions

View file

@ -680,7 +680,8 @@
"description": "Identifier et supprimer les fichiers de services API non utilisés.",
"priority": "P2",
"priority_rank": 20,
"status": "todo",
"status": "completed",
"completed_at": "2025-01-27T16:30:00Z",
"estimated_hours": 1,
"side": "frontend_only",
"files_to_modify": [
@ -1101,13 +1102,13 @@
},
"progress_tracking": {
"total_tasks": 32,
"completed": 19,
"completed": 20,
"in_progress": 0,
"todo": 13,
"todo": 12,
"blocked": 0,
"completion_percentage": 59,
"last_updated": "2025-01-27T16:15:00Z",
"completion_percentage": 63,
"last_updated": "2025-01-27T16:30:00Z",
"estimated_completion_date": null,
"estimated_hours_remaining": 19.5
"estimated_hours_remaining": 18.5
}
}

View file

@ -63,14 +63,16 @@ class CSRFService {
}
/**
* Alias pour compatibilité avec secure-auth.ts
* Alias pour compatibilité (legacy)
* INT-CLEANUP-001: Kept for backward compatibility
*/
clearCsrfToken(): void {
this.clearToken();
}
/**
* Alias pour compatibilité avec secure-auth.ts
* Alias pour compatibilité (legacy)
* INT-CLEANUP-001: Kept for backward compatibility
*/
async refreshCsrfToken(): Promise<string> {
return this.refreshToken();

View file

@ -1,237 +0,0 @@
/**
* IndexedDB wrapper for offline storage
* Provides persistent storage for large objects and file data
*/
class OfflineStorage {
private dbName = 'veza_offline_db';
private dbVersion = 1;
private db: IDBDatabase | null = null;
/**
* Open database connection
*/
async open(): Promise<IDBDatabase> {
if (this.db) {
return this.db;
}
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
reject(new Error('Failed to open IndexedDB'));
};
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// Create object stores for different data types
if (!db.objectStoreNames.contains('queued_actions')) {
const actionStore = db.createObjectStore('queued_actions', {
keyPath: 'id',
});
actionStore.createIndex('timestamp', 'timestamp', { unique: false });
actionStore.createIndex('type', 'type', { unique: false });
}
if (!db.objectStoreNames.contains('uploaded_files')) {
const fileStore = db.createObjectStore('uploaded_files', {
keyPath: 'id',
});
fileStore.createIndex('filename', 'filename', { unique: false });
fileStore.createIndex('status', 'status', { unique: false });
}
if (!db.objectStoreNames.contains('messages')) {
const messageStore = db.createObjectStore('messages', {
keyPath: 'id',
});
messageStore.createIndex('conversationId', 'conversationId', {
unique: false,
});
messageStore.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
/**
* Save data to a store
*/
async save<T>(storeName: string, data: T): Promise<void> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.add(data);
request.onsuccess = () => resolve();
request.onerror = () =>
reject(new Error(`Failed to save data to ${storeName}`));
});
}
/**
* Update data in a store
*/
async update<T>(storeName: string, data: T & { id: string }): Promise<void> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.put(data);
request.onsuccess = () => resolve();
request.onerror = () =>
reject(new Error(`Failed to update data in ${storeName}`));
});
}
/**
* Get data from a store by ID
*/
async get<T>(storeName: string, id: string): Promise<T | null> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(id);
request.onsuccess = () => {
resolve(request.result || null);
};
request.onerror = () =>
reject(new Error(`Failed to get data from ${storeName}`));
});
}
/**
* Get all data from a store
*/
async getAll<T>(storeName: string): Promise<T[]> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.getAll();
request.onsuccess = () => {
resolve(request.result || []);
};
request.onerror = () =>
reject(new Error(`Failed to get all data from ${storeName}`));
});
}
/**
* Delete data from a store by ID
*/
async delete(storeName: string, id: string): Promise<void> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () =>
reject(new Error(`Failed to delete data from ${storeName}`));
});
}
/**
* Clear all data from a store
*/
async clear(storeName: string): Promise<void> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error(`Failed to clear ${storeName}`));
});
}
/**
* Query data by index
*/
async query<T>(
storeName: string,
indexName: string,
query: IDBKeyRange | string,
): Promise<T[]> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.getAll(query);
request.onsuccess = () => {
resolve(request.result || []);
};
request.onerror = () => reject(new Error(`Failed to query ${storeName}`));
});
}
/**
* Get count of items in a store
*/
async count(storeName: string): Promise<number> {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.count();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(new Error(`Failed to count ${storeName}`));
});
}
}
// Export singleton instance
export const offlineStorage = new OfflineStorage();
// Export types
export interface QueuedAction {
id: string;
type: 'send_message' | 'upload_track' | 'update_profile';
payload: Record<string, any>;
timestamp: number;
retries: number;
status: 'pending' | 'processing' | 'failed' | 'completed';
}
export interface UploadedFile {
id: string;
filename: string;
file: File;
status: 'pending' | 'uploading' | 'completed' | 'failed';
progress: number;
error?: string;
}
export interface OfflineMessage {
id: string;
conversationId: string;
content: string;
timestamp: number;
userId: string;
}

View file

@ -1,322 +0,0 @@
//! Service d'authentification sécurisé avec cookies httpOnly
//!
//! Ce service gère:
//! - Authentification via cookies httpOnly
//! - Gestion des tokens CSRF
//! - Protection contre les attaques XSS et CSRF
import { z } from 'zod';
import type { AuthTokens, LoginRequest, RegisterRequest, User } from '@/types';
import { csrfService } from './csrf';
// Custom Error Class
export class ApiError extends Error {
code: string;
details?: Record<string, unknown>;
constructor(
message: string,
code: string = 'UNKNOWN_ERROR',
details?: Record<string, unknown>,
) {
super(message);
this.name = 'ApiError';
this.code = code;
this.details = details;
}
}
// Schémas de validation
const UserSchema = z.object({
id: z.string().uuid(), // Fixed: number -> string, now validates UUID format
username: z.string(),
email: z.string().email(),
first_name: z.string().optional(),
last_name: z.string().optional(),
role: z.enum(['user', 'admin', 'super_admin']),
is_active: z.boolean(),
is_verified: z.boolean(),
created_at: z.string(),
last_login_at: z.string().optional(),
avatar_url: z.string().optional(),
bio: z.string().optional(),
is_admin: z.boolean().default(false),
is_public: z.boolean().default(false),
updated_at: z.string().default(new Date().toISOString()),
});
const AuthTokensSchema = z.object({
access_token: z.string(),
refresh_token: z.string(),
expires_in: z.number(),
});
// Configuration
const API_BASE_URL = (() => {
const url = import.meta.env.VITE_API_URL;
if (!url) {
if (import.meta.env.PROD) {
throw new Error('VITE_API_URL must be defined in production');
}
// Fallback uniquement en développement
return 'http://127.0.0.1:8080/api/v1';
}
return url;
})();
export class SecureAuthService {
private static instance: SecureAuthService;
private isAuthenticatedFlag: boolean = false;
private user: User | null = null;
private constructor() {
this.checkAuthenticationStatus();
}
public static getInstance(): SecureAuthService {
if (!SecureAuthService.instance) {
SecureAuthService.instance = new SecureAuthService();
}
return SecureAuthService.instance;
}
/**
* Vérifie le statut d'authentification via les cookies
*/
private async checkAuthenticationStatus(): Promise<void> {
try {
const response = await fetch(`${API_BASE_URL}/auth/me`, {
method: 'GET',
credentials: 'include', // Important pour les cookies httpOnly
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
});
if (response.ok) {
const data = await response.json();
this.user = UserSchema.parse(data.user) as User;
this.isAuthenticatedFlag = true;
console.debug('Utilisateur authentifié via cookies:', this.user);
} else {
this.clearAuth();
}
} catch (error) {
console.warn(
"Erreur lors de la vérification de l'authentification:",
error,
);
this.clearAuth();
}
}
/**
* Connexion avec cookies httpOnly
*/
public async login(
credentials: LoginRequest,
): Promise<{ user: User; tokens: AuthTokens }> {
try {
// Récupérer le token CSRF avant la connexion
await csrfService.refreshCsrfToken();
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(
errorData.message || 'Erreur de connexion',
errorData.code || 'LOGIN_ERROR',
);
}
const data = await response.json();
const user = UserSchema.parse(data.user) as User;
const tokens = AuthTokensSchema.parse(data.tokens);
// Les tokens sont automatiquement stockés dans les cookies httpOnly
// On ne les stocke pas dans localStorage pour la sécurité
this.user = user;
this.isAuthenticatedFlag = true;
console.debug('Connexion réussie avec cookies httpOnly:', user);
return { user, tokens };
} catch (error) {
console.error('Erreur de connexion:', error);
this.clearAuth();
throw error;
}
}
/**
* Inscription avec cookies httpOnly
*/
public async register(
userData: RegisterRequest,
): Promise<{ user: User; tokens: AuthTokens }> {
try {
// Récupérer le token CSRF avant l'inscription
await csrfService.refreshCsrfToken();
const response = await fetch(`${API_BASE_URL}/auth/register`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
body: JSON.stringify(userData),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(
errorData.message || "Erreur d'inscription",
errorData.code || 'REGISTER_ERROR',
);
}
const data = await response.json();
const user = UserSchema.parse(data.user);
const tokens = AuthTokensSchema.parse(data.tokens);
this.user = user;
this.isAuthenticatedFlag = true;
console.debug('Inscription réussie avec cookies httpOnly:', user);
return { user, tokens };
} catch (error) {
console.error("Erreur d'inscription:", error);
this.clearAuth();
throw error;
}
}
/**
* Déconnexion
*/
public async logout(): Promise<void> {
try {
await fetch(`${API_BASE_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
});
} catch (error) {
console.warn('Erreur lors de la déconnexion:', error);
} finally {
this.clearAuth();
}
}
/**
* Rafraîchit les informations utilisateur
*/
public async refreshUser(): Promise<User | null> {
try {
const response = await fetch(`${API_BASE_URL}/auth/me`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
});
if (response.ok) {
const data = await response.json();
this.user = UserSchema.parse(data.user) as User;
this.isAuthenticatedFlag = true;
return this.user;
} else {
this.clearAuth();
return null;
}
} catch (error) {
console.warn("Erreur lors du rafraîchissement de l'utilisateur:", error);
this.clearAuth();
return null;
}
}
/**
* Rafraîchit le token d'accès
*/
public async refreshAccessToken(): Promise<string> {
try {
const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
},
});
if (!response.ok) {
throw new ApiError(
'Impossible de rafraîchir le token',
'REFRESH_ERROR',
);
}
const data = await response.json();
const tokens = AuthTokensSchema.parse(data.tokens);
// Le nouveau token est automatiquement stocké dans les cookies httpOnly
return tokens.access_token;
} catch (error) {
console.error('Erreur lors du rafraîchissement du token:', error);
this.clearAuth();
throw error;
}
}
/**
* Vérifie si l'utilisateur est authentifié
*/
public isAuthenticated(): boolean {
return this.isAuthenticatedFlag && this.user !== null;
}
/**
* Obtient l'utilisateur actuel
*/
public getCurrentUser(): User | null {
return this.user;
}
/**
* Nettoie l'état d'authentification
*/
private clearAuth(): void {
this.user = null;
this.isAuthenticatedFlag = false;
csrfService.clearCsrfToken();
}
/**
* Obtient les headers d'authentification pour les requêtes
*/
public getAuthHeaders(): Record<string, string> {
return {
'Content-Type': 'application/json',
...csrfService.getCsrfHeaders(),
};
}
}
// Instance singleton
export const secureAuthService = SecureAuthService.getInstance();