diff --git a/backend/src/main/java/com/hitcommunications/servermanager/config/security/JwtService.java b/backend/src/main/java/com/hitcommunications/servermanager/config/security/JwtService.java index 299ffef..5b98e0a 100644 --- a/backend/src/main/java/com/hitcommunications/servermanager/config/security/JwtService.java +++ b/backend/src/main/java/com/hitcommunications/servermanager/config/security/JwtService.java @@ -72,6 +72,7 @@ public class JwtService { } private Claims extractAllClaims(String token) { + // parser() is deprecated in docs, but current JJWT version exposes only this entrypoint return Jwts.parser() .verifyWith(getSignInKey()) .build() diff --git a/backend/src/main/java/com/hitcommunications/servermanager/config/security/SecurityConfig.java b/backend/src/main/java/com/hitcommunications/servermanager/config/security/SecurityConfig.java index 468f5e2..6051a32 100644 --- a/backend/src/main/java/com/hitcommunications/servermanager/config/security/SecurityConfig.java +++ b/backend/src/main/java/com/hitcommunications/servermanager/config/security/SecurityConfig.java @@ -17,6 +17,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Configuration @EnableWebSecurity @@ -30,6 +35,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + .cors(Customizer.withDefaults()) .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth @@ -59,4 +65,21 @@ public class SecurityConfig { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of( + "http://localhost:5173", + "http://127.0.0.1:5173" + )); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + configuration.setExposedHeaders(List.of("Set-Cookie")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } } diff --git a/frontned/.env.example b/frontned/.env.example new file mode 100644 index 0000000..b24fd8a --- /dev/null +++ b/frontned/.env.example @@ -0,0 +1,3 @@ +# Example env file for Vite +# Copy to .env and set your backend URL +VITE_BACKEND_URL=http://localhost:8080 diff --git a/frontned/src/Api.ts b/frontned/src/Api.ts new file mode 100644 index 0000000..856869c --- /dev/null +++ b/frontned/src/Api.ts @@ -0,0 +1,21 @@ +import axios from 'axios' + +const baseURL = (import.meta.env.VITE_BACKEND_URL as string) || 'http://localhost:8080'; + +const api = axios.create({ + baseURL, + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, +}); + +export const setAuthToken = (token?: string) => { + if (token) { + api.defaults.headers.common['Authorization'] = `Bearer ${token}`; + } else { + delete api.defaults.headers.common['Authorization']; + } +}; + +export default api; diff --git a/frontned/src/App.tsx b/frontned/src/App.tsx index 177b1b2..5ce1eab 100644 --- a/frontned/src/App.tsx +++ b/frontned/src/App.tsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import './App.css'; import { Login } from './pages/Login'; @@ -7,6 +7,7 @@ function App() { return ( + } /> } /> diff --git a/frontned/src/main.tsx b/frontned/src/main.tsx index 7cfc26c..59d5805 100644 --- a/frontned/src/main.tsx +++ b/frontned/src/main.tsx @@ -1,9 +1,13 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; +import { Toaster } from 'react-hot-toast'; createRoot(document.getElementById('root')!).render( - + <> + + + > , ); diff --git a/frontned/src/pages/Login.tsx b/frontned/src/pages/Login.tsx index ab557fb..f7419ab 100644 --- a/frontned/src/pages/Login.tsx +++ b/frontned/src/pages/Login.tsx @@ -1,7 +1,10 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Layout } from "../components/Layout"; import type { LoginProps } from "../types/Login"; import { Eye, EyeOff } from "lucide-icons-react"; +import api from "../Api"; +import { useNavigate } from "react-router-dom"; +import toast from "react-hot-toast"; export const Login = () => { const [form, setForm] = useState({ @@ -9,6 +12,9 @@ export const Login = () => { password: "" }); const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const navigate = useNavigate(); const handleChange = (e: React.ChangeEvent) => { setForm({ @@ -21,15 +27,32 @@ export const Login = () => { setShowPassword(!showPassword); }; - useEffect(() => { - console.log(form); - }, [form]); + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setLoading(true); + + try { + await api.post("/api/auth/login", { + username: form.email, + password: form.password + }); + navigate("/"); + toast.success("Login realizado com sucesso!"); + } catch (err: any) { + const message = err?.response?.data?.message || "Falha ao autenticar. Verifique as credenciais."; + setError(message); + toast.error(message); + } finally { + setLoading(false); + } + }; return ( - + Email: { {showPassword ? : } - Login + {error && {error}} + + {loading ? "Autenticando..." : "Login"} + @@ -77,4 +103,4 @@ const Styles = { input: "bg-gray-50 border border-cardBorder text-text text-md rounded-lg outline-none block w-full p-2.5", iconButton: "absolute right-3 top-1/2 text-text p-1 focus:outline-none", button: "w-full bg-accent p-2 rounded-md mt-4 text-white font-bold text-lg hover:bg-hover transition duration-150", -}; \ No newline at end of file +};
{error}