diff --git a/Frontend/src/components/Message.jsx b/Frontend/src/components/Message.jsx index eb8d0d8..cc18762 100644 --- a/Frontend/src/components/Message.jsx +++ b/Frontend/src/components/Message.jsx @@ -1,12 +1,18 @@ import React from "react"; -const Message = ({ message }) => { +const Message = ({ message, type = "error" }) => { const date = new Date(); + + const background = + type === "error" + ? "bg-red-100 border border-red-400 text-red-700" + : "bg-gray-100 border border-gray-300 text-gray-800"; + return ( -
-

{message}

-

- {date.getDate()}/{date.getMonth()}/{date.getFullYear()}{" "} +

+

{message}

+

+ {date.getDate()}/{date.getMonth() + 1}/{date.getFullYear()}{" "} {date.toLocaleTimeString()}

diff --git a/Frontend/src/pages/Login/SignupPage.jsx b/Frontend/src/pages/Login/SignupPage.jsx index ee38806..894d9b2 100644 --- a/Frontend/src/pages/Login/SignupPage.jsx +++ b/Frontend/src/pages/Login/SignupPage.jsx @@ -1,8 +1,9 @@ -import React, { useRef } from "react"; +import React, { useRef, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { BACKEND_URL } from "../../constants"; import { t } from "../../service/translation"; import { useOutletContext } from "react-router-dom"; +import { isPasswordPwned } from "../../utils/passwordUtils"; const SignupPage = (props) => { const outletContext = useOutletContext?.(); @@ -12,41 +13,72 @@ const SignupPage = (props) => { const firstNameElement = useRef(); const lastNameElement = useRef(); const emailElement = useRef(); - const roleElement = useRef(); const passwordElement = useRef(); + const [error, setError] = useState(""); const navigate = useNavigate(); const handleRegisteration = async (event) => { event.preventDefault(); + setError(""); + + const password = passwordElement.current.value; + + const strongPasswordRegex = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/; + + if (!strongPasswordRegex.test(password)) { + setError( + "Password must be at least 8 characters long and include uppercase, lowercase, number, and special character." + ); + return; + } + + try { + const pwned = await isPasswordPwned(password); + if (pwned) { + setError("This password previously appeared in a data breach. Please use a new password."); + return; + } + } catch (err) { + console.error("Password breach check failed:", err); + setError("Something went wrong while checking password security. Try again."); + return; + } const user = { name: firstNameElement.current.value + " " + lastNameElement.current.value, email: emailElement.current.value, - password: passwordElement.current.value, + password: password, }; - event.preventDefault(); + try { + const response = await fetch(`${BACKEND_URL}/api/v1/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(user), + credentials: "include", + }); - const responce = await fetch(`${BACKEND_URL}/api/v1/register`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(user), - credentials: "include", - }); - const data = await responce.json(); + const data = await response.json(); + + if (data.success === true) { + navigate("/user/login"); + } else { + setError(data.message || "Registration failed. Please try again."); + } + } catch (err) { + console.error("Registration error:", err); + setError("Something went wrong on the server. Please try again later."); + } firstNameElement.current.value = ""; lastNameElement.current.value = ""; emailElement.current.value = ""; passwordElement.current.value = ""; - - if (data.success == true) { - navigate("/user/login"); - } }; return ( @@ -57,39 +89,31 @@ const SignupPage = (props) => { {t("signup_register_heading", language)}

{t("signup_welcome", language)}

-

- {t("signup_subtitle", language)} -

-
+

{t("signup_subtitle", language)}

+
-
-
-
+
-
-
-
- - -
-
+ + {error &&

{error}

} +
+

{t("signup_already_have_account", language)}{" "} @@ -162,7 +169,7 @@ const SignupPage = (props) => {

-
+

{t("signup_journey_heading", language)}
diff --git a/Frontend/src/utils/passwordUtils.js b/Frontend/src/utils/passwordUtils.js new file mode 100644 index 0000000..acd0460 --- /dev/null +++ b/Frontend/src/utils/passwordUtils.js @@ -0,0 +1,22 @@ +export async function isPasswordPwned(password) { + const sha1 = await hashPassword(password); + const prefix = sha1.substring(0, 5); + const suffix = sha1.substring(5); + + const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`); + const text = await response.text(); + + const found = text + .split("\n") + .some((line) => line.split(":")[0] === suffix.toUpperCase()); + + return found; +} + +async function hashPassword(password) { + const encoder = new TextEncoder(); + const data = encoder.encode(password); + const hashBuffer = await crypto.subtle.digest("SHA-1", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase(); +}