mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 05:23:14 +00:00
fix: WEB-C1 move JWT to sessionStorage, complete mutation error handling
- WEB-C1 (Critical): Replace all localStorage token operations with sessionStorage in authSlice.ts and client.ts. Tokens now clear when the browser tab closes, reducing the XSS token-theft window. httpOnly cookie migration documented as follow-up. - WEB-M2: 401 interceptor now dispatches Redux logout() before redirect so auth state stays consistent with cleared storage. - WEB-H5/H6: Add onError toast handlers to sendMutation, reviseMutation, and regenerateMutation in AdminWorkspace.
This commit is contained in:
parent
4f1271eb50
commit
67b4732395
3 changed files with 24 additions and 5 deletions
|
|
@ -9,9 +9,12 @@ interface AuthState {
|
|||
error: string | null;
|
||||
}
|
||||
|
||||
// Fix: WEB-C1 — use sessionStorage instead of localStorage to limit JWT exposure.
|
||||
// Tokens are cleared when the browser tab closes, reducing XSS token-theft window.
|
||||
// Follow-up: move to httpOnly cookies once the API supports Set-Cookie auth flow.
|
||||
const getUserFromStorage = (): AuthUser | null => {
|
||||
try {
|
||||
const result = window.localStorage.getItem(STORAGE_KEY_TOKEN);
|
||||
const result = window.sessionStorage.getItem(STORAGE_KEY_TOKEN);
|
||||
return result ? JSON.parse(result) : null;
|
||||
} catch {
|
||||
return null;
|
||||
|
|
@ -48,13 +51,13 @@ const authSlice = createSlice({
|
|||
state.isAuthenticated = action.payload.token ? isTokenValid(action.payload.token) : false;
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
window.localStorage.setItem(STORAGE_KEY_TOKEN, JSON.stringify(action.payload));
|
||||
window.sessionStorage.setItem(STORAGE_KEY_TOKEN, JSON.stringify(action.payload)); // WEB-C1
|
||||
},
|
||||
logout: (state) => {
|
||||
state.user = null;
|
||||
state.isAuthenticated = false;
|
||||
state.error = null;
|
||||
window.localStorage.removeItem(STORAGE_KEY_TOKEN);
|
||||
window.sessionStorage.removeItem(STORAGE_KEY_TOKEN); // WEB-C1
|
||||
},
|
||||
setLoading: (state, action: PayloadAction<boolean>) => {
|
||||
state.loading = action.payload;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import { API_URL, STORAGE_KEY_TOKEN } from '../../constants';
|
||||
import { store } from '../../app/store';
|
||||
import { logout } from '../../app/slices/authSlice';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_URL,
|
||||
|
|
@ -11,7 +13,7 @@ const apiClient = axios.create({
|
|||
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const tokenData = window.localStorage.getItem(STORAGE_KEY_TOKEN);
|
||||
const tokenData = window.sessionStorage.getItem(STORAGE_KEY_TOKEN); // WEB-C1
|
||||
if (tokenData) {
|
||||
try {
|
||||
const parsed = JSON.parse(tokenData);
|
||||
|
|
@ -34,7 +36,9 @@ apiClient.interceptors.response.use(
|
|||
const { status, data } = error.response;
|
||||
|
||||
if (status === 401) {
|
||||
window.localStorage.removeItem(STORAGE_KEY_TOKEN);
|
||||
// Fix: WEB-C1 — use sessionStorage; WEB-M2 — clear Redux auth state before redirect
|
||||
window.sessionStorage.removeItem(STORAGE_KEY_TOKEN);
|
||||
store.dispatch(logout());
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(new Error('Session expired. Please log in again.'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,6 +166,10 @@ export default function AdminWorkspace() {
|
|||
setSendDialogOpen(false);
|
||||
toast.success('Proposal marked as sent');
|
||||
},
|
||||
// Fix: WEB-H5 — add missing onError handler
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Send failed: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const reviseMutation = useMutation({
|
||||
|
|
@ -175,6 +179,10 @@ export default function AdminWorkspace() {
|
|||
setReviseDialogOpen(false);
|
||||
toast.success('Revision created');
|
||||
},
|
||||
// Fix: WEB-H6 — add missing onError handler
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Revision failed: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const regenerateMutation = useMutation({
|
||||
|
|
@ -182,6 +190,10 @@ export default function AdminWorkspace() {
|
|||
onSuccess: () => {
|
||||
toast.info('AI suggestion generation started');
|
||||
},
|
||||
// Fix: WEB-H5 — add missing onError handler
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Regeneration failed: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const pdfMutation = useMutation({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue