91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||
import * as authApi from '../../api/auth';
|
||
import { ApiError } from '../../api/http';
|
||
import { DEFAULT_TENANT } from '../../config';
|
||
import { useAuth } from '../../context/AuthContext';
|
||
import * as permApi from '../../api/permission';
|
||
|
||
type OAuthMode = 'login' | 'register';
|
||
|
||
export function OAuthCallbackPage({ mode }: { mode: OAuthMode }) {
|
||
const navigate = useNavigate();
|
||
const [search] = useSearchParams();
|
||
const { syncSession, refreshRoles } = useAuth();
|
||
const [error, setError] = useState('');
|
||
|
||
useEffect(() => {
|
||
const code = search.get('code');
|
||
const state = search.get('state');
|
||
const oauthError = search.get('error');
|
||
const tenant =
|
||
localStorage.getItem('tenant_slug') ?? DEFAULT_TENANT;
|
||
|
||
if (oauthError) {
|
||
setError(search.get('error_description') ?? oauthError);
|
||
return;
|
||
}
|
||
if (!code || !state) {
|
||
setError('缺少 OAuth 參數(code / state)');
|
||
return;
|
||
}
|
||
|
||
let cancelled = false;
|
||
|
||
(async () => {
|
||
try {
|
||
if (mode === 'login') {
|
||
const result = await authApi.loginSocialCallback(code, state);
|
||
if (result.mfa_required && result.mfa_challenge_id) {
|
||
navigate('/login', {
|
||
replace: true,
|
||
state: {
|
||
message: '請完成雙因素驗證以完成登入',
|
||
mfa_challenge_id: result.mfa_challenge_id,
|
||
tenant,
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
} else {
|
||
await authApi.registerSocialCallback(code, state);
|
||
}
|
||
|
||
if (cancelled) return;
|
||
syncSession();
|
||
await refreshRoles();
|
||
const me = await permApi.getMyPermissions();
|
||
const admin = permApi.isAdminRole(me.roles ?? []);
|
||
navigate(admin ? '/admin' : '/app', { replace: true });
|
||
} catch (err) {
|
||
if (cancelled) return;
|
||
if (err instanceof ApiError && err.code === 29301000 && mode === 'login') {
|
||
setError('尚無帳號,請先註冊或聯絡管理員開通 LDAP 帳號。');
|
||
} else {
|
||
setError(err instanceof ApiError ? err.message : '登入失敗');
|
||
}
|
||
}
|
||
})();
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [mode, navigate, refreshRoles, search, syncSession]);
|
||
|
||
return (
|
||
<div className="auth-card">
|
||
<h1>{mode === 'login' ? '登入處理中' : '註冊處理中'}</h1>
|
||
{error ? (
|
||
<>
|
||
<p className="form-error">{error}</p>
|
||
<p className="auth-footer">
|
||
<Link to={mode === 'login' ? '/login' : '/register'}>返回</Link>
|
||
</p>
|
||
</>
|
||
) : (
|
||
<p className="auth-hint">正在與身分提供者確認,請稍候…</p>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|