This commit is contained in:
@@ -5,7 +5,6 @@ import { setTokenGetter } from './api-client';
|
||||
const AUTH_API = '/api/auth';
|
||||
|
||||
export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [mode, setMode] = useState<'sso' | 'password'>('sso');
|
||||
const [state, setState] = useState<AuthState>({
|
||||
isLoading: true,
|
||||
isAuthenticated: false,
|
||||
@@ -20,6 +19,8 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setTokenGetter(() => tokenRef.current);
|
||||
|
||||
// 防止 StrictMode 双重调用(jumpToken 一次性使用)
|
||||
if (authStarted.current) return;
|
||||
authStarted.current = true;
|
||||
|
||||
// 监听 401 事件
|
||||
const onUnauthorized = () => {
|
||||
@@ -29,25 +30,14 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
};
|
||||
window.addEventListener('auth:unauthorized', onUnauthorized);
|
||||
|
||||
if (!authStarted.current) { authStarted.current = true; authenticate(); }
|
||||
authenticate();
|
||||
|
||||
return () => window.removeEventListener('auth:unauthorized', onUnauthorized);
|
||||
}, []);
|
||||
|
||||
async function authenticate() {
|
||||
let currentMode: 'sso' | 'password';
|
||||
try {
|
||||
const response = await fetch(`${AUTH_API}/config`, { cache: 'no-store' });
|
||||
const config = await response.json();
|
||||
if (!response.ok || !['sso', 'password'].includes(config.mode)) throw new Error();
|
||||
currentMode = config.mode;
|
||||
setMode(currentMode);
|
||||
} catch {
|
||||
setState({ isLoading: false, isAuthenticated: false, user: null, error: '无法获取验证配置,请刷新重试' });
|
||||
return;
|
||||
}
|
||||
// 本地开发免登录开关:.env 里设 VITE_DEV_BYPASS_AUTH=1 启用,仅 dev 生效
|
||||
if (currentMode === 'sso' && import.meta.env.DEV && import.meta.env.VITE_DEV_BYPASS_AUTH === '1') {
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_DEV_BYPASS_AUTH === '1') {
|
||||
setState({
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
@@ -69,15 +59,15 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
tokenRef.current = savedToken;
|
||||
// 验证 token 是否仍然有效(尝试请求 health)
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API}/me`, {
|
||||
const res = await fetch('/api/health', {
|
||||
headers: { Authorization: `Bearer ${savedToken}` },
|
||||
});
|
||||
if (res.ok) {
|
||||
const savedUser = await res.json();
|
||||
const savedUser = sessionStorage.getItem('bi_user');
|
||||
setState({
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
user: savedUser,
|
||||
user: savedUser ? JSON.parse(savedUser) : null,
|
||||
error: null,
|
||||
});
|
||||
return;
|
||||
@@ -85,12 +75,6 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
} catch { /* token 无效,继续流程 */ }
|
||||
sessionStorage.removeItem('bi_jwt');
|
||||
sessionStorage.removeItem('bi_user');
|
||||
tokenRef.current = null;
|
||||
}
|
||||
|
||||
if (currentMode === 'password') {
|
||||
setState({ isLoading: false, isAuthenticated: false, user: null, error: null });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 从 URL 提取 jumpToken
|
||||
@@ -135,18 +119,8 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginWithPassword(password: string) {
|
||||
const response = await fetch(`${AUTH_API}/password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) });
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.token) throw new Error(data.message || '登录失败');
|
||||
tokenRef.current = data.token;
|
||||
sessionStorage.setItem('bi_jwt', data.token);
|
||||
sessionStorage.setItem('bi_user', JSON.stringify(data.user));
|
||||
setState({ isLoading: false, isAuthenticated: true, user: data.user, error: null });
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ ...state, mode, loginWithPassword }}>
|
||||
<AuthContext.Provider value={state}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
export default function PasswordLogin() {
|
||||
const { loginWithPassword, error: sessionError } = useAuth();
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
return <main className="min-h-screen flex items-center justify-center bg-slate-950 p-6">
|
||||
<form className="w-full max-w-sm rounded-2xl bg-white p-7 shadow-xl" onSubmit={async event => {
|
||||
event.preventDefault(); if (busy || !loginWithPassword) return;
|
||||
setBusy(true); setError('');
|
||||
try { await loginWithPassword(password); } catch (e) { setError(e instanceof Error ? e.message : '登录失败,请重试'); }
|
||||
finally { setBusy(false); setPassword(''); }
|
||||
}}>
|
||||
<p className="text-xs font-semibold text-blue-600">羚牛 · 能源 BI</p>
|
||||
<h1 className="mt-2 text-2xl font-bold text-slate-900">访问看板</h1>
|
||||
<p className="mt-2 mb-6 text-sm text-slate-500">请输入管理员提供的访问密码。此入口仅提供只读访问。</p>
|
||||
<label htmlFor="bi-password" className="text-sm font-medium text-slate-700">访问密码</label>
|
||||
<input id="bi-password" type="password" autoComplete="current-password" required maxLength={1024} value={password} onChange={e => setPassword(e.target.value)} className="mt-2 w-full rounded-lg border border-slate-300 px-3 py-3 text-base focus:outline-blue-600" />
|
||||
{error || sessionError ? <p role="alert" className="mt-3 text-sm text-red-600">{error || sessionError}</p> : null}
|
||||
<button disabled={busy || !password} className="mt-5 min-h-11 w-full rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white disabled:opacity-50">{busy ? '正在验证…' : '进入看板'}</button>
|
||||
</form>
|
||||
</main>;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export interface AuthState {
|
||||
mode?: 'sso' | 'password';
|
||||
loginWithPassword?: (password: string) => Promise<void>;
|
||||
isLoading: boolean;
|
||||
isAuthenticated: boolean;
|
||||
user: {
|
||||
|
||||
Reference in New Issue
Block a user