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 exportadasmaster
parent
b6ba3b8593
commit
a69aca5dc8
|
|
@ -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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
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.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
@ -44,7 +45,7 @@ public class SecurityConfig {
|
||||||
)
|
)
|
||||||
.authenticationProvider(authenticationProvider())
|
.authenticationProvider(authenticationProvider())
|
||||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
.httpBasic(Customizer.withDefaults());
|
.httpBasic(AbstractHttpConfigurer::disable);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,4 +116,18 @@ api.interceptors.request.use(async (config) => {
|
||||||
return 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;
|
export default api;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { Login } from './pages/Login';
|
import { Login } from './pages/Login';
|
||||||
import { Dashboard } from './pages/Dashboard';
|
import { Dashboard } from './pages/Dashboard';
|
||||||
|
import { ProtectedRoute } from './routes/ProtectedRoute';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Dashboard />} />
|
<Route path="/" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Dashboard />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,8 @@ const Stat = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const Styles = {
|
const Styles = {
|
||||||
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
|
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",
|
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",
|
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
|
||||||
modalTitle: "text-lg font-semibold text-text",
|
modalTitle: "text-lg font-semibold text-text",
|
||||||
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
interface HeaderActionsProps {
|
export interface HeaderActionsProps {
|
||||||
isMenuOpen: boolean;
|
isMenuOpen: boolean;
|
||||||
onToggleMenu: () => void;
|
onToggleMenu: () => void;
|
||||||
onAddServer: () => void;
|
onAddServer: () => void;
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ export const ProfileModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const Styles = {
|
const Styles = {
|
||||||
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
|
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",
|
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",
|
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
|
||||||
modalTitle: "text-lg font-semibold text-text",
|
modalTitle: "text-lg font-semibold text-text",
|
||||||
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,8 @@ export const ServerModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const Styles = {
|
const Styles = {
|
||||||
modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4",
|
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",
|
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",
|
modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
|
||||||
modalTitle: "text-lg font-semibold text-text",
|
modalTitle: "text-lg font-semibold text-text",
|
||||||
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
|
||||||
|
|
|
||||||
|
|
@ -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}</>;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue