fix captchas not being validated properly

This commit is contained in:
Braydon 2024-09-17 14:16:36 -04:00
parent 18d916391f
commit 25b96b3be9
6 changed files with 44 additions and 21 deletions

@ -60,6 +60,7 @@ public class WebSecurityConfig {
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/register")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/login")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/user/exists")).permitAll()
.anyRequest().authenticated())
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint((request, response, authException) -> { // Handle invalid access tokens

@ -8,6 +8,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* This controller is responsible for
* handling user authentication requests.
@ -37,4 +39,9 @@ public final class UserController {
public ResponseEntity<UserDTO> getUser() {
return ResponseEntity.ok(userService.getUser());
}
@GetMapping("/exists") @ResponseBody @NonNull
public ResponseEntity<Map<String, Object>> doesUserExist(@RequestParam @NonNull String email) {
return ResponseEntity.ok(Map.of("exists", userService.doesUserExist(email)));
}
}

@ -17,11 +17,6 @@ public final class UserLoginInput {
*/
private final String email;
/**
* The username of the user to login with.
*/
private final String username;
/**
* The password of the user to login with.
*/
@ -38,7 +33,7 @@ public final class UserLoginInput {
* @return whether this input is valid
*/
public boolean isValid() {
return (email != null && (!email.isBlank()) || username != null && (!username.isBlank()))
return email != null && (!email.isBlank())
&& password != null && (!password.isBlank())
&& captchaResponse != null && (!captchaResponse.isBlank());
}

@ -23,7 +23,6 @@ import org.springframework.stereotype.Service;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
@ -100,8 +99,7 @@ public final class AuthService {
validateLoginInput(input); // Ensure the input is valid
// Lookup the user by the email or username and ensure the user exists
User user = input.getEmail() == null ? userRepository.findByUsernameIgnoreCase(Objects.requireNonNull(input.getUsername()))
: userRepository.findByEmailIgnoreCase(input.getEmail());
User user = userRepository.findByEmailIgnoreCase(input.getEmail());
if (user == null) {
throw new BadRequestException(Error.USER_NOT_FOUND);
}
@ -197,10 +195,8 @@ public final class AuthService {
throw new BadRequestException(passwordError);
}
// Finally validate the captcha
if (EnvironmentUtils.isProduction()) {
captchaService.validateCaptcha(input.getCaptchaResponse());
}
}
/**
* Validate the given login input.
@ -217,15 +213,9 @@ public final class AuthService {
if (input.getEmail() != null && (!StringUtils.isValidEmail(input.getEmail()))) {
throw new BadRequestException(Error.EMAIL_INVALID);
}
// Ensure the username is valid
if (input.getUsername() != null && (!StringUtils.isValidUsername(input.getUsername()))) {
throw new BadRequestException(Error.USERNAME_INVALID);
}
// Finally validate the captcha
if (EnvironmentUtils.isProduction()) {
captchaService.validateCaptcha(input.getCaptchaResponse());
}
}
private enum Error implements IGenericResponse {
MALFORMED_INPUT,

@ -8,6 +8,7 @@ import kong.unirest.core.JsonNode;
import kong.unirest.core.Unirest;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
/**
@ -28,7 +29,10 @@ public final class CaptchaService {
JsonObject body = new JsonObject();
body.addProperty("secret", secretKey);
body.addProperty("response", captchaResponse);
HttpResponse<JsonNode> response = Unirest.post("https://challenges.cloudflare.com/turnstile/v0/siteverify").body(body).asJson();
HttpResponse<JsonNode> response = Unirest.post("https://challenges.cloudflare.com/turnstile/v0/siteverify")
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(body)
.asJson();
if (!response.getBody().getObject().getBoolean("success")) {
throw new BadRequestException(Error.CAPTCHA_INVALID);
}

@ -1,7 +1,9 @@
package cc.pulseapp.api.service;
import cc.pulseapp.api.common.StringUtils;
import cc.pulseapp.api.model.user.User;
import cc.pulseapp.api.model.user.UserDTO;
import cc.pulseapp.api.repository.UserRepository;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -13,6 +15,9 @@ import java.util.Date;
*/
@Service
public final class UserService {
/**
* The auth service to use.
*/
@NonNull private final AuthService authService;
/**
@ -20,15 +25,36 @@ public final class UserService {
*/
@NonNull private final SnowflakeService snowflakeService;
/**
* The user repository to use.
*/
@NonNull private final UserRepository userRepository;
@Autowired
public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService) {
public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService, @NonNull UserRepository userRepository) {
this.authService = authService;
this.snowflakeService = snowflakeService;
this.userRepository = userRepository;
}
/**
* Get the currently authenticated user.
*
* @return the authenticated user
*/
@NonNull
public UserDTO getUser() {
User user = authService.getAuthenticatedUser();
return UserDTO.asDTO(user, new Date(snowflakeService.extractCreationTime(user.getSnowflake())));
}
/**
* Check if the user with the given email exists.
*
* @param email the email to check
* @return whether the user exists
*/
public boolean doesUserExist(@NonNull String email) {
return StringUtils.isValidEmail(email) && userRepository.findByEmailIgnoreCase(email) != null;
}
}