Add new frontend - fix 6
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// src/api/auth/tokenManager.ts
|
||||
// File: frontend/src/api/auth/tokenManager.ts
|
||||
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
|
||||
interface TokenPayload {
|
||||
@@ -13,7 +14,7 @@ interface TokenResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
expires_in?: number;
|
||||
}
|
||||
|
||||
class TokenManager {
|
||||
@@ -42,20 +43,32 @@ class TokenManager {
|
||||
|
||||
// Check if token needs refresh
|
||||
if (this.isTokenExpired()) {
|
||||
await this.refreshAccessToken();
|
||||
try {
|
||||
await this.refreshAccessToken();
|
||||
} catch (error) {
|
||||
// If refresh fails on init, clear tokens
|
||||
this.clearTokens();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async storeTokens(response: TokenResponse): Promise<void> {
|
||||
this.accessToken = response.access_token;
|
||||
this.refreshToken = response.refresh_token;
|
||||
async storeTokens(response: TokenResponse | any): Promise<void> {
|
||||
// Handle both direct TokenResponse and login response with nested tokens
|
||||
if (response.access_token) {
|
||||
this.accessToken = response.access_token;
|
||||
this.refreshToken = response.refresh_token;
|
||||
} else {
|
||||
// Handle login response format
|
||||
this.accessToken = response.access_token;
|
||||
this.refreshToken = response.refresh_token;
|
||||
}
|
||||
|
||||
// Calculate expiry time
|
||||
const expiresIn = response.expires_in || 3600; // Default 1 hour
|
||||
this.tokenExpiry = new Date(Date.now() + expiresIn * 1000);
|
||||
|
||||
// Store securely (not in localStorage for security)
|
||||
// Store securely
|
||||
this.secureStore({
|
||||
accessToken: this.accessToken,
|
||||
refreshToken: this.refreshToken,
|
||||
@@ -66,7 +79,12 @@ class TokenManager {
|
||||
async getAccessToken(): Promise<string | null> {
|
||||
// Check if token is expired or will expire soon (5 min buffer)
|
||||
if (this.shouldRefreshToken()) {
|
||||
await this.refreshAccessToken();
|
||||
try {
|
||||
await this.refreshAccessToken();
|
||||
} catch (error) {
|
||||
console.error('Token refresh failed:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return this.accessToken;
|
||||
}
|
||||
@@ -92,7 +110,8 @@ class TokenManager {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
// FIXED: Use correct refresh endpoint
|
||||
const response = await fetch('/api/v1/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -103,12 +122,14 @@ class TokenManager {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Token refresh failed');
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || 'Token refresh failed');
|
||||
}
|
||||
|
||||
const data: TokenResponse = await response.json();
|
||||
await this.storeTokens(data);
|
||||
} catch (error) {
|
||||
console.error('Token refresh error:', error);
|
||||
// Clear tokens on refresh failure
|
||||
this.clearTokens();
|
||||
throw error;
|
||||
@@ -140,26 +161,33 @@ class TokenManager {
|
||||
|
||||
// Secure storage implementation
|
||||
private secureStore(data: any): void {
|
||||
// In production, use httpOnly cookies or secure session storage
|
||||
// For now, using sessionStorage with encryption
|
||||
const encrypted = this.encrypt(JSON.stringify(data));
|
||||
sessionStorage.setItem('auth_tokens', encrypted);
|
||||
try {
|
||||
const encrypted = this.encrypt(JSON.stringify(data));
|
||||
sessionStorage.setItem('auth_tokens', encrypted);
|
||||
} catch (error) {
|
||||
console.error('Failed to store tokens:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredTokens(): any {
|
||||
const stored = sessionStorage.getItem('auth_tokens');
|
||||
if (!stored) return null;
|
||||
|
||||
try {
|
||||
const stored = sessionStorage.getItem('auth_tokens');
|
||||
if (!stored) return null;
|
||||
|
||||
const decrypted = this.decrypt(stored);
|
||||
return JSON.parse(decrypted);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error('Failed to retrieve stored tokens:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private clearSecureStore(): void {
|
||||
sessionStorage.removeItem('auth_tokens');
|
||||
try {
|
||||
sessionStorage.removeItem('auth_tokens');
|
||||
} catch (error) {
|
||||
console.error('Failed to clear stored tokens:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple encryption for demo (use proper encryption in production)
|
||||
|
||||
@@ -320,5 +320,5 @@ class ApiClient {
|
||||
|
||||
// Create default instance
|
||||
export const apiClient = new ApiClient({
|
||||
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:8000/api/v1'
|
||||
baseURL: process.env.FRONTEND_API_URL || 'http://localhost:8000'
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/api/auth/authService.ts
|
||||
// File: frontend/src/api/services/authService.ts
|
||||
|
||||
import { tokenManager } from '../auth/tokenManager';
|
||||
import { apiClient } from '../base/apiClient';
|
||||
|
||||
@@ -26,41 +27,37 @@ export interface UserProfile {
|
||||
|
||||
class AuthService {
|
||||
async login(credentials: LoginCredentials): Promise<UserProfile> {
|
||||
// OAuth2 password flow
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('username', credentials.email);
|
||||
formData.append('password', credentials.password);
|
||||
formData.append('grant_type', 'password');
|
||||
// FIXED: Use correct endpoint and method
|
||||
const response = await apiClient.post('/api/v1/auth/login', credentials);
|
||||
|
||||
// Store tokens from login response
|
||||
await tokenManager.storeTokens(response);
|
||||
|
||||
const response = await fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Login failed');
|
||||
// Get user profile from the response or make separate call
|
||||
if (response.user) {
|
||||
return response.user;
|
||||
} else {
|
||||
return this.getCurrentUser();
|
||||
}
|
||||
|
||||
const tokenResponse = await response.json();
|
||||
await tokenManager.storeTokens(tokenResponse);
|
||||
|
||||
// Get user profile
|
||||
return this.getCurrentUser();
|
||||
}
|
||||
|
||||
async register(data: RegisterData): Promise<UserProfile> {
|
||||
const response = await apiClient.post('/auth/register', data);
|
||||
// FIXED: Use correct endpoint path
|
||||
const response = await apiClient.post('/api/v1/auth/register', data);
|
||||
|
||||
return response;
|
||||
// Registration only returns user data, NOT tokens
|
||||
// So we need to login separately to get tokens
|
||||
await this.login({
|
||||
email: data.email,
|
||||
password: data.password
|
||||
});
|
||||
|
||||
return response; // This is the user profile from registration
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
await apiClient.post('/auth/logout');
|
||||
await apiClient.post('/api/v1/auth/logout');
|
||||
} finally {
|
||||
tokenManager.clearTokens();
|
||||
window.location.href = '/login';
|
||||
@@ -68,15 +65,15 @@ class AuthService {
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<UserProfile> {
|
||||
return apiClient.get('/auth/me');
|
||||
return apiClient.get('/api/v1/auth/me');
|
||||
}
|
||||
|
||||
async updateProfile(updates: Partial<UserProfile>): Promise<UserProfile> {
|
||||
return apiClient.patch('/auth/profile', updates);
|
||||
return apiClient.patch('/api/v1/auth/profile', updates);
|
||||
}
|
||||
|
||||
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
await apiClient.post('/auth/change-password', {
|
||||
await apiClient.post('/api/v1/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
});
|
||||
@@ -87,4 +84,4 @@ class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = new AuthService();
|
||||
export const authService = new AuthService();
|
||||
Reference in New Issue
Block a user