diff --git a/growbros-frontend/package-lock.json b/growbros-frontend/package-lock.json index b9c0190f79675bd3a584482877d7163aa48e5e56..b85ec7a34722b10b8daa36b9831e2f9d4f5cff75 100644 --- a/growbros-frontend/package-lock.json +++ b/growbros-frontend/package-lock.json @@ -9,15 +9,18 @@ "version": "0.0.0", "dependencies": { "font-awesome": "^4.7.0", + "jwt-decode": "^4.0.0", "rc-slider": "^10.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0", "zod": "^3.22.4" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", @@ -774,6 +777,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", @@ -1430,6 +1442,11 @@ "node": ">=0.10.0" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1785,6 +1802,14 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1915,6 +1940,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2074,6 +2107,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2161,6 +2209,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-router": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", @@ -2498,6 +2569,14 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/growbros-frontend/package.json b/growbros-frontend/package.json index 7ca6c4475f64bf1dd3840f71542f0f217fa4ec98..a45121749b4db8e5c22a113fc4947254ca9de468 100644 --- a/growbros-frontend/package.json +++ b/growbros-frontend/package.json @@ -11,15 +11,18 @@ }, "dependencies": { "font-awesome": "^4.7.0", + "jwt-decode": "^4.0.0", "rc-slider": "^10.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0", "zod": "^3.22.4" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", diff --git a/growbros-frontend/src/App.tsx b/growbros-frontend/src/App.tsx index fe6ec4f489cf36a18e980bcf08593d1c7b44a187..df485f3c79cb1abe9dbec8d8d5ed40c2235ea493 100644 --- a/growbros-frontend/src/App.tsx +++ b/growbros-frontend/src/App.tsx @@ -6,18 +6,34 @@ import Navbar from "./components/Navbar"; import {Route, Routes} from "react-router-dom"; import Wunschliste from "./pages/Wunschliste"; import PlantDetails from "./components/PlantDetails"; +import NavbarRegistrationAndLogin from "./components/NavbarRegistrationAndLogin.tsx"; +import {checkJwtStatus, deleteJwtCookie} from "./jwt/Cookies.ts"; +import {useState} from "react"; +import Register from "./pages/Register.tsx"; +import Login from "./pages/Login.tsx"; function App() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + + checkJwtStatus(setIsLoggedIn); + + const handleLogout = async () => { + deleteJwtCookie(); + setIsLoggedIn(false); + }; + return ( <div className="App"> - <Navbar/> - <Routes> + {isLoggedIn ? <Navbar onLogout={handleLogout} /> : <NavbarRegistrationAndLogin />} <Routes> <Route path="/" element={<Home/>}/> <Route path="/garten" element={<Garten/>}/> <Route path="/wunschliste" element={<Wunschliste/>}/> <Route path="/suchen" element={<Suche/>}/> <Route path="/pflanze/:plantId" element={<PlantDetails/>}/> - </Routes> + <Route path="/register" element={<Register />} /> + <Route path="/login" element={<Login />} /> + + </Routes> </div> ); } diff --git a/growbros-frontend/src/pages/Register.tsx b/growbros-frontend/src/pages/Register.tsx index cfb54045f8d85871541ef52f86fbf20a9008c872..01d55084e0ef11a59babc9778838493c43a71eed 100644 --- a/growbros-frontend/src/pages/Register.tsx +++ b/growbros-frontend/src/pages/Register.tsx @@ -3,7 +3,6 @@ import {FormEvent, useState} from "react"; import {setCookie} from "../jwt/Cookies.ts"; import { useNavigate } from 'react-router-dom'; import { jwtDecode } from 'jwt-decode'; -import Modal from 'react-modal'; function Register() { @@ -14,9 +13,6 @@ function Register() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - const isValidEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); @@ -30,38 +26,32 @@ function Register() { return password.length >= 4; // Prüfe, ob das Passwort mindestens 4 Zeichen lang ist }; - const openErrorModal = (message: string) => { - setErrorMessage(message); - setErrorModalIsOpen(true); - }; - - const closeErrorModal = () => { - setErrorModalIsOpen(false); - setErrorMessage(''); - }; - const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); //verhindert, dass die Seite neu geladen wird, wenn das Formular abgeschickt wird if (!isValidEmail(email)) { - openErrorModal('Ungültige E-Mail-Adresse. Frontend'); + window.alert('Ungültige E-Mail-Adresse.'); return; } - if (!isValidName(firstname) || !isValidName(lastname)) { - openErrorModal('Vorname und Nachname dürfen nicht leer sein. Frontend'); + if (!isValidName(firstname)) { + window.alert('Vorname darf nicht leer sein.'); + return; + } + + if (!isValidName(lastname)) { + window.alert('Nachname darf nicht leer sein.'); return; } if (!isValidPassword(password)) { - openErrorModal('Das Passwort muss mindestens 4 Zeichen lang sein. Frontend'); + window.alert('Das Passwort muss mindestens 4 Zeichen lang sein.'); return; } await handleRegister(); }; const handleRegister = async () => { - console.log("handle register") try { const requestBody = { firstname: firstname, @@ -78,7 +68,8 @@ function Register() { body: JSON.stringify(requestBody) } ) - if (res.ok) { + console.log(res); + if (res.status === 200) { const {token} = await res.json(); console.log("Jwt Token not decoded" + token); const decodedToken = jwtDecode(token); @@ -88,21 +79,20 @@ function Register() { console.log("Jwt decoded " + decodedToken); navigate('/'); } else { - const errorBody = await res.json(); - if (res.status === 400 && errorBody.error === 'InvalidEmailException') { - window.alert("Fehler bei der Anmeldung: Ungültige E-Mail-Adresse."); - console.error('Fehler bei der Anmeldung: Ungültige E-Mail-Adresse.'); - } else if (res.status === 409 && errorBody.error === 'DataIntegrityViolationException') { - window.alert("Fehler bei der Anmeldung: E-Mail-Adresse bereits vorhanden."); - console.error('Fehler bei der Anmeldung: E-Mail-Adresse bereits vorhanden.'); - - } else { - window.alert(`Fehler bei der Anmeldung: ${res.status} - ${res.statusText}`); - console.error('Fehler bei der Anmeldung:', res.status, res.statusText); + // @ts-ignore + const reader = res.body.getReader(); + const { value, done } = await reader.read(); + + if (!done) { + const text = new TextDecoder().decode(value); + window.alert(text) + console.error(text); } } - } catch (e) { + + } catch (e: any) { + window.alert(e.status); } }; @@ -148,17 +138,6 @@ function Register() { </div> </form> </div> - <Modal - isOpen={errorModalIsOpen} - onRequestClose={closeErrorModal} - contentLabel="Fehlermeldung" - className="errorField" - > - <div className="error-message"> - <p>{errorMessage}</p> - <button onClick={closeErrorModal}>Schließen</button> - </div> - </Modal> </main> ); } diff --git a/src/main/java/hdm/mi/growbros/auth/AuthenticationResponse.java b/src/main/java/hdm/mi/growbros/auth/AuthenticationResponse.java index a16fe46d690544679546018623b87b33ebf1b837..e0f15556c84bdd62ffca43047038c289f3099c09 100644 --- a/src/main/java/hdm/mi/growbros/auth/AuthenticationResponse.java +++ b/src/main/java/hdm/mi/growbros/auth/AuthenticationResponse.java @@ -11,4 +11,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class AuthenticationResponse { private String token; + private String error; + private String message; } diff --git a/src/main/java/hdm/mi/growbros/auth/AuthenticationService.java b/src/main/java/hdm/mi/growbros/auth/AuthenticationService.java index 4f46ea08a0f7e0cd314cadd9ef9bdf0106d4f040..1762e32a51a94ce1f028ee9a385c9fd04c85a51c 100644 --- a/src/main/java/hdm/mi/growbros/auth/AuthenticationService.java +++ b/src/main/java/hdm/mi/growbros/auth/AuthenticationService.java @@ -1,5 +1,6 @@ package hdm.mi.growbros.auth; +import hdm.mi.growbros.exceptions.EmailAlreadyExistsException; import hdm.mi.growbros.exceptions.InvalidDataException; import hdm.mi.growbros.exceptions.InvalidEmailException; import hdm.mi.growbros.models.user.Role; @@ -7,6 +8,8 @@ import hdm.mi.growbros.models.user.User; import hdm.mi.growbros.repositories.UserRepository; import hdm.mi.growbros.security.JwtService; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetailsService; @@ -32,29 +35,32 @@ public class AuthenticationService { public AuthenticationResponse register(RegisterRequest request) { if (!isValidEmail(request.getEmail())) { - throw new InvalidEmailException("Ungültige E-Mail-Adresse. Backend"); + throw new InvalidEmailException(HttpStatus.BAD_REQUEST, "Ungültige E-Mail-Adresse. Backend"); } - if (request.getFirstname().length() < 4 ) { - throw new InvalidDataException("Vorname muss mindestens 4 Zeichen lang sein. Backend"); + if (request.getFirstname().isEmpty()) { + throw new InvalidDataException(HttpStatus.BAD_REQUEST, "Vorname fehlt. Backend"); } - if (request.getLastname().length() < 4) { - throw new InvalidDataException("Nachname muss mindestens 4 Zeichen lang sein. Backend"); + if (request.getLastname().isEmpty()) { + throw new InvalidDataException(HttpStatus.BAD_REQUEST, "Nachname fehlt. Backend"); + } + try { + var user = User.builder() + .firstname(request.getFirstname()) + .lastname(request.getLastname()) + .email(request.getEmail()) + .password(passwordEncoder.encode(request.getPassword())) + .role(Role.USER) + .build(); + repository.save(user); + var jwtToken = jwtService.generateToken(user); + return AuthenticationResponse.builder() + .token(jwtToken) + .build(); + } catch (DataIntegrityViolationException e) { + throw new EmailAlreadyExistsException(HttpStatus.BAD_REQUEST, "Email ist bereits registriert."); } - - var user = User.builder() - .firstname(request.getFirstname()) - .lastname(request.getLastname()) - .email(request.getEmail()) - .password(passwordEncoder.encode(request.getPassword())) - .role(Role.USER) - .build(); - repository.save(user); - var jwtToken = jwtService.generateToken(user); - return AuthenticationResponse.builder() - .token(jwtToken) - .build(); } public AuthenticationResponse authenticate(AuthenticationRequest request) { @@ -73,6 +79,9 @@ public class AuthenticationService { .build(); } + + + private boolean isValidEmail(String email) { String emailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$"; Pattern pattern = Pattern.compile(emailRegex); diff --git a/src/main/java/hdm/mi/growbros/controllers/AuthenticationController.java b/src/main/java/hdm/mi/growbros/controllers/AuthenticationController.java index e82f7f0bb39dad3cef38db5d2dfd09cb1d4e4034..9684e861dfab17106a7f31be44ddc4e686fc44fe 100644 --- a/src/main/java/hdm/mi/growbros/controllers/AuthenticationController.java +++ b/src/main/java/hdm/mi/growbros/controllers/AuthenticationController.java @@ -4,8 +4,12 @@ import hdm.mi.growbros.auth.AuthenticationRequest; import hdm.mi.growbros.auth.AuthenticationResponse; import hdm.mi.growbros.auth.AuthenticationService; import hdm.mi.growbros.auth.RegisterRequest; +import hdm.mi.growbros.exceptions.EmailAlreadyExistsException; +import hdm.mi.growbros.exceptions.GrowBrosException; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; +import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -20,7 +24,7 @@ public class AuthenticationController { public ResponseEntity<AuthenticationResponse> register( @RequestBody RegisterRequest request ) { - return ResponseEntity.ok(authenticationService.register(request)); + return ResponseEntity.ok(authenticationService.register(request)); } @PostMapping("/authenticate") diff --git a/src/main/java/hdm/mi/growbros/exceptions/EmailAlreadyExistsException.java b/src/main/java/hdm/mi/growbros/exceptions/EmailAlreadyExistsException.java new file mode 100644 index 0000000000000000000000000000000000000000..1e2330a4088326577ceb366b1f3e6ce2ce020bd2 --- /dev/null +++ b/src/main/java/hdm/mi/growbros/exceptions/EmailAlreadyExistsException.java @@ -0,0 +1,9 @@ +package hdm.mi.growbros.exceptions; + +import org.springframework.http.HttpStatus; + +public class EmailAlreadyExistsException extends GrowBrosException{ + public EmailAlreadyExistsException(HttpStatus httpStatus, String message) { + super(httpStatus, message); + } +} diff --git a/src/main/java/hdm/mi/growbros/exceptions/InvalidDataException.java b/src/main/java/hdm/mi/growbros/exceptions/InvalidDataException.java index e1c032f96ad56d958892183526602840bac21df8..d45b903438e3bbf9239a40d9efa6a5e3cc72b0b1 100644 --- a/src/main/java/hdm/mi/growbros/exceptions/InvalidDataException.java +++ b/src/main/java/hdm/mi/growbros/exceptions/InvalidDataException.java @@ -1,8 +1,10 @@ package hdm.mi.growbros.exceptions; +import org.springframework.http.HttpStatus; + public class InvalidDataException extends GrowBrosException{ - public InvalidDataException(String message) { - super(message); + public InvalidDataException(HttpStatus httpStatus, String message) { + super(httpStatus, message); } } diff --git a/src/main/java/hdm/mi/growbros/exceptions/InvalidEmailException.java b/src/main/java/hdm/mi/growbros/exceptions/InvalidEmailException.java index a181cb240a93a44d4cbf6186dec6e6096be04feb..65e484bff184a4e11aec61ffc51844be8f3ce1e8 100644 --- a/src/main/java/hdm/mi/growbros/exceptions/InvalidEmailException.java +++ b/src/main/java/hdm/mi/growbros/exceptions/InvalidEmailException.java @@ -3,7 +3,7 @@ package hdm.mi.growbros.exceptions; import org.springframework.http.HttpStatus; public class InvalidEmailException extends GrowBrosException{ - public InvalidEmailException(String message) { - super(message); + public InvalidEmailException(HttpStatus httpStatus,String message) { + super(httpStatus, message); } } diff --git a/src/test/java/hdm/mi/growbros/config/SpringSecurityTestConfig.java b/src/test/java/hdm/mi/growbros/config/SpringSecurityTestConfig.java index 24f4295782dd6275223969747ba07f98c9a05c15..1604559107251981e956e0d09f0c509d76e2bd9d 100644 --- a/src/test/java/hdm/mi/growbros/config/SpringSecurityTestConfig.java +++ b/src/test/java/hdm/mi/growbros/config/SpringSecurityTestConfig.java @@ -15,7 +15,7 @@ import java.util.List; @TestConfiguration public class SpringSecurityTestConfig { @Bean - @Primary + //@Primary public UserDetailsService testUserDetailsService() { User user1 = UserTestData.getUser1(); User user2 = UserTestData.getUser2();