fix(): Ajuste no bloqueio de telas
This commit is contained in:
parent
3114dbb6d7
commit
0d5d285786
15 changed files with 169 additions and 47 deletions
|
|
@ -28,7 +28,7 @@ const geistMono = Geist_Mono({
|
|||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'SAAS - Orius Tecnologia',
|
||||
title: 'Monitoring App - Orius Tecnologia',
|
||||
description: 'Evolução tecnológica com toque humano',
|
||||
icons: {
|
||||
icon: '/images/favicon.ico',
|
||||
|
|
@ -57,13 +57,13 @@ export default function RootLayout({
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
Building Your Application
|
||||
Orius Tecnologia
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>
|
||||
Data Fetching
|
||||
Monitoramento de Servidores
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
|
|
|
|||
30
src/app/api/auth/me/route.ts
Normal file
30
src/app/api/auth/me/route.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import GetSigla from '@/shared/actions/text/GetSigla';
|
||||
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
data?: UserAuthenticatedInterface;
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const token = req.cookies.get('access_token')?.value;
|
||||
if (!token) return NextResponse.json(null);
|
||||
|
||||
const decoded: JwtPayload = jwtDecode(token);
|
||||
if (decoded.data && typeof decoded.data === 'string') {
|
||||
decoded.data = JSON.parse(decoded.data);
|
||||
}
|
||||
if (decoded.data) {
|
||||
decoded.data.sigla = GetSigla(decoded.data.nome || '');
|
||||
}
|
||||
|
||||
return NextResponse.json(decoded);
|
||||
} catch (err) {
|
||||
return NextResponse.json(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
SidebarRail,
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
import useGUsuarioGetJWTHook from '@/shared/hooks/auth/useGUsuarioGetJWTHook';
|
||||
import useUserGetJWTHook from '@/shared/hooks/auth/useUserGetJWTHook';
|
||||
import Image from 'next/image';
|
||||
|
||||
// This is sample data.
|
||||
|
|
@ -52,13 +52,11 @@ const data = {
|
|||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [],
|
||||
]
|
||||
};
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
const { userAuthenticated } = useGUsuarioGetJWTHook();
|
||||
console.log('LOGADO', userAuthenticated)
|
||||
const { userAuthenticated } = useUserGetJWTHook();
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
|
|
@ -78,10 +76,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||
</div>
|
||||
<div className="flex flex-col gap-0.5 leading-none">
|
||||
<span className="font-semibold">
|
||||
Orius Tecnologia
|
||||
Monitoring App
|
||||
</span>
|
||||
<span className="">
|
||||
25.9.1
|
||||
v1.0
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
@ -91,7 +89,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavProjects projects={data.projects} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
{userAuthenticated?.data ? <NavUser user={userAuthenticated.data} /> : 'Carregando...'}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export function NavMain({
|
|||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
<SidebarMenuButton tooltip={item.title} className='cursor-pointer'>
|
||||
{item.icon && <item.icon />}
|
||||
|
||||
<span>{item.title}</span>
|
||||
|
|
|
|||
|
|
@ -101,14 +101,14 @@ export function NavUser({ user }: { user: UserAuthenticatedInterface }) {
|
|||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
{/* <DropdownMenuGroup>
|
||||
<DropdownMenuItem className="cursor-pointer">
|
||||
<Sparkles />
|
||||
Configurações
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator /> */}
|
||||
|
||||
<DropdownMenuItem className="cursor-pointer" onClick={() => handleConfirmOpen()}>
|
||||
<LogOut />
|
||||
|
|
|
|||
|
|
@ -14,37 +14,44 @@ export function middleware(request: NextRequest) {
|
|||
const publicRoute = publicRoutes.find((route) => route.path === path);
|
||||
const authToken = request.cookies.get('access_token');
|
||||
|
||||
// 1. Não autenticado e rota pública → segue normal
|
||||
// Evita loop — se já estiver na página de login, não redireciona novamente
|
||||
if (path === ROOT_PATH) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// 1 Não autenticado e rota pública → segue normal
|
||||
if (!authToken && publicRoute) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// 2. Não autenticado e rota privada → redireciona para raiz
|
||||
// 2 Não autenticado e rota privada → redireciona para login
|
||||
if (!authToken && !publicRoute) {
|
||||
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
|
||||
}
|
||||
|
||||
// 3. Autenticado em rota pública com flag "redirect" → vai para raiz
|
||||
// 3 Autenticado em rota pública com flag "redirect" → vai para raiz
|
||||
if (authToken && publicRoute && publicRoute.whenAuthenticated === 'redirect') {
|
||||
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
|
||||
}
|
||||
|
||||
// 4. Autenticado em rota privada → valida token
|
||||
// 4 Autenticado em rota privada → valida token
|
||||
if (authToken && !publicRoute) {
|
||||
const decoded: any = jwt.decode(authToken.value);
|
||||
|
||||
// Token inválido → redireciona (uma vez)
|
||||
if (!decoded || !decoded.exp) {
|
||||
console.log('Token inválido');
|
||||
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
|
||||
}
|
||||
|
||||
// Verifica expiração
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
if (decoded.exp <= currentTime) {
|
||||
console.log('Token expirado');
|
||||
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
|
||||
}
|
||||
|
||||
// Token válido → segue
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export default function ClientTable({ data, pagination, onEdit, onDelete }: Clie
|
|||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{data.map((client) => (
|
||||
{Array.isArray(data) && data.length > 0 ? ( data.map((client) => (
|
||||
<TableRow key={client.client_id}>
|
||||
<TableCell className="font-medium">{client.client_id}</TableCell>
|
||||
<TableCell>
|
||||
|
|
@ -89,13 +89,20 @@ export default function ClientTable({ data, pagination, onEdit, onDelete }: Clie
|
|||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center text-muted-foreground">
|
||||
Nenhum cliente encontrado.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
{/* Se existir paginação, mostra o total de registros; senão, usa o tamanho da lista */}
|
||||
Total de clientes: {pagination?.total_records ?? data.length}
|
||||
Total de clientes: {pagination?.total_records ?? data?.length}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export default function UserForm({ isOpen, data, onClose, onSave, buttonIsLoadin
|
|||
{/* Campo: Cargo */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cargo"
|
||||
name="position"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cargo</FormLabel>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { AuthenticateUserInterface } from '@/shared/interfaces/AuthenticateUserI
|
|||
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
|
||||
|
||||
export default async function UserLoginData(form: any) {
|
||||
|
||||
const api = new API();
|
||||
|
||||
const response = await api.send({
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ export function useUserFormHook(data: FormValues | null) {
|
|||
useEffect(() => {
|
||||
if (data) {
|
||||
// Modo edição → carrega dados do usuário
|
||||
form.reset(data);
|
||||
form.reset({
|
||||
...data,
|
||||
password: '',
|
||||
});
|
||||
console.log('Form carregado com dados:', data);
|
||||
} else {
|
||||
// Modo criação → limpa o formulário
|
||||
|
|
@ -53,7 +56,7 @@ export function useUserFormHook(data: FormValues | null) {
|
|||
email: '',
|
||||
password: '',
|
||||
team: '',
|
||||
cargo: '',
|
||||
position: '',
|
||||
status: SituacoesEnum.ATIVO,
|
||||
user_id_create: null,
|
||||
user_id_update: null,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ export const UserSchema = z.object({
|
|||
user_id: z.number().optional(),
|
||||
name: z.string().min(3, { message: "O nome deve ter no mínimo 3 caracteres." }),
|
||||
email: z.email({ message: "Por favor, insira um email válido." }),
|
||||
password: z.string().min(6, { message: "A senha deve ter pelo menos 6 caracteres." }),
|
||||
cargo: z.string().nullable().optional(),
|
||||
password: z.string().min(6, { message: "A senha deve ter pelo menos 6 caracteres." }).optional().or(z.literal("")),
|
||||
position: z.string().nullable().optional(),
|
||||
team: z.string().min(1, { message: "A equipe é obrigatória." }),
|
||||
status: z.enum(SituacoesEnum), // 'A' ou 'I'
|
||||
date_register: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -23,25 +23,28 @@ export default async function UserLoginService(form: any) {
|
|||
console.log("service", response);
|
||||
|
||||
// Verifica se o usuário foi encontrado (caso contrário, retorna erro 404)
|
||||
if (response.data.user_id <= 0) {
|
||||
if (response?.data?.user_id <= 0) {
|
||||
return {
|
||||
code: 404, // Código de erro HTTP simulando "não encontrado"
|
||||
message: 'Não foi localizado o usuário', // Mensagem informativa ao cliente
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
// Obtém o manipulador de cookies do contexto do servidor
|
||||
const cookieStore = await cookies();
|
||||
|
||||
// Define um cookie com o token de autenticação
|
||||
cookieStore.set('access_token', response?.data?.token, {
|
||||
httpOnly: true, // Impede acesso via JavaScript (segurança contra XSS)
|
||||
secure: process.env.NODE_ENV === 'production', // Garante HTTPS em produção
|
||||
sameSite: 'strict', // Restringe envio de cookies entre domínios
|
||||
path: '/', // Torna o cookie acessível em toda a aplicação
|
||||
maxAge: 60 * 60 * 24, // Define validade de 24 horas (em segundos)
|
||||
});
|
||||
|
||||
// Redireciona o usuário autenticado para a página administrativa de usuários
|
||||
redirect('/administrativo/clientes');
|
||||
|
||||
}
|
||||
|
||||
// Obtém o manipulador de cookies do contexto do servidor
|
||||
const cookieStore = await cookies();
|
||||
|
||||
// Define um cookie com o token de autenticação
|
||||
cookieStore.set('access_token', response.data.token, {
|
||||
httpOnly: true, // Impede acesso via JavaScript (segurança contra XSS)
|
||||
secure: process.env.NODE_ENV === 'production', // Garante HTTPS em produção
|
||||
sameSite: 'strict', // Restringe envio de cookies entre domínios
|
||||
path: '/', // Torna o cookie acessível em toda a aplicação
|
||||
maxAge: 60 * 60 * 24, // Define validade de 24 horas (em segundos)
|
||||
});
|
||||
|
||||
// Redireciona o usuário autenticado para a página administrativa de usuários
|
||||
redirect('/administrativo/usuarios');
|
||||
}
|
||||
|
|
|
|||
36
src/shared/actions/auth/GetAuthenticatedUser.ts
Normal file
36
src/shared/actions/auth/GetAuthenticatedUser.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// src/shared/actions/auth/GetAuthenticatedUser.ts
|
||||
import { cookies } from 'next/headers';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import GetSigla from '@/shared/actions/text/GetSigla';
|
||||
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
data?: UserAuthenticatedInterface;
|
||||
}
|
||||
|
||||
export async function getUserFromServer(): Promise<JwtPayload | null> {
|
||||
try {
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
if (!token) return null;
|
||||
|
||||
const decoded: JwtPayload = jwtDecode(token);
|
||||
|
||||
if (decoded.data && typeof decoded.data === 'string') {
|
||||
decoded.data = JSON.parse(decoded.data);
|
||||
}
|
||||
|
||||
if (decoded.data) {
|
||||
decoded.data.sigla = GetSigla(decoded.data.nome || '');
|
||||
}
|
||||
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
console.error('Erro ao decodificar token no server:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,17 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import GetSigla from '@/shared/actions/text/GetSigla';
|
||||
import GUsuarioAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
|
||||
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
data?: GUsuarioAuthenticatedInterface;
|
||||
data?: UserAuthenticatedInterface;
|
||||
}
|
||||
|
||||
export default function useGUsuarioGetJWTHook() {
|
||||
export default function useUserGetJWTHook() {
|
||||
|
||||
const [userAuthenticated, setUserAuthenticated] = useState<JwtPayload | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -21,9 +22,11 @@ export default function useGUsuarioGetJWTHook() {
|
|||
// Lê o token diretamente do cookie (lado do cliente)
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('access_token='))
|
||||
.find(row => row.startsWith('access_token'))
|
||||
?.split('=')[1];
|
||||
|
||||
console.log('Token encontrado:', token);
|
||||
|
||||
// Se não houver token, apenas sai (sem erro nem redirecionamento)
|
||||
if (!token) {
|
||||
console.warn('Token ausente ou inválido no cookie.');
|
||||
35
src/shared/hooks/auth/useUserGetJWTHook.ts
Normal file
35
src/shared/hooks/auth/useUserGetJWTHook.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
data?: UserAuthenticatedInterface;
|
||||
}
|
||||
|
||||
export default function useUserGetJWTHook() {
|
||||
const [userAuthenticated, setUserAuthenticated] = useState<JwtPayload | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchUser() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me', { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('Erro ao buscar usuário');
|
||||
|
||||
const data: JwtPayload | null = await res.json();
|
||||
setUserAuthenticated(data);
|
||||
} catch (err) {
|
||||
console.error('Erro ao buscar usuário logado:', err);
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser();
|
||||
}, []);
|
||||
|
||||
return { userAuthenticated };
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue