From 21b1e796722b51caa943c2687fea4e315dd2879e Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 15 Jul 2025 08:34:40 +0200 Subject: [PATCH] fix: resolve gallery login redirect issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated API interceptor to better handle gallery authentication - Fixed 401 error handling to prevent redirect loops on gallery pages - Improved token extraction logic for gallery API requests - Consolidated duplicate verifyGalleryAccess middleware - Added proper error handling in GalleryView component - Gallery authentication now properly distinguishes from admin routes The issue was caused by the API interceptor redirecting to admin login when gallery API calls failed with 401, even when users were already on gallery pages attempting to authenticate. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/middleware/gallery.js | 8 ++- backend/src/routes/gallery.js | 26 +--------- .../src/components/gallery/GalleryView.tsx | 13 ++++- frontend/src/config/api.ts | 51 ++++++++++++++----- frontend/src/hooks/useGallery.ts | 2 + 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/backend/src/middleware/gallery.js b/backend/src/middleware/gallery.js index 99482cb..1852629 100644 --- a/backend/src/middleware/gallery.js +++ b/backend/src/middleware/gallery.js @@ -11,7 +11,13 @@ async function verifyGalleryAccess(req, res, next) { } const decoded = jwt.verify(token, process.env.JWT_SECRET); - const event = await db('events').where({ id: decoded.eventId, is_active: formatBoolean(true) }).first(); + const event = await db('events') + .where({ + id: decoded.eventId, + is_active: formatBoolean(true), + is_archived: formatBoolean(false) + }) + .first(); if (!event) { return res.status(404).json({ error: 'Gallery not found or expired' }); diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 450824c..cda8014 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -6,35 +6,11 @@ const archiver = require('archiver'); const path = require('path'); const router = express.Router(); const watermarkService = require('../services/watermarkService'); +const { verifyGalleryAccess } = require('../middleware/gallery'); // Get storage path from environment or default const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); -// Middleware to verify gallery access -async function verifyGalleryAccess(req, res, next) { - try { - const token = req.headers.authorization?.split(' ')[1]; - if (!token) { - return res.status(401).json({ error: 'No token provided' }); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET); - const event = await db('events') - .where({ id: decoded.eventId, is_active: formatBoolean(true), is_archived: formatBoolean(false) }) - .first(); - - if (!event) { - return res.status(404).json({ error: 'Gallery not found or expired' }); - } - - req.event = event; - next(); - } catch (error) { - console.error('Error verifying gallery access:', error); - res.status(401).json({ error: 'Invalid token', details: error.message }); - } -} - // Verify share token router.get('/:slug/verify-token/:token', async (req, res) => { try { diff --git a/frontend/src/components/gallery/GalleryView.tsx b/frontend/src/components/gallery/GalleryView.tsx index 5607695..8a783fe 100644 --- a/frontend/src/components/gallery/GalleryView.tsx +++ b/frontend/src/components/gallery/GalleryView.tsx @@ -52,7 +52,7 @@ export const GalleryView: React.FC = ({ slug, event }) => { const { watermarkEnabled } = useWatermarkSettings(); // Fetch photos - const { data, isLoading, error } = useGalleryPhotos(slug); + const { data, isLoading, error, refetch } = useGalleryPhotos(slug); // Debug logging useEffect(() => { @@ -294,11 +294,20 @@ export const GalleryView: React.FC = ({ slug, event }) => { } if (error || !data) { + // Check if it's an authentication error (401) + const is401Error = (error as any)?.response?.status === 401; + + if (is401Error) { + // Authentication failed - logout and let the parent component handle re-authentication + logout(); + return null; + } + return (

{t('gallery.failedToLoad')}

-
diff --git a/frontend/src/config/api.ts b/frontend/src/config/api.ts index eb69dda..39572af 100644 --- a/frontend/src/config/api.ts +++ b/frontend/src/config/api.ts @@ -32,14 +32,24 @@ api.interceptors.request.use( config.headers.Authorization = `Bearer ${token}`; } } else { - // For gallery routes, get the slug from the URL path - const pathParts = window.location.pathname.split('/'); - if (pathParts[1] === 'gallery' && pathParts[2]) { - const gallerySlug = pathParts[2]; + // For gallery routes, try to extract slug from the request URL first + const galleryMatch = config.url?.match(/\/gallery\/([^\/]+)/); + if (galleryMatch && galleryMatch[1]) { + const gallerySlug = galleryMatch[1]; const token = localStorage.getItem(`gallery_token_${gallerySlug}`); if (token) { config.headers.Authorization = `Bearer ${token}`; } + } else { + // Fallback to getting slug from the current page URL + const pathParts = window.location.pathname.split('/'); + if (pathParts[1] === 'gallery' && pathParts[2]) { + const gallerySlug = pathParts[2]; + const token = localStorage.getItem(`gallery_token_${gallerySlug}`); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + } } } @@ -73,21 +83,34 @@ api.interceptors.response.use( } if (error.response?.status === 401) { - // Redirect to appropriate login + // Check if it's an admin route const isAdminRoute = error.config?.url?.includes('/admin'); + const currentPath = window.location.pathname; + if (isAdminRoute) { // Clear admin token on unauthorized Cookies.remove(ADMIN_TOKEN_KEY); - window.location.href = '/admin/login'; + // Only redirect if we're not already on the admin login page + if (!currentPath.includes('/admin/login')) { + window.location.href = '/admin/login'; + } } else { - // For gallery routes, clear gallery-specific token and redirect - const currentPath = window.location.pathname; - const pathParts = currentPath.split('/'); - if (pathParts[1] === 'gallery' && pathParts[2]) { - const gallerySlug = pathParts[2]; - localStorage.removeItem(`gallery_token_${gallerySlug}`); - localStorage.removeItem(`gallery_event_${gallerySlug}`); - window.location.href = `/gallery/${gallerySlug}`; + // For gallery routes, check if the error is from a gallery API call + const galleryMatch = error.config?.url?.match(/\/gallery\/([^\/]+)/); + + // Don't redirect if we're on any gallery page (to avoid redirect loops during login) + if (currentPath.startsWith('/gallery/')) { + // If we have a gallery match from the API URL, clear that specific gallery's token + if (galleryMatch && galleryMatch[1]) { + const gallerySlug = galleryMatch[1]; + localStorage.removeItem(`gallery_token_${gallerySlug}`); + localStorage.removeItem(`gallery_event_${gallerySlug}`); + } + // Don't redirect - let the component handle the auth state + } else { + // We're not on a gallery page but got a 401 from a gallery API + // This shouldn't happen in normal flow, but if it does, redirect to homepage + window.location.href = '/'; } } } diff --git a/frontend/src/hooks/useGallery.ts b/frontend/src/hooks/useGallery.ts index 498855f..f84f6d3 100644 --- a/frontend/src/hooks/useGallery.ts +++ b/frontend/src/hooks/useGallery.ts @@ -18,6 +18,8 @@ export const useGalleryPhotos = (slug: string, enabled: boolean = true) => { enabled, retry: 1, staleTime: 5 * 60 * 1000, // 5 minutes + // Add a small delay to ensure auth token is properly set + retryDelay: 100, }); };