fix: Implement gallery-specific authentication tokens

- Fix issue where different galleries shared authentication
- Store gallery tokens with slug-specific keys in localStorage
- Remove global gallery_token cookie approach
- Each gallery now maintains its own authentication state
- Add cleanup for legacy authentication data

This ensures that accessing different galleries requires separate authentication
and prevents cross-gallery authentication leakage.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-09 08:05:04 +02:00
parent c8cfce3e36
commit cf32b01356
4 changed files with 73 additions and 27 deletions
+24 -13
View File
@@ -25,12 +25,22 @@ api.interceptors.request.use(
(config) => {
// Check if it's an admin route or gallery route
const isAdminRoute = config.url?.includes('/admin');
const token = isAdminRoute
? Cookies.get(ADMIN_TOKEN_KEY)
: Cookies.get(GALLERY_TOKEN_KEY);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
if (isAdminRoute) {
const token = Cookies.get(ADMIN_TOKEN_KEY);
if (token) {
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];
const token = localStorage.getItem(`gallery_token_${gallerySlug}`);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
}
// Don't set Content-Type for FormData - let browser set it with boundary
@@ -63,19 +73,20 @@ api.interceptors.response.use(
}
if (error.response?.status === 401) {
// Clear tokens on unauthorized
Cookies.remove(ADMIN_TOKEN_KEY);
Cookies.remove(GALLERY_TOKEN_KEY);
// Redirect to appropriate login
const isAdminRoute = error.config?.url?.includes('/admin');
if (isAdminRoute) {
// Clear admin token on unauthorized
Cookies.remove(ADMIN_TOKEN_KEY);
window.location.href = '/admin/login';
} else {
// For gallery routes, redirect to the gallery password page
// For gallery routes, clear gallery-specific token and redirect
const currentPath = window.location.pathname;
const gallerySlug = currentPath.split('/')[2];
if (gallerySlug) {
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}`;
}
}
+39 -12
View File
@@ -1,7 +1,7 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import type { ReactNode } from 'react';
import { getAuthToken } from '../config/api';
import { authService } from '../services';
import { cleanupOldGalleryAuth } from '../utils/cleanupGalleryAuth';
interface GalleryEvent {
id: number;
@@ -42,20 +42,42 @@ export const GalleryAuthProvider: React.FC<GalleryAuthProviderProps> = ({ childr
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Get current gallery slug from URL
const getCurrentGallerySlug = () => {
const pathParts = window.location.pathname.split('/');
if (pathParts[1] === 'gallery' && pathParts[2]) {
return pathParts[2];
}
return null;
};
useEffect(() => {
// Clean up old authentication data on mount
cleanupOldGalleryAuth();
// Check if user has a valid token on mount
const token = getAuthToken(false);
if (token) {
// Try to restore event data from localStorage
const storedEvent = localStorage.getItem('gallery_event');
if (storedEvent) {
const currentSlug = getCurrentGallerySlug();
if (currentSlug) {
// Try to restore event data from localStorage with slug-specific key
const storedEvent = localStorage.getItem(`gallery_event_${currentSlug}`);
const storedToken = localStorage.getItem(`gallery_token_${currentSlug}`);
if (storedEvent && storedToken) {
try {
const eventData = JSON.parse(storedEvent);
setEvent(eventData);
setIsAuthenticated(true);
// Verify the stored event matches the current gallery slug
if (eventData && eventData.id) {
setEvent(eventData);
setIsAuthenticated(true);
} else {
// Clear invalid data
localStorage.removeItem(`gallery_event_${currentSlug}`);
localStorage.removeItem(`gallery_token_${currentSlug}`);
}
} catch (error) {
console.error('Failed to parse stored event data');
localStorage.removeItem('gallery_event');
localStorage.removeItem(`gallery_event_${currentSlug}`);
localStorage.removeItem(`gallery_token_${currentSlug}`);
}
}
}
@@ -70,8 +92,9 @@ export const GalleryAuthProvider: React.FC<GalleryAuthProviderProps> = ({ childr
setEvent(response.event);
setIsAuthenticated(true);
// Store event data in localStorage
localStorage.setItem('gallery_event', JSON.stringify(response.event));
// Store event data and token in localStorage with slug-specific key
localStorage.setItem(`gallery_event_${slug}`, JSON.stringify(response.event));
localStorage.setItem(`gallery_token_${slug}`, response.token);
} catch (err: any) {
setError(err.response?.data?.error || 'Invalid password');
throw err;
@@ -81,10 +104,14 @@ export const GalleryAuthProvider: React.FC<GalleryAuthProviderProps> = ({ childr
};
const logout = () => {
const currentSlug = getCurrentGallerySlug();
if (currentSlug) {
localStorage.removeItem(`gallery_event_${currentSlug}`);
localStorage.removeItem(`gallery_token_${currentSlug}`);
}
authService.galleryLogout();
setIsAuthenticated(false);
setEvent(null);
localStorage.removeItem('gallery_event');
};
return (
+2 -2
View File
@@ -28,11 +28,11 @@ export const authService = {
recaptchaToken
});
setAuthToken(response.data.token, false);
// Token is now handled by GalleryAuthContext with slug-specific storage
return response.data;
},
galleryLogout() {
clearAuthToken(false);
// Logout is now handled by GalleryAuthContext
},
};
+8
View File
@@ -0,0 +1,8 @@
// Cleanup function to remove old gallery authentication data
export const cleanupOldGalleryAuth = () => {
// Remove old global gallery authentication
localStorage.removeItem('gallery_event');
// Remove old gallery token from cookies if it exists
document.cookie = 'gallery_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
};