This commit is contained in:
gpt-engineer-app[bot]
2026-01-07 14:29:40 +00:00
parent 654840efed
commit 06c23ad1dc
2 changed files with 93 additions and 24 deletions
+11 -4
View File
@@ -1,6 +1,6 @@
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api, User, ApiError } from '@/lib/api'; import { api, User, ApiError, tokenManager } from '@/lib/api';
interface AuthContextType { interface AuthContextType {
user: User | null; user: User | null;
@@ -30,9 +30,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
} }
}, []); }, []);
// Check session on mount // Check for existing token on mount
useEffect(() => { useEffect(() => {
const checkSession = async () => { const checkAuth = async () => {
// Only attempt to fetch user if we have a valid token
if (!tokenManager.hasValidToken()) {
setUser(null);
setIsLoading(false);
return;
}
try { try {
const response = await api.users.me(); const response = await api.users.me();
setUser(response.user); setUser(response.user);
@@ -43,7 +50,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
} }
}; };
checkSession(); checkAuth();
}, []); }, []);
const login = useCallback(async (email: string, password: string, rememberMe = false) => { const login = useCallback(async (email: string, password: string, rememberMe = false) => {
+82 -20
View File
@@ -1,5 +1,5 @@
// API Client for Gatehouse Backend // API Client for Gatehouse Backend
// Uses session-based authentication with cookies // Uses Bearer token authentication
import { config } from '@/config'; import { config } from '@/config';
@@ -44,14 +44,10 @@ export interface OrganizationsResponse {
count: number; count: number;
} }
export interface Session {
id: string;
expires_at: string;
}
export interface LoginResponse { export interface LoginResponse {
user: User; user: User;
session: Session; token: string;
expires_at: string;
} }
export interface ProfileResponse { export interface ProfileResponse {
@@ -72,22 +68,75 @@ class ApiError extends Error {
} }
} }
// Token storage keys
const TOKEN_KEY = 'gatehouse_token';
const TOKEN_EXPIRY_KEY = 'gatehouse_token_expiry';
// Token management
export const tokenManager = {
getToken: (): string | null => {
const token = localStorage.getItem(TOKEN_KEY);
const expiry = localStorage.getItem(TOKEN_EXPIRY_KEY);
// Check if token is expired
if (token && expiry) {
const expiryDate = new Date(expiry);
if (expiryDate <= new Date()) {
tokenManager.clearToken();
return null;
}
}
return token;
},
setToken: (token: string, expiresAt: string): void => {
localStorage.setItem(TOKEN_KEY, token);
localStorage.setItem(TOKEN_EXPIRY_KEY, expiresAt);
},
clearToken: (): void => {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(TOKEN_EXPIRY_KEY);
},
hasValidToken: (): boolean => {
return tokenManager.getToken() !== null;
},
};
// Central request function - all API calls go through here
async function request<T>( async function request<T>(
endpoint: string, endpoint: string,
options: RequestInit = {} options: RequestInit = {},
requiresAuth = true
): Promise<T> { ): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
// Add Authorization header if we have a token and auth is required
if (requiresAuth) {
const token = tokenManager.getToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}
const response = await fetch(`${config.api.baseUrl}${endpoint}`, { const response = await fetch(`${config.api.baseUrl}${endpoint}`, {
...options, ...options,
credentials: 'include', // Important: include session cookies headers,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
}); });
const json: ApiResponse<T> = await response.json(); const json: ApiResponse<T> = await response.json();
if (!json.success) { if (!json.success) {
// Clear token on 401 errors
if (json.code === 401) {
tokenManager.clearToken();
}
throw new ApiError( throw new ApiError(
json.message || 'An error occurred', json.message || 'An error occurred',
json.code, json.code,
@@ -99,18 +148,31 @@ async function request<T>(
return json.data as T; return json.data as T;
} }
// Centralized API client - all routes defined here
export const api = { export const api = {
auth: { auth: {
login: (email: string, password: string, remember_me = false) => login: async (email: string, password: string, remember_me = false): Promise<LoginResponse> => {
request<LoginResponse>('/auth/login', { const response = await request<LoginResponse>('/auth/login', {
method: 'POST', method: 'POST',
body: JSON.stringify({ email, password, remember_me }), body: JSON.stringify({ email, password, remember_me }),
}), }, false); // Login doesn't require auth
logout: () => // Store token on successful login
request<void>('/auth/logout', { tokenManager.setToken(response.token, response.expires_at);
method: 'POST',
}), return response;
},
logout: async (): Promise<void> => {
try {
await request<void>('/auth/logout', {
method: 'POST',
});
} finally {
// Always clear token on logout
tokenManager.clearToken();
}
},
}, },
users: { users: {