fix(auth): Block access to protected routes for users without 'active' status in supabase
This commit is contained in:
@@ -3,22 +3,42 @@ import { Navigate } from 'react-router-dom';
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
|
|
||||||
|
import { useUserProfile } from '@/hooks/useUserProfile';
|
||||||
|
|
||||||
interface ProtectedAdminRouteProps {
|
interface ProtectedAdminRouteProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProtectedAdminRoute: React.FC<ProtectedAdminRouteProps> = ({ children }) => {
|
export const ProtectedAdminRoute: React.FC<ProtectedAdminRouteProps> = ({ children }) => {
|
||||||
const { user, loading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
const { isAdmin, loading: roleLoading } = useUserRole();
|
const { isAdmin, loading: roleLoading } = useUserRole();
|
||||||
|
const { profile, loading: profileLoading } = useUserProfile();
|
||||||
|
|
||||||
if (loading || roleLoading) {
|
const loading = authLoading || roleLoading || profileLoading;
|
||||||
return <div>Carregando...</div>;
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-background">
|
||||||
|
<div className="text-muted-foreground">Carregando...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return <Navigate to="/auth" replace />;
|
return <Navigate to="/auth" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar se o usuário está ativo no sistema
|
||||||
|
if (profile && profile.status !== 'active') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-center p-4">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Acesso Bloqueado</h1>
|
||||||
|
<p className="text-muted-foreground mb-2">Seu usuário existe, mas encontra-se com status: <strong>{profile.status || 'pendente'}</strong>.</p>
|
||||||
|
<p className="text-muted-foreground">Por favor, contate o administrador para aprovar o seu acesso.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
return <Navigate to="/dashboard" replace />;
|
return <Navigate to="/dashboard" replace />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { useUserProfile } from '@/hooks/useUserProfile';
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
|
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
|
||||||
const { user, loading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
|
const { profile, loading: profileLoading } = useUserProfile();
|
||||||
|
|
||||||
|
const loading = authLoading || profileLoading;
|
||||||
|
|
||||||
// Mostrar loading enquanto carrega autenticação
|
// Mostrar loading enquanto carrega autenticação
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -23,7 +27,16 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
|
|||||||
return <Navigate to="/auth" replace />;
|
return <Navigate to="/auth" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logto já valida o usuário via JWT/OIDC.
|
// Verificar se o usuário está ativo no sistema
|
||||||
// Não precisa de profile separado no Supabase.
|
if (profile && profile.status !== 'active') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-center p-4">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Acesso Bloqueado</h1>
|
||||||
|
<p className="text-muted-foreground mb-2">Seu usuário existe, mas encontra-se com status: <strong>{profile.status || 'pendente'}</strong>.</p>
|
||||||
|
<p className="text-muted-foreground">Por favor, contate o administrador para aprovar o seu acesso.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,8 @@ import { Navigate } from 'react-router-dom';
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
|
|
||||||
|
import { useUserProfile } from '@/hooks/useUserProfile';
|
||||||
|
|
||||||
interface ProtectedRouteByResourceProps {
|
interface ProtectedRouteByResourceProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
resourceKey: string;
|
resourceKey: string;
|
||||||
@@ -11,10 +13,13 @@ interface ProtectedRouteByResourceProps {
|
|||||||
export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> = ({
|
export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { user, loading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
const { isAdmin, loading: roleLoading } = useUserRole();
|
const { isAdmin, loading: roleLoading } = useUserRole();
|
||||||
|
const { profile, loading: profileLoading } = useUserProfile();
|
||||||
|
|
||||||
if (loading || roleLoading) {
|
const loading = authLoading || roleLoading || profileLoading;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
<div className="text-muted-foreground">Carregando...</div>
|
<div className="text-muted-foreground">Carregando...</div>
|
||||||
@@ -26,6 +31,17 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
|
|||||||
return <Navigate to="/auth" replace />;
|
return <Navigate to="/auth" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar se o usuário está ativo no sistema
|
||||||
|
if (profile && profile.status !== 'active') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-center p-4">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Acesso Bloqueado</h1>
|
||||||
|
<p className="text-muted-foreground mb-2">Seu usuário existe, mas encontra-se com status: <strong>{profile.status || 'pendente'}</strong>.</p>
|
||||||
|
<p className="text-muted-foreground">Por favor, contate o administrador para aprovar o seu acesso.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Migração pro Logto: todos usuários autenticados têm acesso aos recursos.
|
// Migração pro Logto: todos usuários autenticados têm acesso aos recursos.
|
||||||
// Sistema de permissões granulares fica pra depois.
|
// Sistema de permissões granulares fica pra depois.
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface UserProfile {
|
|||||||
full_name: string | null;
|
full_name: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
profile_image_url: string | null;
|
profile_image_url: string | null;
|
||||||
|
status: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUserProfile() {
|
export function useUserProfile() {
|
||||||
@@ -21,6 +22,7 @@ export function useUserProfile() {
|
|||||||
setProfile(null);
|
setProfile(null);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
const fetchUserProfile = async () => {
|
const fetchUserProfile = async () => {
|
||||||
@@ -30,7 +32,7 @@ export function useUserProfile() {
|
|||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('full_name, email, profile_image_url')
|
.select('full_name, email, profile_image_url, status')
|
||||||
.eq('id', user.id)
|
.eq('id', user.id)
|
||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
@@ -41,7 +43,8 @@ export function useUserProfile() {
|
|||||||
setProfile({
|
setProfile({
|
||||||
full_name: user.user_metadata?.full_name || null,
|
full_name: user.user_metadata?.full_name || null,
|
||||||
email: user.email || null,
|
email: user.email || null,
|
||||||
profile_image_url: null
|
profile_image_url: null,
|
||||||
|
status: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -50,7 +53,8 @@ export function useUserProfile() {
|
|||||||
setProfile({
|
setProfile({
|
||||||
full_name: user.user_metadata?.full_name || null,
|
full_name: user.user_metadata?.full_name || null,
|
||||||
email: user.email || null,
|
email: user.email || null,
|
||||||
profile_image_url: null
|
profile_image_url: null,
|
||||||
|
status: null
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user