Files
erp/apps/web/src/stores/auth.ts
iven 3a05523d23 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
2026-04-11 12:36:34 +08:00

70 lines
2.2 KiB
TypeScript

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;
loading: boolean;
login: (username: string, password: string) => Promise<void>;
logout: () => Promise<void>;
loadFromStorage: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: initial.user,
isAuthenticated: initial.isAuthenticated,
loading: false,
login: async (username, password) => {
set({ loading: true });
try {
const resp = await apiLogin({ username, password });
localStorage.setItem('access_token', resp.access_token);
localStorage.setItem('refresh_token', resp.refresh_token);
localStorage.setItem('user', JSON.stringify(resp.user));
set({ user: resp.user, isAuthenticated: true, loading: false });
} catch (error) {
set({ loading: false });
throw error;
}
},
logout: async () => {
try {
await apiLogout();
} catch {
// Ignore logout API errors
}
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user');
set({ user: null, isAuthenticated: false });
},
// Kept for backward compatibility but no longer needed since
// initial state is restored synchronously at store creation.
loadFromStorage: () => {
const state = restoreInitialState();
set({ user: state.user, isAuthenticated: state.isAuthenticated });
},
}));