feat(auth): proteger dashboard e melhorar UX

- Remove HTTP Basic e trata 401 redirecionando para /login
- Adiciona ProtectedRoute garantindo acesso ao dashboard apenas autenticado
- Refina modais e menu com bulk upload e tipagens exportadas
master
Artur Oliveira 2025-12-16 14:26:18 -03:00
parent b6ba3b8593
commit a69aca5dc8
8 changed files with 47 additions and 9 deletions

View File

@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.authentication.configurati
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -44,7 +45,7 @@ public class SecurityConfig {
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.httpBasic(Customizer.withDefaults());
.httpBasic(AbstractHttpConfigurer::disable);
return http.build();
}

View File

@ -116,4 +116,18 @@ api.interceptors.request.use(async (config) => {
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error?.response?.status === 401) {
setAuthToken(undefined);
const currentPath = window.location.pathname;
if (currentPath !== "/login") {
window.location.href = "/login";
}
}
return Promise.reject(error);
}
);
export default api;

View File

@ -2,13 +2,18 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import { Login } from './pages/Login';
import { Dashboard } from './pages/Dashboard';
import { ProtectedRoute } from './routes/ProtectedRoute';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/login" element={<Login />} />
</Routes>
</BrowserRouter>

View File

@ -134,8 +134,8 @@ const Stat = ({
);
const Styles = {
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl",
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4 !mt-0",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transform transition-all duration-200 animate-fade-up",
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
modalTitle: "text-lg font-semibold text-text",
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",

View File

@ -1,4 +1,4 @@
interface HeaderActionsProps {
export interface HeaderActionsProps {
isMenuOpen: boolean;
onToggleMenu: () => void;
onAddServer: () => void;

View File

@ -73,8 +73,8 @@ export const ProfileModal = ({
};
const Styles = {
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl",
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4 !mt-0",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transform transition-all duration-200 animate-fade-up",
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
modalTitle: "text-lg font-semibold text-text",
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",

View File

@ -100,8 +100,8 @@ export const ServerModal = ({
};
const Styles = {
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl",
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4 !mt-0",
modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transform transition-all duration-200 animate-fade-up",
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
modalTitle: "text-lg font-semibold text-text",
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",

View File

@ -0,0 +1,18 @@
import type { ReactNode } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { getAccessToken } from "../Api";
interface ProtectedRouteProps {
children: ReactNode;
}
export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const token = getAccessToken();
const location = useLocation();
if (!token) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
};