fix: address Phase 1-2 audit findings
- CORS: replace permissive() with configurable whitelist (default.toml) - Auth store: synchronously restore state at creation to eliminate flash-of-login-page on refresh - MainLayout: menu highlight now tracks current route via useLocation - Add extractErrorMessage() utility to reduce repeated error parsing - Fix all clippy warnings across 4 crates (erp-auth, erp-config, erp-workflow, erp-message): remove unnecessary casts, use div_ceil, collapse nested ifs, reduce function arguments with DTOs
This commit is contained in:
13
apps/web/src/api/errors.ts
Normal file
13
apps/web/src/api/errors.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Extract a user-friendly error message from an Axios error response.
|
||||
*
|
||||
* The backend returns `{ success: false, message: "..." }` on errors.
|
||||
* This helper centralizes the extraction logic to avoid repeating the
|
||||
* same type assertion in every catch block.
|
||||
*/
|
||||
export function extractErrorMessage(err: unknown, fallback = '操作失败'): string {
|
||||
return (
|
||||
(err as { response?: { data?: { message?: string } } })?.response?.data
|
||||
?.message || fallback
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,25 @@
|
||||
import { create } from 'zustand';
|
||||
import { login as apiLogin, logout as apiLogout, type UserInfo } from '../api/auth';
|
||||
|
||||
// Synchronously restore auth state from localStorage at store creation time.
|
||||
// This eliminates the flash-of-login-page on refresh because isAuthenticated
|
||||
// is already `true` before the first render.
|
||||
function restoreInitialState(): { user: UserInfo | null; isAuthenticated: boolean } {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const userStr = localStorage.getItem('user');
|
||||
if (token && userStr) {
|
||||
try {
|
||||
const user = JSON.parse(userStr) as UserInfo;
|
||||
return { user, isAuthenticated: true };
|
||||
} catch {
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
}
|
||||
return { user: null, isAuthenticated: false };
|
||||
}
|
||||
|
||||
const initial = restoreInitialState();
|
||||
|
||||
interface AuthState {
|
||||
user: UserInfo | null;
|
||||
isAuthenticated: boolean;
|
||||
@@ -11,8 +30,8 @@ interface AuthState {
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
user: initial.user,
|
||||
isAuthenticated: initial.isAuthenticated,
|
||||
loading: false,
|
||||
|
||||
login: async (username, password) => {
|
||||
@@ -41,16 +60,10 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
set({ user: null, isAuthenticated: false });
|
||||
},
|
||||
|
||||
// Kept for backward compatibility but no longer needed since
|
||||
// initial state is restored synchronously at store creation.
|
||||
loadFromStorage: () => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const userStr = localStorage.getItem('user');
|
||||
if (token && userStr) {
|
||||
try {
|
||||
const user = JSON.parse(userStr) as UserInfo;
|
||||
set({ user, isAuthenticated: true });
|
||||
} catch {
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
}
|
||||
const state = restoreInitialState();
|
||||
set({ user: state.user, isAuthenticated: state.isAuthenticated });
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user