Skip to content

Commit

Permalink
feat: add 2FA type in authentication error message (#19894)
Browse files Browse the repository at this point in the history
* feat: add 2FA type in authentication error message

Signed-off-by: Morten Svanaes <[email protected]>
  • Loading branch information
netroms authored Feb 10, 2025
1 parent 5c86744 commit ef1850f
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@
*/
package org.hisp.dhis.security.spring2fa;

import org.hisp.dhis.security.twofa.TwoFactorType;
import org.springframework.security.authentication.BadCredentialsException;

/**
* @author Morten Svanæs <[email protected]>
*/
public class TwoFactorAuthenticationException extends BadCredentialsException {
public TwoFactorAuthenticationException(String msg) {
private final TwoFactorType type;

public TwoFactorAuthenticationException(String msg, TwoFactorType type) {
super(msg);
this.type = type;
}

public TwoFactorType getType() {
return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,17 @@ private void validate2FACode(@CheckForNull String code, @Nonnull UserDetails use
if (type == TwoFactorType.EMAIL_ENABLED && StringUtils.isBlank(code)) {
sendEmail2FACode(userDetails);
// Inform the caller that the email code has been sent.
throw new TwoFactorAuthenticationException(ErrorCode.E3051.getMessage());
throw new TwoFactorAuthenticationException(ErrorCode.E3051.getMessage(), type);
}

// If the code is blank (null, empty, or only whitespace), reject the login.
if (StringUtils.isBlank(code)) {
throw new TwoFactorAuthenticationException(ErrorCode.E3023.getMessage());
throw new TwoFactorAuthenticationException(ErrorCode.E3023.getMessage(), type);
}

// Validate the provided 2FA code.
if (!isValid2FACode(type, code, userDetails.getSecret())) {
throw new TwoFactorAuthenticationException(ErrorCode.E3023.getMessage());
throw new TwoFactorAuthenticationException(ErrorCode.E3023.getMessage(), type);
}
// If no exception is thrown, the 2FA code is valid.
}
Expand All @@ -194,7 +194,8 @@ private void sendEmail2FACode(UserDetails userDetails) {
try {
twoFactorAuthService.sendEmail2FACode(userDetails.getUsername());
} catch (ConflictException e) {
throw new TwoFactorAuthenticationException(ErrorCode.E3049.getMessage());
throw new TwoFactorAuthenticationException(
ErrorCode.E3049.getMessage(), TwoFactorType.EMAIL_ENABLED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public enum STATUS {
ACCOUNT_LOCKED("accountLocked"),
ACCOUNT_EXPIRED("accountExpired"),
PASSWORD_EXPIRED("passwordExpired"),
INCORRECT_TWO_FACTOR_CODE("incorrectTwoFactorCode"),
INCORRECT_TWO_FACTOR_CODE_TOTP("incorrectTwoFactorCodeTOTP"),
INCORRECT_TWO_FACTOR_CODE_EMAIL("incorrectTwoFactorCodeEmail"),
REQUIRES_TWO_FACTOR_ENROLMENT("requiresTwoFactorEnrolment");

private final String keyName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ void testDisableTOTP2FA() throws JsonProcessingException {
// Test Login doesn't work without 2FA code
ResponseEntity<LoginResponse> failedLoginResp =
loginWithUsernameAndPassword(username, password, null);
assertLoginStatus(failedLoginResp, STATUS.INCORRECT_TWO_FACTOR_CODE);
assertLoginStatus(failedLoginResp, STATUS.INCORRECT_TWO_FACTOR_CODE_TOTP);

// Disable TOTP 2FA
disable2FAWithTOTP(qrSecretAndCookie);
Expand All @@ -181,7 +181,7 @@ void testDisableEmail2FA() throws IOException, MessagingException {
// Test Login doesn't work without 2FA code
ResponseEntity<LoginResponse> failedLoginResp =
loginWithUsernameAndPassword(username, password, null);
assertLoginStatus(failedLoginResp, STATUS.INCORRECT_TWO_FACTOR_CODE);
assertLoginStatus(failedLoginResp, STATUS.INCORRECT_TWO_FACTOR_CODE_EMAIL);

// Disable Email 2FA
disable2FAWithEmail(cookie);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void testLoginWithTOTP2FA() {
.content(HttpStatus.OK)
.as(JsonLoginResponse.class);

assertEquals("INCORRECT_TWO_FACTOR_CODE", wrong2FaCodeResponse.getLoginStatus());
assertEquals("INCORRECT_TWO_FACTOR_CODE_TOTP", wrong2FaCodeResponse.getLoginStatus());
Assertions.assertNull(wrong2FaCodeResponse.getRedirectUrl());

Totp totp = new Totp(secret);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.hisp.dhis.security.spring2fa.TwoFactorAuthenticationEnrolmentException;
import org.hisp.dhis.security.spring2fa.TwoFactorAuthenticationException;
import org.hisp.dhis.security.spring2fa.TwoFactorWebAuthenticationDetails;
import org.hisp.dhis.security.twofa.TwoFactorType;
import org.hisp.dhis.setting.SystemSettingsProvider;
import org.hisp.dhis.user.User;
import org.hisp.dhis.user.UserDetails;
Expand Down Expand Up @@ -163,7 +164,11 @@ public LoginResponse login(
return LoginResponse.builder().loginStatus(STATUS.SUCCESS).redirectUrl(redirectUrl).build();

} catch (TwoFactorAuthenticationException e) {
return LoginResponse.builder().loginStatus(STATUS.INCORRECT_TWO_FACTOR_CODE).build();
TwoFactorType twoFactorType = e.getType();
if (twoFactorType == TwoFactorType.EMAIL_ENABLED) {
return LoginResponse.builder().loginStatus(STATUS.INCORRECT_TWO_FACTOR_CODE_EMAIL).build();
}
return LoginResponse.builder().loginStatus(STATUS.INCORRECT_TWO_FACTOR_CODE_TOTP).build();
} catch (TwoFactorAuthenticationEnrolmentException e) {
return LoginResponse.builder().loginStatus(STATUS.REQUIRES_TWO_FACTOR_ENROLMENT).build();
} catch (CredentialsExpiredException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public enum STATUS {
ACCOUNT_LOCKED("accountLocked"),
ACCOUNT_EXPIRED("accountExpired"),
PASSWORD_EXPIRED("passwordExpired"),
INCORRECT_TWO_FACTOR_CODE("incorrectTwoFactorCode"),
INCORRECT_TWO_FACTOR_CODE_TOTP("incorrectTwoFactorCodeTOTP"),
INCORRECT_TWO_FACTOR_CODE_EMAIL("incorrectTwoFactorCodeEmail"),
REQUIRES_TWO_FACTOR_ENROLMENT("requiresTwoFactorEnrolment");

private final String keyName;
Expand Down

0 comments on commit ef1850f

Please sign in to comment.