Harden auth cookies and fix native schema for event creation
Mirror to GitHub / mirror (push) Successful in 45s
Test and Lint / backend-test (push) Successful in 1m37s
Test and Lint / frontend-test (push) Successful in 2m8s
Version and Release / version-bump (push) Successful in 1m0s
Version and Release / trigger-drone (push) Successful in 3s

This commit is contained in:
2025-09-18 15:58:58 +02:00
parent bda76ff513
commit 71e7179145
35 changed files with 1055 additions and 381 deletions
+5 -100
View File
@@ -1,9 +1,4 @@
import axios, { AxiosHeaders } from 'axios';
import Cookies from 'js-cookie';
// Cookie keys
export const ADMIN_TOKEN_KEY = 'admin_token';
export const GALLERY_TOKEN_KEY = 'gallery_token';
import axios from 'axios';
// Maintenance mode callback
let maintenanceModeCallback: ((enabled: boolean) => void) | null = null;
@@ -18,80 +13,12 @@ export const api = axios.create({
headers: {
'Content-Type': 'application/json',
},
withCredentials: false, // Ensure we're not relying on cookies
withCredentials: true,
});
// Request interceptor to add auth token
// Request interceptor: drop Content-Type for FormData payloads so the browser can set boundaries
api.interceptors.request.use(
(config) => {
// Don't process if headers are already set by the component
const existingAuth = config.headers?.['Authorization'] || config.headers?.get?.('Authorization');
// If authorization is already set by the component, don't override it
if (existingAuth) {
return config;
}
// Check if it's an admin route or gallery route
const isAdminRoute = config.url?.includes('/admin');
if (isAdminRoute) {
const token = Cookies.get(ADMIN_TOKEN_KEY);
if (token) {
if (!config.headers) {
config.headers = {};
}
config.headers['Authorization'] = `Bearer ${token}`;
}
} else {
// For gallery routes, try to extract slug from the request URL first
const galleryMatch = config.url?.match(/gallery\/([^\/]+)/);
if (galleryMatch && galleryMatch[1]) {
const galleryIdOrSlug = galleryMatch[1];
// Remove any query parameters from the slug
const cleanIdOrSlug = galleryIdOrSlug.split('?')[0];
// Check if it's a numeric ID (for upload endpoints)
let token = null;
if (/^\d+$/.test(cleanIdOrSlug)) {
// It's an event ID - try to find the token from current page slug
const pathParts = window.location.pathname.split('/');
if (pathParts[1] === 'gallery' && pathParts[2]) {
const gallerySlug = pathParts[2];
const cleanSlug = gallerySlug.split('?')[0];
token = localStorage.getItem(`gallery_token_${cleanSlug}`);
}
} else {
// It's a slug - use it directly
token = localStorage.getItem(`gallery_token_${cleanIdOrSlug}`);
}
if (token) {
if (!config.headers) {
config.headers = {};
}
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];
// Remove any query parameters from the slug
const cleanSlug = gallerySlug.split('?')[0];
const token = localStorage.getItem(`gallery_token_${cleanSlug}`);
if (token) {
if (!config.headers) {
config.headers = {};
}
config.headers['Authorization'] = `Bearer ${token}`;
}
}
}
}
// Don't set Content-Type for FormData - let browser set it with boundary
if (config.data instanceof FormData) {
delete config.headers?.['Content-Type'];
}
@@ -110,10 +37,9 @@ api.interceptors.response.use(
// Handle maintenance mode (503)
if (error.response?.status === 503) {
const isAdminRoute = error.config?.url?.includes('/admin');
const hasAdminAuth = error.config?.headers?.Authorization?.startsWith('Bearer ');
// Only trigger maintenance mode for non-admin routes or unauthenticated admin routes
if (!isAdminRoute || !hasAdminAuth) {
if (!isAdminRoute) {
if (maintenanceModeCallback) {
maintenanceModeCallback(true);
}
@@ -126,8 +52,6 @@ api.interceptors.response.use(
const currentPath = window.location.pathname;
if (isAdminRoute) {
// Clear admin token on unauthorized
Cookies.remove(ADMIN_TOKEN_KEY);
// Only redirect if we're not already on the admin login page
if (!currentPath.includes('/admin/login')) {
window.location.href = '/admin/login';
@@ -144,8 +68,7 @@ api.interceptors.response.use(
// Don't clear tokens for image requests - they might just need a retry
if (!isImageRequest && galleryMatch && galleryMatch[1]) {
const gallerySlug = galleryMatch[1];
localStorage.removeItem(`gallery_token_${gallerySlug}`);
localStorage.removeItem(`gallery_event_${gallerySlug}`);
sessionStorage.removeItem(`gallery_event_${gallerySlug}`);
}
// Don't redirect - let the component handle the auth state
} else if (galleryMatch) {
@@ -159,21 +82,3 @@ api.interceptors.response.use(
return Promise.reject(error);
}
);
// Helper to set auth tokens
export const setAuthToken = (token: string, isAdmin: boolean = false) => {
const key = isAdmin ? ADMIN_TOKEN_KEY : GALLERY_TOKEN_KEY;
Cookies.set(key, token, { expires: 1 }); // 1 day expiry
};
// Helper to clear auth tokens
export const clearAuthToken = (isAdmin: boolean = false) => {
const key = isAdmin ? ADMIN_TOKEN_KEY : GALLERY_TOKEN_KEY;
Cookies.remove(key);
};
// Helper to get auth tokens
export const getAuthToken = (isAdmin: boolean = false) => {
const key = isAdmin ? ADMIN_TOKEN_KEY : GALLERY_TOKEN_KEY;
return Cookies.get(key);
};