feat(auth): enhance access control in PrivateRoute and display error message in LoginPage
This commit is contained in:
+16
-2
@@ -43,9 +43,12 @@ import PwaInstallBanner from './components/ui/PwaInstallBanner';
|
||||
|
||||
// ── Guards ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const ALLOWED_ROLES = ['admin', 'doctor', 'clinic', 'secretary'] as const;
|
||||
|
||||
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||
const { isAuthenticated, primaryRole, availableContexts, dbUuid, fetchMe } = useAuthStore();
|
||||
const { isAuthenticated, primaryRole, availableContexts, dbUuid, fetchMe, logout } = useAuthStore();
|
||||
const location = useLocation();
|
||||
const [accessDenied, setAccessDenied] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && !primaryRole) {
|
||||
@@ -53,10 +56,21 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}, [isAuthenticated, primaryRole, fetchMe]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && primaryRole && !ALLOWED_ROLES.includes(primaryRole as any)) {
|
||||
setAccessDenied(true);
|
||||
logout();
|
||||
}
|
||||
}, [isAuthenticated, primaryRole, logout]);
|
||||
|
||||
if (accessDenied) {
|
||||
return <Navigate to="/admin/login?error=access_denied" replace />;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) return <Navigate to="/admin/login" replace />;
|
||||
|
||||
// اگر context هنوز لود نشده — صبر کن
|
||||
if (isAuthenticated && !primaryRole) {
|
||||
if (!primaryRole) {
|
||||
return <div style={{ padding: 40, textAlign: 'center' }}>در حال بارگذاری...</div>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
@@ -10,6 +11,7 @@ type ForgotStep = 1 | 2 | 3;
|
||||
|
||||
export default function LoginPage() {
|
||||
const login = useAuthStore((s) => s.login);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [mode, setMode] = useState<Mode>('password');
|
||||
const [pwMobile, setPwMobile] = useState('');
|
||||
@@ -186,6 +188,17 @@ export default function LoginPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{searchParams.get('error') === 'access_denied' && (
|
||||
<div style={{
|
||||
background: '#fee2e2', border: '1px solid #fca5a5',
|
||||
borderRadius: 8, padding: '10px 14px',
|
||||
fontSize: 13, color: '#dc2626', marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
حساب شما دسترسی به پنل مدیریت را ندارد
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode !== 'forgot' && (
|
||||
<div style={{ display: 'flex', gap: 4, marginBottom: 24, background: 'var(--bg-2)', borderRadius: 10, padding: 4 }}>
|
||||
{(['password', 'sms'] as const).map((m) => (
|
||||
|
||||
Reference in New Issue
Block a user