Require your TFA pin to disable TFA
All checks were successful
Deploy API / deploy (ubuntu-latest, 2.44.0) (push) Successful in 43s

This commit is contained in:
Braydon 2024-09-19 23:33:56 -04:00
parent b95c92707b
commit ce87e6e242
3 changed files with 52 additions and 5 deletions

@ -4,6 +4,7 @@ import cc.pulseapp.api.exception.impl.BadRequestException;
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.model.user.input.CompleteOnboardingInput; import cc.pulseapp.api.model.user.input.CompleteOnboardingInput;
import cc.pulseapp.api.model.user.input.DisableTFAInput;
import cc.pulseapp.api.model.user.input.EnableTFAInput; import cc.pulseapp.api.model.user.input.EnableTFAInput;
import cc.pulseapp.api.model.user.input.UserExistsInput; import cc.pulseapp.api.model.user.input.UserExistsInput;
import cc.pulseapp.api.model.user.response.UserSetupTFAResponse; import cc.pulseapp.api.model.user.response.UserSetupTFAResponse;
@ -100,11 +101,13 @@ public final class UserController {
/** /**
* A POST endpoint to disable TFA for a useer. * A POST endpoint to disable TFA for a useer.
* *
* @param input the input to process
* @return the disabled response * @return the disabled response
* @throws BadRequestException if disabling fails
*/ */
@PostMapping("/disable-tfa") @ResponseBody @NonNull @PostMapping("/disable-tfa") @ResponseBody @NonNull
public ResponseEntity<Map<String, Object>> disableTwoFactor() { public ResponseEntity<Map<String, Object>> disableTwoFactor(DisableTFAInput input) throws BadRequestException {
userService.disableTwoFactor(); userService.disableTwoFactor(input);
return ResponseEntity.ok(Map.of("success", true)); return ResponseEntity.ok(Map.of("success", true));
} }

@ -0,0 +1,28 @@
package cc.pulseapp.api.model.user.input;
import cc.pulseapp.api.model.user.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* The input to disable TFA for a {@link User}.
*
* @author Braydon
*/
@AllArgsConstructor @Getter @ToString
public final class DisableTFAInput {
/**
* The TFA pin to validate.
*/
private final String pin;
/**
* Check if this input is valid.
*
* @return whether this input is valid
*/
public boolean isValid() {
return pin != null && (pin.length() == 6);
}
}

@ -11,6 +11,7 @@ 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.model.user.UserFlag; import cc.pulseapp.api.model.user.UserFlag;
import cc.pulseapp.api.model.user.input.CompleteOnboardingInput; import cc.pulseapp.api.model.user.input.CompleteOnboardingInput;
import cc.pulseapp.api.model.user.input.DisableTFAInput;
import cc.pulseapp.api.model.user.input.EnableTFAInput; import cc.pulseapp.api.model.user.input.EnableTFAInput;
import cc.pulseapp.api.model.user.input.UserExistsInput; import cc.pulseapp.api.model.user.input.UserExistsInput;
import cc.pulseapp.api.model.user.response.UserSetupTFAResponse; import cc.pulseapp.api.model.user.response.UserSetupTFAResponse;
@ -197,7 +198,7 @@ public final class UserService {
throw new BadRequestException(Error.TFA_SETUP_SECRET_MISMATCH); throw new BadRequestException(Error.TFA_SETUP_SECRET_MISMATCH);
} }
if (!tfaService.getPin(secret).equals(input.getPin())) { // Ensure the pin is valid if (!tfaService.getPin(secret).equals(input.getPin())) { // Ensure the pin is valid
throw new BadRequestException(Error.TFA_SETUP_PIN_INVALID); throw new BadRequestException(Error.TFA_PIN_INVALID);
} }
// Enable TFA for the user // Enable TFA for the user
byte[] salt = HashUtils.generateSalt(); byte[] salt = HashUtils.generateSalt();
@ -225,9 +226,22 @@ public final class UserService {
/** /**
* Disable two-factor auth for the * Disable two-factor auth for the
* currently authenticated user. * currently authenticated user.
*
* @param input the input to process
* @throws BadRequestException if disabling fails
*/ */
public void disableTwoFactor() { public void disableTwoFactor(DisableTFAInput input) throws BadRequestException {
if (input == null || (!input.isValid())) { // Ensure the input was provided
throw new BadRequestException(Error.MALFORMED_DISABLE_TFA_INPUT);
}
User user = authService.getAuthenticatedUser(); User user = authService.getAuthenticatedUser();
if (!user.hasFlag(UserFlag.TFA_ENABLED)) { // Ensure TFA is already on
throw new BadRequestException(Error.TFA_NOT_ENABLED);
}
if (!tfaService.getPin(user.getTfa().getSecret()).equals(input.getPin())) { // Ensure the pin is valid
throw new BadRequestException(Error.TFA_PIN_INVALID);
}
// Disable TFA for the user
user.setTfa(null); user.setTfa(null);
user.removeFlag(UserFlag.TFA_ENABLED); user.removeFlag(UserFlag.TFA_ENABLED);
userRepository.save(user); userRepository.save(user);
@ -247,11 +261,13 @@ public final class UserService {
MALFORMED_USER_EXISTS_INPUT, MALFORMED_USER_EXISTS_INPUT,
MALFORMED_ONBOARDING_INPUT, MALFORMED_ONBOARDING_INPUT,
MALFORMED_ENABLE_TFA_INPUT, MALFORMED_ENABLE_TFA_INPUT,
MALFORMED_DISABLE_TFA_INPUT,
ORGANIZATION_SLUG_INVALID, ORGANIZATION_SLUG_INVALID,
ALREADY_ONBOARDED, ALREADY_ONBOARDED,
TFA_ALREADY_ENABLED, TFA_ALREADY_ENABLED,
TFA_NOT_ENABLED,
TFA_SETUP_TIMED_OUT, TFA_SETUP_TIMED_OUT,
TFA_SETUP_SECRET_MISMATCH, TFA_SETUP_SECRET_MISMATCH,
TFA_SETUP_PIN_INVALID, TFA_PIN_INVALID,
} }
} }