From d4e0922077f769cf71447b47a84a4275e336a1c2 Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Tue, 27 Aug 2024 16:13:41 -0600 Subject: [PATCH 1/3] Modificacion de api y endpoint --- cosiap_frontend/src/api.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cosiap_frontend/src/api.js b/cosiap_frontend/src/api.js index 26b0c72..9a2a5ed 100644 --- a/cosiap_frontend/src/api.js +++ b/cosiap_frontend/src/api.js @@ -1,4 +1,5 @@ import axios from "axios"; + axios.defaults.withCredentials = true; const apiUrl = "http://localhost:8000"; @@ -6,7 +7,7 @@ const apiUrl = "http://localhost:8000"; // Configuración de Axios const ax = axios.create({ - baseURL: apiUrl, + baseURL: apiUrl, }); // Interceptor de solicitud @@ -64,6 +65,11 @@ const api = { token: { login: (data) => ax.post('api/usuarios/token/',data), refresh: () => ax.post('api/usuarios/token/refresh/'), + logout: () => ax.get('api/usuarios/logout/') + }, + + admin: { + is_admin: () => ax.get('api/usuarios/user-is-admin/') }, //Endpoints del submodulo solicitantes -- GitLab From af1797599b087fe90285ad2ed8ffa477d6d46023 Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Tue, 27 Aug 2024 16:18:00 -0600 Subject: [PATCH 2/3] =?UTF-8?q?Modificaci=C3=B3n=20de=20layouts=20de=20nav?= =?UTF-8?q?egaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/layouts/LayoutBaseNavigation.jsx | 17 ++++++++++++----- .../LayoutsNavigation/Navbar/MobileMenu.jsx | 6 +++++- .../layouts/LayoutsNavigation/Navbar/Navbar.jsx | 2 +- .../LayoutsNavigation/Navbar/NavbarLinks.jsx | 6 ++++-- .../LayoutsNavigation/NotificationToggle.jsx | 2 +- .../LayoutsNavigation/Sidebar/MobileMenu.jsx | 6 +++++- .../LayoutsNavigation/Sidebar/Notifications.jsx | 2 +- .../LayoutsNavigation/Sidebar/Sidebar.jsx | 8 ++++++-- 8 files changed, 35 insertions(+), 14 deletions(-) diff --git a/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx b/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx index 1c1cae1..da255dd 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx @@ -5,7 +5,6 @@ import Sidebar from "@/components/common/layouts/LayoutsNavigation/Sidebar/Sideb import SettingsToggle from "@/components/common/layouts/LayoutsNavigation/SettingsToggle"; import Settings from "@/components/common/layouts/LayoutsNavigation/Settings"; - const linksItemsSolicitante = [ { nameIcon: 'library_books', @@ -47,7 +46,7 @@ const linksItemsSolicitante = [ export default function LayoutBaseNavigation() { const [viewMenu, setViewMenu] = useState(false); - const [selectedNav, setSelectedNav] = useState("vertical"); + const [selectedNav, setSelectedNav] = useState('vertical'); const [viewSettings, setViewSettings] = useState(false); const [links, setLinks] = useState([...linksItemsSolicitante]); @@ -61,20 +60,28 @@ export default function LayoutBaseNavigation() { setSectionURL(location.pathname.split('/').filter(Boolean).pop()); }, [location]); + useEffect(() => { + const savedNavStyle = localStorage.getItem('selectedNav'); + if (savedNavStyle) { + setSelectedNav(savedNavStyle); + } + }, []); + + useEffect(() => { + localStorage.setItem('selectedNav', selectedNav); + }, [selectedNav]); + useEffect(() => { const updatedLinks = links.map(link => { - // Verificación directa basada en 'nameItem' y 'navigate' const isSelected = link.nameItem.toLowerCase() === sectionURL.toLowerCase() || link.navigate?.split('/').pop().toLowerCase() === sectionURL.toLowerCase(); - // Actualizar submenú si existe const updatedSubMenu = link.subMenu?.map(sub => ({ ...sub, isSelected: sub.nameItem.toLowerCase() === sectionURL.toLowerCase() || sub.navigate?.split('/').pop().toLowerCase() === sectionURL.toLowerCase() })); - // Marcar padre si algún subelemento está seleccionado const isParentSelected = updatedSubMenu?.some(sub => sub.isSelected); return { diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/MobileMenu.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/MobileMenu.jsx index c0f4107..78089a0 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/MobileMenu.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/MobileMenu.jsx @@ -1,7 +1,9 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import { useAutenticacion } from '@/components/common/utility/Autenticador'; export default function MobileMenu({ menuRef, linksItems }) { + const {cerrarSesion} = useAutenticacion(); const navigate = useNavigate(); // useState para manejar la visibilidad del submenú de "Ayuda" @@ -60,7 +62,9 @@ export default function MobileMenu({ menuRef, linksItems }) { ) : '' // Si no hay linksItems, no se muestra nada } {/* Enlace de cerrar sesión */} - + logout {/* Icono del ítem */} diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/Navbar.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/Navbar.jsx index 7af25c2..7350992 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/Navbar.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Navbar/Navbar.jsx @@ -72,7 +72,7 @@ export default function Navbar({ linksItems, viewMenu, setViewMenu }) { {/* Contenedor para los toggles del menú móvil y notificaciones en pantallas pequeñas */} -
+
{/* Toggle de notificaciones para móviles */} logout diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/NotificationToggle.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/NotificationToggle.jsx index 33a002e..29acd07 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/NotificationToggle.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/NotificationToggle.jsx @@ -5,7 +5,7 @@ export default function NotificationToggle( {buttonNotificationsRef, viewNotific {viewNotifications ? 'notifications_active' : 'notifications'} - + diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/MobileMenu.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/MobileMenu.jsx index 15e7b69..426cc85 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/MobileMenu.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/MobileMenu.jsx @@ -1,8 +1,10 @@ import { useState, useEffect, useRef } from "react"; import SidebarLinks from "@/components/common/layouts/LayoutsNavigation/Sidebar/SidebarLinks"; import SidebarBrand from "@/components/common/layouts/LayoutsNavigation/Sidebar/SidebarBrand"; +import { useAutenticacion } from '@/components/common/utility/Autenticador'; export default function MobileMenu({ menuRef, linksItems }) { + const {cerrarSesion} = useAutenticacion(); const [viewSubMenuHelp, setViewSubMenuHelp] = useState(false); const firstContainerRef = useRef(null); @@ -59,7 +61,9 @@ export default function MobileMenu({ menuRef, linksItems }) {
{/* Enlace de cerrar sesión */} - + logout {/* Icono del ítem */} diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Notifications.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Notifications.jsx index a800e00..bbf4ba4 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Notifications.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Notifications.jsx @@ -4,7 +4,7 @@ export default function Notifications( {notificationsRef} ){ return ( <> -
+
diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Sidebar.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Sidebar.jsx index e4fb6de..13056d9 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Sidebar.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Sidebar/Sidebar.jsx @@ -6,8 +6,10 @@ import SidebarLinks from "@/components/common/layouts/LayoutsNavigation/Sidebar/ import SidebarBrand from "@/components/common/layouts/LayoutsNavigation/Sidebar/SidebarBrand"; import Notifications from "@/components/common/layouts/LayoutsNavigation/Sidebar/Notifications"; import MobileMenu from "@/components/common/layouts/LayoutsNavigation/Sidebar/MobileMenu"; +import { useAutenticacion } from '@/components/common/utility/Autenticador'; export default function Sidebar( {linksItems, viewMenu, setViewMenu} ){ + const {cerrarSesion} = useAutenticacion(); const navigate = useNavigate(); // Estado para controlar la visibilidad de las notificaciones const [viewNotifications, setViewNotifications] = useState(false); @@ -79,7 +81,7 @@ export default function Sidebar( {linksItems, viewMenu, setViewMenu} ){ setViewMenu={setViewMenu} />
-
+
{/* Enlace de cerrar sesión */} - + logout {/* Icono del ítem */} -- GitLab From 2c616f49f9cdd4c7c8d835a66205edf4137c705b Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Tue, 27 Aug 2024 17:39:30 -0600 Subject: [PATCH 3/3] Autenticador completado, vista de paginas que no existen, de paginas solamente accedidas por administradores --- cosiap_frontend/src/App.jsx | 89 +++++++++++++------ .../common/utility/Autenticador.jsx | 51 ++++++++--- .../common/utility/LoginRequiredRoute.jsx | 55 ++++++++++++ .../common/utility/LoginRequiredRoutes.jsx | 16 ---- .../src/components/users/Login/LoginForm.jsx | 7 +- .../src/components/users/NoAutorizado.jsx | 29 ++++++ .../src/components/users/PageNotFound.jsx | 29 ++++++ 7 files changed, 220 insertions(+), 56 deletions(-) create mode 100644 cosiap_frontend/src/components/common/utility/LoginRequiredRoute.jsx delete mode 100644 cosiap_frontend/src/components/common/utility/LoginRequiredRoutes.jsx create mode 100644 cosiap_frontend/src/components/users/NoAutorizado.jsx create mode 100644 cosiap_frontend/src/components/users/PageNotFound.jsx diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index c35512a..9c0fc17 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -1,15 +1,26 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import {PaginaHead} from "@/components/common/utility/PaginaHead"; import {Autenticador} from "@/components/common/utility/Autenticador"; -import {LoginRequiredRoutes} from "@/components/common/utility/LoginRequiredRoutes" +import { useAutenticacion } from '@/components/common/utility/Autenticador'; + +import {LoginRequiredRoute, IsAdminRequiredRoute, IsLogged} from "@/components/common/utility/LoginRequiredRoute" import "./App.css"; import PageLoader from '@/components/common/ui/PageLoader'; -import { Login } from './components/users/Login/Login'; -import Register from './components/users/Register/Register'; -import ResetPassword from './components/users/Password/ResetPassword'; -import Inicio from "./components/Inicio"; + +// Importaciones de componentes de autenticacion +import { Login } from '@/components/users/Login/Login'; +import Register from '@/components/users/Register/Register'; +import ResetPassword from '@/components/users/Password/ResetPassword'; +import NoAutorizado from '@/components/users/NoAutorizado'; +import PageNotFound from '@/components/users/PageNotFound'; + +import Inicio from "@/components/Inicio"; import { useState } from 'react'; -import LayoutBaseAuthenticator from './components/common/layouts/LayoutBaseAuthenticator'; +// Importaciones de layoutsBase +import LayoutBaseAuthenticator from '@/components/common/layouts/LayoutBaseAuthenticator'; +import LayoutBaseNavigation from '@/components/common/layouts/LayoutBaseNavigation'; + + function App() { @@ -27,28 +38,56 @@ function App() { viewPageLoader && } - - - {/* Rutas públicas */} - - {/* Componente del layout */} - }> - {/* Componentes hijos del layout autenticador */} - } /> - } /> - } /> - } /> - - - {/* Rutas protegidas */} - }> - } /> - - + + ); } -export default App; +function RoutesApp( {setViewPageLoader} ){ + const {token} = useAutenticacion(); + + return ( + + } /> + {/* Rutas públicas */} + {/* Componente del layout */} + }> + }> + {/* Componentes hijos del layout autenticador */} + } /> + } /> + } /> + + + {/* Rutas protegidas */} + }> + {/* Componentes del layout de navegación */} + }> + {/* Componentes hijos del layout autenticador */} + + {/* Cualquier usuario puede acceder a estas url */} + } /> + } /> + } /> + } /> + } /> + } /> + {/* Solo administradores pueden acceder a estas url */} + }> + } /> + } /> + + + + } /> + + } /> + + + ); +} + +export default App; \ No newline at end of file diff --git a/cosiap_frontend/src/components/common/utility/Autenticador.jsx b/cosiap_frontend/src/components/common/utility/Autenticador.jsx index dcb8654..0a9d8e1 100644 --- a/cosiap_frontend/src/components/common/utility/Autenticador.jsx +++ b/cosiap_frontend/src/components/common/utility/Autenticador.jsx @@ -21,35 +21,35 @@ export const useAutenticacion = () => { return contextoAut; }; -export const Autenticador = ({ children }) => { +export const Autenticador = ({ setViewPageLoader, children }) => { const [token, setToken] = useState(); + const [isAdmin, setIsAdmin] = useState(false); useEffect(() => { const buscarUsuario = async () => { - try { + try { const response = await api.usuarios.token.refresh(); setToken(response.data.access); + // IMPORTANTE: Primero se deben configurar los interceptores antes que determinar si es admin + configurarInterceptors(response.data.access); + const responseAd = await api.usuarios.admin.is_admin(); + setIsAdmin(responseAd.data.user_is_admin); + console.log(isAdmin) } catch { setToken(null); + setIsAdmin(false); } } buscarUsuario(); }, []); - useLayoutEffect(() => { + const configurarInterceptors = (token) => { const interceptadorAccess = api.axios.interceptors.request.use((config) => { config.headers.Authorization = !config.__retry && token ? `Bearer ${token}` : config.headers.Authorization; return config; }); - return () => { - api.axios.interceptors.request.eject(interceptadorAccess); - }; - - }, [token]); - - useLayoutEffect(() => { const interceptadorRefresh = api.axios.interceptors.response.use( (response) => response, async (error) => { const requestOriginal = error.config; @@ -60,10 +60,13 @@ export const Autenticador = ({ children }) => { setToken(response.data.access); requestOriginal.headers.Authorization = `Bearer ${response.data.access}`; requestOriginal.__retry = true; + const responseAd = await api.usuarios.admin.is_admin(); + setIsAdmin(responseAd.data.user_is_admin); return api(requestOriginal); } catch { setToken(null); + setIsAdmin(false); } } @@ -71,12 +74,34 @@ export const Autenticador = ({ children }) => { }, ); + // Eject interceptors on component unmount return () => { + api.axios.interceptors.request.eject(interceptadorAccess); api.axios.interceptors.response.eject(interceptadorRefresh); }; - }); + }; + + useLayoutEffect(() => { + if (token) { + const ejectInterceptors = configurarInterceptors(token); + return () => ejectInterceptors(); + } + }, [token]); + + const cerrarSesion = async () => { + setViewPageLoader(true); + try { + const response = await api.usuarios.token.logout(); + console.log('Logout succesful ', response); + setToken(null); + } catch (error) { + console.error('Logout unsuccesful ', error); + } + setViewPageLoader(false); + }; + return ( - + {children} ); @@ -84,4 +109,4 @@ export const Autenticador = ({ children }) => { Autenticador.propTypes = { children: PropTypes.node, -}; \ No newline at end of file +}; diff --git a/cosiap_frontend/src/components/common/utility/LoginRequiredRoute.jsx b/cosiap_frontend/src/components/common/utility/LoginRequiredRoute.jsx new file mode 100644 index 0000000..33607d6 --- /dev/null +++ b/cosiap_frontend/src/components/common/utility/LoginRequiredRoute.jsx @@ -0,0 +1,55 @@ +import { useAutenticacion } from "@/components/common/utility/Autenticador"; +import { useState, useEffect } from "react"; +import { Navigate, Outlet } from "react-router-dom"; +import api from "@/api"; + +export const LoginRequiredRoute = () => { + const { token, isAdmin } = useAutenticacion(); + console.log(isAdmin) + if (token === null) { + return ; + } else if (token === undefined) { + return null; + }else { + return ; + } +}; + +export const IsAdminRequiredRoute = ({ setViewPageLoader }) => { + const [isAdmin, setIsAdmin] = useState(false); + const [hasCheckedAdmin, setHasCheckedAdmin] = useState(false); // Nuevo estado para verificar si ya se hizo la comprobación + + useEffect(() => { + const verificarAdmin = async () => { + setViewPageLoader(true); // Muestra el loader + try { + const response = await api.usuarios.admin.is_admin(); + setIsAdmin(response.data.user_is_admin); + } catch (error) { + console.error('Determinación de permisos admin ha fallado', error); + setIsAdmin(false); + } finally { + setHasCheckedAdmin(true); // Indica que la verificación ha terminado + setViewPageLoader(false); // Oculta el loader después de recibir la respuesta + } + }; + + verificarAdmin(); + }, [setViewPageLoader]); + + if (!hasCheckedAdmin) { + // Mientras no se haya verificado si es admin o no, retorna null + return null; + } + + return isAdmin ? : ; +}; + +export const IsLogged = () => { + const { token } = useAutenticacion(); + if (token === null) { + return + }else{ + return ; + } +}; diff --git a/cosiap_frontend/src/components/common/utility/LoginRequiredRoutes.jsx b/cosiap_frontend/src/components/common/utility/LoginRequiredRoutes.jsx deleted file mode 100644 index 784e8ac..0000000 --- a/cosiap_frontend/src/components/common/utility/LoginRequiredRoutes.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useAutenticacion } from "@/components/common/utility/Autenticador"; -import { Navigate, Outlet } from "react-router-dom"; - -export const LoginRequiredRoutes = () => { - const { token } = useAutenticacion(); - - if (token === null) { - return ; - } else if (token === undefined) { - return null; // O un spinner de carga o marcador de posición - } else { - return ; - } -}; - -export default LoginRequiredRoutes; \ No newline at end of file diff --git a/cosiap_frontend/src/components/users/Login/LoginForm.jsx b/cosiap_frontend/src/components/users/Login/LoginForm.jsx index b5b820c..57d0a42 100644 --- a/cosiap_frontend/src/components/users/Login/LoginForm.jsx +++ b/cosiap_frontend/src/components/users/Login/LoginForm.jsx @@ -16,7 +16,7 @@ import {useAutenticacion} from "@/components/common/utility/Autenticador" export function LoginForm( {setViewPageLoader} ) { const [loginError, setLoginError] = useState('') const navigate = useNavigate(); - const { setToken } = useAutenticacion(); + const { setToken, setIsAdmin, isAdmin } = useAutenticacion(); const { register, handleSubmit, @@ -30,7 +30,10 @@ export function LoginForm( {setViewPageLoader} ) { try { const response = await api.usuarios.token.login(data); console.log("Login successful:", response.data); - setToken(response.data.access) + setToken(response.data.access) + //Establecemos si es admin o no + const responseAd = await api.usuarios.admin.is_admin(); + setIsAdmin(responseAd.data.user_is_admin); navigate("/inicio"); } catch (error) { console.error("Login failed:", error); diff --git a/cosiap_frontend/src/components/users/NoAutorizado.jsx b/cosiap_frontend/src/components/users/NoAutorizado.jsx new file mode 100644 index 0000000..cda2032 --- /dev/null +++ b/cosiap_frontend/src/components/users/NoAutorizado.jsx @@ -0,0 +1,29 @@ +import { useNavigate } from "react-router-dom"; +export default function NoAutorizado() { + const navigate = useNavigate(); + + return ( + + ); +} diff --git a/cosiap_frontend/src/components/users/PageNotFound.jsx b/cosiap_frontend/src/components/users/PageNotFound.jsx new file mode 100644 index 0000000..3c0ce5d --- /dev/null +++ b/cosiap_frontend/src/components/users/PageNotFound.jsx @@ -0,0 +1,29 @@ +import { useNavigate } from "react-router-dom"; +export default function NoAutorizado() { + const navigate = useNavigate(); + + return ( +
+
+
+ Pagina no encontrada +
+
+ La pagina solicitada no existe +
+ +
+
+ ); +} -- GitLab