fix: mail plugin CSRF token handling and trailing slash
- Add CSRF token storage in sessionStorage (persists across reloads) - Send X-CSRF-Token header on POST/PATCH/DELETE requests - Store CSRF token on login, clear on logout - Fix trailing slash in mail list API call (/mail/ -> /mail)
This commit is contained in:
@@ -15,6 +15,23 @@ export const apiClient = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CSRF token storage — persisted in sessionStorage, sent on all unsafe methods
|
||||||
|
const CSRF_KEY = 'leocrm_csrf_token';
|
||||||
|
let csrfToken: string | null = sessionStorage.getItem(CSRF_KEY);
|
||||||
|
|
||||||
|
export function setCsrfToken(token: string | null) {
|
||||||
|
csrfToken = token;
|
||||||
|
if (token) {
|
||||||
|
sessionStorage.setItem(CSRF_KEY, token);
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(CSRF_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCsrfToken(): string | null {
|
||||||
|
return csrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
let onUnauthorized: (() => void) | null = null;
|
let onUnauthorized: (() => void) | null = null;
|
||||||
let onValidationError: ((errors: Record<string, string[]>) => void) | null = null;
|
let onValidationError: ((errors: Record<string, string[]>) => void) | null = null;
|
||||||
|
|
||||||
@@ -28,6 +45,11 @@ export function setValidationErrorHandler(handler: (errors: Record<string, strin
|
|||||||
|
|
||||||
apiClient.interceptors.request.use(
|
apiClient.interceptors.request.use(
|
||||||
(config: InternalAxiosRequestConfig) => {
|
(config: InternalAxiosRequestConfig) => {
|
||||||
|
// Attach CSRF token on unsafe methods
|
||||||
|
const unsafe = ['post', 'put', 'patch', 'delete'];
|
||||||
|
if (unsafe.includes(config.method?.toLowerCase() ?? '') && csrfToken) {
|
||||||
|
config.headers['X-CSRF-Token'] = csrfToken;
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiPost, apiGet, apiPatch, apiDelete, apiClient } from './client';
|
import { apiPost, apiGet, apiPatch, apiDelete, apiClient, setCsrfToken } from './client';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
|
|
||||||
export interface LoginPayload {
|
export interface LoginPayload {
|
||||||
@@ -112,6 +112,10 @@ export function useLogin() {
|
|||||||
onSuccess: (data: any) => {
|
onSuccess: (data: any) => {
|
||||||
setUser(data.user || data);
|
setUser(data.user || data);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
// Store CSRF token for subsequent unsafe requests
|
||||||
|
if (data.csrf_token) {
|
||||||
|
setCsrfToken(data.csrf_token);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
setError(error.message || 'Login failed');
|
setError(error.message || 'Login failed');
|
||||||
@@ -126,6 +130,7 @@ export function useLogout() {
|
|||||||
mutationFn: () => apiPost('/auth/logout'),
|
mutationFn: () => apiPost('/auth/logout'),
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
logout();
|
logout();
|
||||||
|
setCsrfToken(null);
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ export function deleteFolder(folderId: string): Promise<void> {
|
|||||||
|
|
||||||
export function fetchMails(folderId: string, page: number): Promise<MailListResult> {
|
export function fetchMails(folderId: string, page: number): Promise<MailListResult> {
|
||||||
const qs = new URLSearchParams({ folder_id: folderId, page: String(page) }).toString();
|
const qs = new URLSearchParams({ folder_id: folderId, page: String(page) }).toString();
|
||||||
return apiGet<MailListResult>(`/mail/?${qs}`);
|
return apiGet<MailListResult>(`/mail?${qs}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMail(mailId: string): Promise<Mail> {
|
export function getMail(mailId: string): Promise<Mail> {
|
||||||
|
|||||||
Reference in New Issue
Block a user