From 70cfd63ca1757cb4989d2e3c19c9fc6cf031276c Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Fri, 23 Jan 2026 18:59:46 +0000 Subject: [PATCH] fix: support HTTPS proxy by using relative API paths - Update API client to use relative paths when VITE_API_URL is empty - Fix EventSource URLs to use window.location.origin for proxy compatibility - Update image/video URL construction to use relative paths - Add debug logging for API response troubleshooting - All API calls now work correctly when served through HTTPS proxy This fixes mixed content errors and allows the admin frontend to work when accessed via HTTPS domain with reverse proxy (Caddy/nginx). --- admin-frontend/src/api/auth.ts | 8 ++++++-- admin-frontend/src/api/client.ts | 6 +++++- admin-frontend/src/api/jobs.ts | 7 +++++-- admin-frontend/src/api/photos.ts | 7 +++++-- admin-frontend/src/api/videos.ts | 10 ++++++++-- admin-frontend/src/context/AuthContext.tsx | 20 +++++++++++++++++++- admin-frontend/src/pages/Identify.tsx | 3 ++- admin-frontend/src/pages/ReportedPhotos.tsx | 3 ++- 8 files changed, 52 insertions(+), 12 deletions(-) diff --git a/admin-frontend/src/api/auth.ts b/admin-frontend/src/api/auth.ts index a4b2c14..7189347 100644 --- a/admin-frontend/src/api/auth.ts +++ b/admin-frontend/src/api/auth.ts @@ -48,8 +48,12 @@ export const authApi = { }, me: async (): Promise => { - const { data } = await apiClient.get('/api/v1/auth/me') - return data + const response = await apiClient.get('/api/v1/auth/me') + console.log('🔍 Raw /me API response:', response) + console.log('🔍 Response data:', response.data) + console.log('🔍 Response data type:', typeof response.data) + console.log('🔍 Response data keys:', response.data ? Object.keys(response.data) : 'no keys') + return response.data }, changePassword: async ( diff --git a/admin-frontend/src/api/client.ts b/admin-frontend/src/api/client.ts index a5ad15e..ccbe58d 100644 --- a/admin-frontend/src/api/client.ts +++ b/admin-frontend/src/api/client.ts @@ -3,7 +3,11 @@ import axios from 'axios' // Get API base URL from environment variable or use default // The .env file should contain: VITE_API_URL=http://127.0.0.1:8000 // Alternatively, Vite proxy can be used (configured in vite.config.ts) by setting VITE_API_URL to empty string -const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' +// When VITE_API_URL is empty/undefined, use relative path to work with HTTPS proxy +const envApiUrl = import.meta.env.VITE_API_URL +const API_BASE_URL = envApiUrl && envApiUrl.trim() !== '' + ? envApiUrl + : '' // Use relative path when empty - works with proxy and HTTPS export const apiClient = axios.create({ baseURL: API_BASE_URL, diff --git a/admin-frontend/src/api/jobs.ts b/admin-frontend/src/api/jobs.ts index 1a9d9c6..51bb8c5 100644 --- a/admin-frontend/src/api/jobs.ts +++ b/admin-frontend/src/api/jobs.ts @@ -27,8 +27,11 @@ export const jobsApi = { }, streamJobProgress: (jobId: string): EventSource => { - // EventSource needs absolute URL - use VITE_API_URL or fallback to direct backend URL - const baseURL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' + // EventSource needs absolute URL - use VITE_API_URL or construct from current origin + const envApiUrl = import.meta.env.VITE_API_URL + const baseURL = envApiUrl && envApiUrl.trim() !== '' + ? envApiUrl + : window.location.origin // Use current origin when empty - works with proxy and HTTPS return new EventSource(`${baseURL}/api/v1/jobs/stream/${jobId}`) }, diff --git a/admin-frontend/src/api/photos.ts b/admin-frontend/src/api/photos.ts index 3f52719..3d1aab4 100644 --- a/admin-frontend/src/api/photos.ts +++ b/admin-frontend/src/api/photos.ts @@ -70,8 +70,11 @@ export const photosApi = { }, streamJobProgress: (jobId: string): EventSource => { - // EventSource needs absolute URL - use VITE_API_URL or fallback to direct backend URL - const baseURL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' + // EventSource needs absolute URL - use VITE_API_URL or construct from current origin + const envApiUrl = import.meta.env.VITE_API_URL + const baseURL = envApiUrl && envApiUrl.trim() !== '' + ? envApiUrl + : window.location.origin // Use current origin when empty - works with proxy and HTTPS return new EventSource(`${baseURL}/api/v1/jobs/stream/${jobId}`) }, diff --git a/admin-frontend/src/api/videos.ts b/admin-frontend/src/api/videos.ts index b0e77a7..45fdc73 100644 --- a/admin-frontend/src/api/videos.ts +++ b/admin-frontend/src/api/videos.ts @@ -108,12 +108,18 @@ export const videosApi = { }, getThumbnailUrl: (videoId: number): string => { - const baseURL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' + const envApiUrl = import.meta.env.VITE_API_URL + const baseURL = envApiUrl && envApiUrl.trim() !== '' + ? envApiUrl + : '' // Use relative path when empty - works with proxy and HTTPS return `${baseURL}/api/v1/videos/${videoId}/thumbnail` }, getVideoUrl: (videoId: number): string => { - const baseURL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' + const envApiUrl = import.meta.env.VITE_API_URL + const baseURL = envApiUrl && envApiUrl.trim() !== '' + ? envApiUrl + : '' // Use relative path when empty - works with proxy and HTTPS return `${baseURL}/api/v1/videos/${videoId}/video` }, } diff --git a/admin-frontend/src/context/AuthContext.tsx b/admin-frontend/src/context/AuthContext.tsx index dce4063..3db1c02 100644 --- a/admin-frontend/src/context/AuthContext.tsx +++ b/admin-frontend/src/context/AuthContext.tsx @@ -38,6 +38,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { authApi .me() .then((user) => { + console.log('🔍 Auth /me response:', { + username: user.username, + is_admin: user.is_admin, + role: user.role, + permissions: user.permissions + }) setAuthState({ isAuthenticated: true, username: user.username, @@ -81,6 +87,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { localStorage.setItem('access_token', tokens.access_token) localStorage.setItem('refresh_token', tokens.refresh_token) const user = await authApi.me() + console.log('🔍 Login /me response:', { + username: user.username, + is_admin: user.is_admin, + role: user.role, + permissions: user.permissions + }) const passwordChangeRequired = tokens.password_change_required || false setAuthState({ isAuthenticated: true, @@ -133,7 +145,13 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (authState.isAdmin) { return true } - return Boolean(authState.permissions[featureKey]) + const hasPerm = Boolean(authState.permissions[featureKey]) + console.log(`🔍 hasPermission(${featureKey}):`, { + isAdmin: authState.isAdmin, + hasPerm, + permissions: authState.permissions + }) + return hasPerm }, [authState.isAdmin, authState.permissions] ) diff --git a/admin-frontend/src/pages/Identify.tsx b/admin-frontend/src/pages/Identify.tsx index 68090b3..1adc2ec 100644 --- a/admin-frontend/src/pages/Identify.tsx +++ b/admin-frontend/src/pages/Identify.tsx @@ -604,7 +604,8 @@ export default function Identify() { const preloadImages = () => { const preloadUrls: string[] = [] - const baseUrl = apiClient.defaults.baseURL || 'http://127.0.0.1:8000' + // Use relative path when baseURL is empty (works with proxy and HTTPS) + const baseUrl = apiClient.defaults.baseURL || '' // Preload next face if (currentIdx + 1 < faces.length) { diff --git a/admin-frontend/src/pages/ReportedPhotos.tsx b/admin-frontend/src/pages/ReportedPhotos.tsx index 9a0831a..49910ee 100644 --- a/admin-frontend/src/pages/ReportedPhotos.tsx +++ b/admin-frontend/src/pages/ReportedPhotos.tsx @@ -41,7 +41,8 @@ export default function ReportedPhotos() { // Create direct backend URLs for images (only for non-video photos) const newImageUrls: Record = {} - const baseURL = apiClient.defaults.baseURL || 'http://10.0.10.121:8000' + // Use relative path when baseURL is empty (works with proxy and HTTPS) + const baseURL = apiClient.defaults.baseURL || '' response.items.forEach((reported) => { if (reported.photo_id && reported.photo_media_type !== 'video') { // Use direct backend URL - the backend endpoint doesn't require auth for images