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("/error")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/register")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/register")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/login")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher("/v*/auth/login")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v*/user/exists")).permitAll()
.anyRequest().authenticated()) .anyRequest().authenticated())
.exceptionHandling(exceptionHandling -> exceptionHandling .exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint((request, response, authException) -> { // Handle invalid access tokens .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.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map;
/** /**
* This controller is responsible for * This controller is responsible for
* handling user authentication requests. * handling user authentication requests.
@ -37,4 +39,9 @@ public final class UserController {
public ResponseEntity<UserDTO> getUser() { public ResponseEntity<UserDTO> getUser() {
return ResponseEntity.ok(userService.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; private final String email;
/**
* The username of the user to login with.
*/
private final String username;
/** /**
* The password of the user to login with. * The password of the user to login with.
*/ */
@ -38,7 +33,7 @@ public final class UserLoginInput {
* @return whether this input is valid * @return whether this input is valid
*/ */
public boolean isValid() { public boolean isValid() {
return (email != null && (!email.isBlank()) || username != null && (!username.isBlank())) return email != null && (!email.isBlank())
&& password != null && (!password.isBlank()) && password != null && (!password.isBlank())
&& captchaResponse != null && (!captchaResponse.isBlank()); && captchaResponse != null && (!captchaResponse.isBlank());
} }

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

@ -8,6 +8,7 @@ import kong.unirest.core.JsonNode;
import kong.unirest.core.Unirest; import kong.unirest.core.Unirest;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@ -28,7 +29,10 @@ public final class CaptchaService {
JsonObject body = new JsonObject(); JsonObject body = new JsonObject();
body.addProperty("secret", secretKey); body.addProperty("secret", secretKey);
body.addProperty("response", captchaResponse); 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")) { if (!response.getBody().getObject().getBoolean("success")) {
throw new BadRequestException(Error.CAPTCHA_INVALID); throw new BadRequestException(Error.CAPTCHA_INVALID);
} }

@ -1,7 +1,9 @@
package cc.pulseapp.api.service; package cc.pulseapp.api.service;
import cc.pulseapp.api.common.StringUtils;
import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.model.user.User;
import cc.pulseapp.api.model.user.UserDTO; import cc.pulseapp.api.model.user.UserDTO;
import cc.pulseapp.api.repository.UserRepository;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -13,6 +15,9 @@ import java.util.Date;
*/ */
@Service @Service
public final class UserService { public final class UserService {
/**
* The auth service to use.
*/
@NonNull private final AuthService authService; @NonNull private final AuthService authService;
/** /**
@ -20,15 +25,36 @@ public final class UserService {
*/ */
@NonNull private final SnowflakeService snowflakeService; @NonNull private final SnowflakeService snowflakeService;
/**
* The user repository to use.
*/
@NonNull private final UserRepository userRepository;
@Autowired @Autowired
public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService) { public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService, @NonNull UserRepository userRepository) {
this.authService = authService; this.authService = authService;
this.snowflakeService = snowflakeService; this.snowflakeService = snowflakeService;
this.userRepository = userRepository;
} }
/**
* Get the currently authenticated user.
*
* @return the authenticated user
*/
@NonNull @NonNull
public UserDTO getUser() { public UserDTO getUser() {
User user = authService.getAuthenticatedUser(); User user = authService.getAuthenticatedUser();
return UserDTO.asDTO(user, new Date(snowflakeService.extractCreationTime(user.getSnowflake()))); 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;
}
} }