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:
+24
-13
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
@@ -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=/;';
|
||||
};
|
||||
Reference in New Issue
Block a user