From 37c3fb0979830583eb238255fe1bf4e10adab7b2 Mon Sep 17 00:00:00 2001 From: durn Date: Thu, 30 Apr 2026 00:04:52 -0600 Subject: [PATCH] more checks and stuff for verification --- .../demo/controller/EmailController.java | 5 + .../demo/controller/UserController.java | 9 + .../com/example/demo/repository/UserRepo.java | 2 + .../example/demo/service/EmailService.java | 11 + .../demo/service/Impl/UserServiceImpl.java | 5 + .../com/example/demo/service/UserService.java | 2 + .../resources/templates/create-account.html | 208 ++++++++++-------- 7 files changed, 155 insertions(+), 87 deletions(-) diff --git a/src/main/java/com/example/demo/controller/EmailController.java b/src/main/java/com/example/demo/controller/EmailController.java index 56d74c0..6e94ca1 100644 --- a/src/main/java/com/example/demo/controller/EmailController.java +++ b/src/main/java/com/example/demo/controller/EmailController.java @@ -25,6 +25,11 @@ public class EmailController { if (emailService.isValidEmail(email) == false) { return ResponseEntity.status(429).body("Invalid Email Detected."); } + + if (emailService.isEmailTaken(email)) { + return ResponseEntity.status(409).body("An account with this email already exists."); + } + return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/example/demo/controller/UserController.java b/src/main/java/com/example/demo/controller/UserController.java index 30429aa..e00b079 100644 --- a/src/main/java/com/example/demo/controller/UserController.java +++ b/src/main/java/com/example/demo/controller/UserController.java @@ -57,6 +57,15 @@ public class UserController { List users = userService.getUsersByName(name); return new ResponseEntity<>(users, HttpStatus.OK); } + + @PostMapping("/check") + public ResponseEntity isTaken(@RequestParam String name) { + + if (userService.isUsernameTaken(name)) { + return ResponseEntity.status(409).body("An account with this username already exists."); + } + return ResponseEntity.ok().build(); + } // build get current user REST API @GetMapping("/me") diff --git a/src/main/java/com/example/demo/repository/UserRepo.java b/src/main/java/com/example/demo/repository/UserRepo.java index 669a759..0c6d4a3 100644 --- a/src/main/java/com/example/demo/repository/UserRepo.java +++ b/src/main/java/com/example/demo/repository/UserRepo.java @@ -10,6 +10,8 @@ import java.util.Optional; public interface UserRepo extends JpaRepository { Optional findByUsername(String username); + + Optional findByEmail(String Email); List findByUsernameContainingIgnoreCase(String name); diff --git a/src/main/java/com/example/demo/service/EmailService.java b/src/main/java/com/example/demo/service/EmailService.java index 06265f8..7039352 100644 --- a/src/main/java/com/example/demo/service/EmailService.java +++ b/src/main/java/com/example/demo/service/EmailService.java @@ -7,6 +7,9 @@ import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; + +import com.example.demo.repository.UserRepo; + import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; @@ -23,6 +26,9 @@ public class EmailService { @Autowired private OtpStore otpStore; + @Autowired + private UserRepo userRepository; + public void sendOtpEmail(String toEmail) { String otp = OtpUtil.generateOtp(6); otpStore.save(toEmail, otp); @@ -46,11 +52,16 @@ public class EmailService { } } + public boolean isEmailTaken(String email) { + return userRepository.findByEmail(email.toLowerCase().trim()).isPresent(); + } + public boolean isValidEmail(String email) { if (email == null || email.isBlank()) return false; String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; if (!email.matches(emailRegex)) return false; + try { InternetAddress emailAddr = new InternetAddress(email, true); diff --git a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java index 6f00bee..43d7dff 100644 --- a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java @@ -255,4 +255,9 @@ public class UserServiceImpl implements UserService { return favoriteRepository.existsById(new FavoriteId(user.getId(), recipeId)); } + + @Override + public Boolean isUsernameTaken(String username) { + return userRepository.findByUsername(username.toLowerCase().trim()).isPresent(); + } } \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/UserService.java b/src/main/java/com/example/demo/service/UserService.java index a2b5cea..56bd9fe 100644 --- a/src/main/java/com/example/demo/service/UserService.java +++ b/src/main/java/com/example/demo/service/UserService.java @@ -10,6 +10,8 @@ import com.example.demo.entity.User; public interface UserService { UserDto convertToDto(User user); + + Boolean isUsernameTaken(String username); User saveUser(User user); diff --git a/src/main/resources/templates/create-account.html b/src/main/resources/templates/create-account.html index 3c1a79a..75bc10a 100644 --- a/src/main/resources/templates/create-account.html +++ b/src/main/resources/templates/create-account.html @@ -87,108 +87,142 @@ function isProfane(str) { error.textContent = message; } - document.addEventListener("DOMContentLoaded", function () { + document.addEventListener("DOMContentLoaded", function () { - const form = document.getElementById("createUserForm"); - const passwordField = document.getElementById("password"); - const confirmPasswordField = document.getElementById("confirmPassword"); - const passwordError = document.getElementById("passwordError"); + const form = document.getElementById("createUserForm"); + const passwordField = document.getElementById("password"); + const confirmPasswordField = document.getElementById("confirmPassword"); + const usernameField = document.getElementById("username"); + const emailField = document.getElementById("email"); + const passwordError = document.getElementById("passwordError"); - function checkPasswords() { - if (confirmPasswordField.value === "") { - confirmPasswordField.classList.remove("invalid"); - passwordError.textContent = "Password cannot be blank"; - return; - } - + function checkPasswords() { + if (confirmPasswordField.value === "") { + confirmPasswordField.classList.remove("invalid"); + passwordError.textContent = ""; + return; + } + if (passwordField.value !== confirmPasswordField.value) { + confirmPasswordField.classList.add("invalid"); + passwordError.textContent = "Passwords do not match."; + } else { + confirmPasswordField.classList.remove("invalid"); + passwordError.textContent = ""; + } + } - if (passwordField.value !== confirmPasswordField.value) { - confirmPasswordField.classList.add("invalid"); - passwordError.textContent = "Passwords do not match."; - } else { - confirmPasswordField.classList.remove("invalid"); - passwordError.textContent = ""; - } - } + passwordField.addEventListener("input", checkPasswords); + confirmPasswordField.addEventListener("input", checkPasswords); - passwordField.addEventListener("input", checkPasswords); - confirmPasswordField.addEventListener("input", checkPasswords); + const csrfToken = () => document.querySelector('meta[name="_csrf"]').getAttribute("content"); + const csrfHeader = () => document.querySelector('meta[name="_csrf_header"]').getAttribute("content"); - form.addEventListener("submit", async function(e) { - e.preventDefault(); + emailField.addEventListener("blur", async () => { + if (!emailField.value) return; + const res = await fetch(`/api/email/check?email=${encodeURIComponent(emailField.value)}`, + { method: "POST", headers: { [csrfHeader()]: csrfToken() } }); + if (!res.ok) showError(emailField, await res.text() || "Email already in use."); + else clearError(emailField); + }); - const password = passwordField.value; - const confirmPassword = confirmPasswordField.value; - const name = document.getElementById("username").value; - const email = document.getElementById("email").value; + usernameField.addEventListener("blur", async () => { + if (!usernameField.value) { + clearError(usernameField); + return; + } - if (password !== confirmPassword) { - confirmPasswordField.classList.add("invalid"); - passwordError.textContent = "Passwords do not match."; - return; - } - - if (isProfane(document.getElementById("username").value)) { - showError(username, 'Username contains inappropriate language'); - return; - } - + if (isProfane(usernameField.value)) { + showError(usernameField, "Username contains inappropriate language."); + return; + } + clearError(usernameField); - const userData = { - username: document.getElementById("username").value, - email: document.getElementById("email").value, - hashedpassword: password, - role: "ROLE_USER" - }; + const res = await fetch(`/api/users/check?name=${encodeURIComponent(usernameField.value)}`, + { method: "POST", headers: { [csrfHeader()]: csrfToken() } }); - const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content'); - const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content'); + if (!res.ok) showError(usernameField, await res.text() || "Username already taken."); + else clearError(usernameField); + }) - try { - const checkResponse = await fetch(`/api/email/check?email=${encodeURIComponent(email)}`, { - method: "POST", - headers: { [csrfHeader]: csrfToken } - }); + form.addEventListener("submit", async function(e) { + e.preventDefault(); + + [usernameField, emailField, passwordField, confirmPasswordField].forEach(clearError); + passwordError.textContent = ""; - if (!checkResponse.ok) { - const errorText = await checkResponse.text(); - passwordError.style.color = "red"; - passwordError.textContent = errorText || "Invalid Email Detected."; - return; - } + const password = passwordField.value; + const confirmPassword = confirmPasswordField.value; + const name = usernameField.value; + const email = emailField.value; - sessionStorage.setItem("pendingEmail", email); - sessionStorage.setItem("pendingUser", JSON.stringify({ - username: name, - email: email, - hashedpassword: password, - role: "ROLE_USER" - })); + if (password !== confirmPassword) { + confirmPasswordField.classList.add("invalid"); + passwordError.textContent = "Passwords do not match."; + return; + } - const sendResponse = await fetch(`/api/email/send?email=${encodeURIComponent(email)}`, { - method: "POST", - headers: { [csrfHeader]: csrfToken } - }); - - if (!sendResponse.ok) { - passwordError.style.color = "red"; - passwordError.textContent = "Failed to send verification email. Please check your address."; - return; - } + if (isProfane(name)) { + showError(usernameField, "Username contains inappropriate language."); + return; + } - passwordError.style.color = "green"; - passwordError.textContent = "Check your email for a verification code..."; - setTimeout(function () { - window.location.href = "/verify"; - }, 1500); + try { + const headers = { [csrfHeader()]: csrfToken() }; - } catch (error) { - passwordError.style.color = "red"; - passwordError.textContent = "Could not connect to the server."; - console.error("Request error:", error); - } - }); - }); + const emailRes = await fetch(`/api/email/check?email=${encodeURIComponent(email)}`, + { method: "POST", headers }); + if (!emailRes.ok) { + passwordError.style.color = "red"; + passwordError.textContent = await emailRes.text() || "Invalid email detected."; + return; + } + + const userRes = await fetch(`/api/users/check?name=${encodeURIComponent(name)}`, + { method: "POST", headers }); + if (!userRes.ok) { + passwordError.style.color = "red"; + passwordError.textContent = await userRes.text() || "Username already taken."; + return; + } + + sessionStorage.setItem("pendingEmail", email); + sessionStorage.setItem("pendingUser", JSON.stringify({ + username: name, email, hashedpassword: password, role: "ROLE_USER" + })); + + + const sendRes = await fetch(`/api/email/send?email=${encodeURIComponent(email)}`, + { method: "POST", headers }); + if (!sendRes.ok) { + passwordError.style.color = "red"; + passwordError.textContent = "Failed to send verification email. Please check your address."; + return; + } + + passwordError.style.color = "green"; + passwordError.textContent = "Check your email for a verification code..."; + setTimeout(() => { window.location.href = "/verify"; }, 1500); + + } catch (error) { + passwordError.style.color = "red"; + passwordError.textContent = "Could not connect to the server."; + console.error("Request error:", error); + } + }); + }); + + + + function showError(input, message) { + input.classList.add("invalid"); + passwordError.style.color = "red"; + passwordError.textContent = message; + } + + function clearError(input) { + input.classList.remove("invalid"); + passwordError.textContent = ""; + } \ No newline at end of file