refactor(build): remove Gruntfile and transition to npm scripts for build process
- Deleted Gruntfile.js to streamline the build process. - Updated package.json to include new npm scripts for build, clean, and watch tasks. - Added dependencies for concurrent execution and CSRF token management. - Integrated csrf-sync for improved CSRF protection in the application. - Refactored app and API client to utilize the new CSRF token management approach.
This commit is contained in:
@@ -13,6 +13,7 @@ import router from './app/routes';
|
||||
// Hooks & Utils
|
||||
import { useAppSelector } from './hooks/useAppSelector';
|
||||
import { initMixpanel } from './utils/mixpanelInit';
|
||||
import { initializeCsrfToken } from './api/api-client';
|
||||
|
||||
// Types & Constants
|
||||
import { Language } from './features/i18n/localesSlice';
|
||||
@@ -35,6 +36,13 @@ const App: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
});
|
||||
}, [language]);
|
||||
|
||||
// Initialize CSRF token on app startup
|
||||
useEffect(() => {
|
||||
initializeCsrfToken().catch(error => {
|
||||
logger.error('Failed to initialize CSRF token:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<SuspenseFallback />}>
|
||||
<ThemeWrapper>
|
||||
|
||||
@@ -4,27 +4,36 @@ import alertService from '@/services/alerts/alertService';
|
||||
import logger from '@/utils/errorLogger';
|
||||
import config from '@/config/env';
|
||||
|
||||
export const getCsrfToken = (): string | null => {
|
||||
const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN='));
|
||||
// Store CSRF token in memory (since csrf-sync uses session-based tokens)
|
||||
let csrfToken: string | null = null;
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return decodeURIComponent(match.split('=')[1]);
|
||||
export const getCsrfToken = (): string | null => {
|
||||
return csrfToken;
|
||||
};
|
||||
|
||||
// Function to refresh CSRF token if needed
|
||||
// Function to refresh CSRF token from server
|
||||
export const refreshCsrfToken = async (): Promise<string | null> => {
|
||||
try {
|
||||
// Make a GET request to the server to get a fresh CSRF token
|
||||
await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true });
|
||||
return getCsrfToken();
|
||||
const response = await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true });
|
||||
if (response.data && response.data.token) {
|
||||
csrfToken = response.data.token;
|
||||
return csrfToken;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh CSRF token:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize CSRF token on app load
|
||||
export const initializeCsrfToken = async (): Promise<void> => {
|
||||
if (!csrfToken) {
|
||||
await refreshCsrfToken();
|
||||
}
|
||||
};
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: config.apiUrl,
|
||||
withCredentials: true,
|
||||
@@ -36,12 +45,16 @@ const apiClient = axios.create({
|
||||
|
||||
// Request interceptor
|
||||
apiClient.interceptors.request.use(
|
||||
config => {
|
||||
const token = getCsrfToken();
|
||||
if (token) {
|
||||
config.headers['X-CSRF-Token'] = token;
|
||||
async config => {
|
||||
// Ensure we have a CSRF token before making requests
|
||||
if (!csrfToken) {
|
||||
await refreshCsrfToken();
|
||||
}
|
||||
|
||||
if (csrfToken) {
|
||||
config.headers['X-CSRF-Token'] = csrfToken;
|
||||
} else {
|
||||
console.warn('No CSRF token found');
|
||||
console.warn('No CSRF token available');
|
||||
}
|
||||
return config;
|
||||
},
|
||||
@@ -84,7 +97,7 @@ apiClient.interceptors.response.use(
|
||||
(typeof errorResponse.data === 'object' &&
|
||||
errorResponse.data !== null &&
|
||||
'message' in errorResponse.data &&
|
||||
errorResponse.data.message === 'Invalid CSRF token' ||
|
||||
(errorResponse.data.message === 'invalid csrf token' || errorResponse.data.message === 'Invalid CSRF token') ||
|
||||
(error as any).code === 'EBADCSRFTOKEN')) {
|
||||
alertService.error('Security Error', 'Invalid security token. Refreshing your session...');
|
||||
|
||||
@@ -94,7 +107,7 @@ apiClient.interceptors.response.use(
|
||||
// Update the token in the failed request
|
||||
error.config.headers['X-CSRF-Token'] = newToken;
|
||||
// Retry the original request with the new token
|
||||
return axios(error.config);
|
||||
return apiClient(error.config);
|
||||
} else {
|
||||
// If token refresh failed, redirect to login
|
||||
window.location.href = '/auth/login';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toQueryString } from '@/utils/toQueryString';
|
||||
import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types';
|
||||
import { IMyTask } from '@/types/home/my-tasks.types';
|
||||
import { IProject } from '@/types/project/project.types';
|
||||
import { getCsrfToken } from '../api-client';
|
||||
import { getCsrfToken, refreshCsrfToken } from '../api-client';
|
||||
import config from '@/config/env';
|
||||
|
||||
const rootUrl = '/home';
|
||||
@@ -14,9 +14,18 @@ const api = createApi({
|
||||
reducerPath: 'homePageApi',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
|
||||
prepareHeaders: headers => {
|
||||
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
||||
prepareHeaders: async headers => {
|
||||
// Get CSRF token, refresh if needed
|
||||
let token = getCsrfToken();
|
||||
if (!token) {
|
||||
token = await refreshCsrfToken();
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers.set('X-CSRF-Token', token);
|
||||
}
|
||||
headers.set('Content-Type', 'application/json');
|
||||
return headers;
|
||||
},
|
||||
credentials: 'include',
|
||||
}),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IProjectCategory } from '@/types/project/projectCategory.types';
|
||||
import { IProjectsViewModel } from '@/types/project/projectsViewModel.types';
|
||||
import { IServerResponse } from '@/types/common.types';
|
||||
import { IProjectMembersViewModel } from '@/types/projectMember.types';
|
||||
import { getCsrfToken } from '../api-client';
|
||||
import { getCsrfToken, refreshCsrfToken } from '../api-client';
|
||||
import config from '@/config/env';
|
||||
|
||||
const rootUrl = '/projects';
|
||||
@@ -14,9 +14,18 @@ export const projectsApi = createApi({
|
||||
reducerPath: 'projectsApi',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
|
||||
prepareHeaders: headers => {
|
||||
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
||||
prepareHeaders: async headers => {
|
||||
// Get CSRF token, refresh if needed
|
||||
let token = getCsrfToken();
|
||||
if (!token) {
|
||||
token = await refreshCsrfToken();
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers.set('X-CSRF-Token', token);
|
||||
}
|
||||
headers.set('Content-Type', 'application/json');
|
||||
return headers;
|
||||
},
|
||||
credentials: 'include',
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user