fix: close permission review gaps

fix: harden permission-gated UI — minimum-org endpoint, modal/Popconfirm fail-closed on revocation
This commit is contained in:
2026-07-23 12:23:33 +08:00
parent c98d37307e
commit a7a7af1667
17 changed files with 391 additions and 227 deletions

View File

@@ -86,25 +86,57 @@ const MainLayout: React.FC = () => {
useEffect(() => {
let cancelled = false;
beginPermissionVerification();
api
.get<{ id: number; username: string; permissions: string[]; roles?: string[] }>(
'/auth/profile',
)
.then((profile) => {
if (cancelled) return;
writePermissions(profile.permissions || []);
const cachedUser = JSON.parse(localStorage.getItem('user') || '{}');
const nextUser = { ...cachedUser, ...profile };
localStorage.setItem('user', JSON.stringify(nextUser));
setUser(nextUser);
})
.catch(() => {
if (!cancelled) clearPermissions('ready');
// The API interceptor handles expired/invalid sessions.
});
let retryTimer: number | undefined;
let verificationInFlight = false;
const verifyPermissions = () => {
if (cancelled || verificationInFlight || !localStorage.getItem('token')) return;
if (retryTimer !== undefined) {
window.clearTimeout(retryTimer);
retryTimer = undefined;
}
verificationInFlight = true;
beginPermissionVerification();
api
.get<{ id: number; username: string; permissions: string[]; roles?: string[] }>(
'/auth/profile',
)
.then((profile) => {
if (cancelled) return;
verificationInFlight = false;
writePermissions(profile.permissions || []);
const cachedUser = JSON.parse(localStorage.getItem('user') || '{}');
const nextUser = { ...cachedUser, ...profile };
localStorage.setItem('user', JSON.stringify(nextUser));
setUser(nextUser);
})
.catch(() => {
verificationInFlight = false;
if (cancelled || !localStorage.getItem('token')) return;
retryTimer = window.setTimeout(verifyPermissions, 5_000);
});
};
const handleStorage = (event: StorageEvent) => {
if (event.key !== 'token' && event.key !== 'permissions') return;
beginPermissionVerification();
window.location.reload();
};
const handleOnline = () => verifyPermissions();
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') verifyPermissions();
};
verifyPermissions();
window.addEventListener('storage', handleStorage);
window.addEventListener('online', handleOnline);
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
cancelled = true;
if (retryTimer !== undefined) window.clearTimeout(retryTimer);
window.removeEventListener('storage', handleStorage);
window.removeEventListener('online', handleOnline);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);