@ -39,14 +39,14 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.audit.AuditLogService ;
import org.thingsboard.server.dao.user.UserService ;
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService ;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuth ConfigManager ;
import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuth Settings ;
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFactorAuth AccountConfig ;
import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFactorAuth AccountConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFactorAuth ProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFactorAuth ProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuth ProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuth ProviderType ;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager ;
import org.thingsboard.server.common.data.security.model.mfa.Platform TwoFaSettings ;
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig ;
import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFaAccountConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFaProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFaProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig ;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType ;
import org.thingsboard.server.service.security.auth.rest.LoginRequest ;
import org.thingsboard.server.service.security.model.JwtTokenPair ;
@ -68,319 +68,319 @@ import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status ;
public abstract class TwoFactorAuthTest extends AbstractControllerTest {
@Autowired
private TwoFactorAuthConfigManager twoFactorAuthConfigManager ;
@Autowired
private TwoFactorAuthService twoFactorAuthService ;
@MockBean
private SmsService smsService ;
@Autowired
private AuditLogService auditLogService ;
@Autowired
private UserService userService ;
private User user ;
private String username ;
private String password ;
@Before
public void beforeEach ( ) throws Exception {
username = "mfa@tb.io" ;
password = "psswrd" ;
user = new User ( ) ;
user . setAuthority ( Authority . TENANT_ADMIN ) ;
user . setEmail ( username ) ;
user . setTenantId ( tenantId ) ;
loginSysAdmin ( ) ;
user = createUser ( user , password ) ;
}
@After
public void afterEach ( ) {
twoFactorAuthConfigManager . deleteTwoFaSettings ( tenantId ) ;
twoFactorAuthConfigManager . deleteTwoFaSettings ( TenantId . SYS_TENANT_ID ) ;
}
@Test
public void testTwoFa_totp ( ) throws Exception {
TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa ( ) ;
logInWithPreVerificationToken ( ) ;
doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isOk ( ) ) ;
String correctVerificationCode = getCorrectTotp ( totpTwoFaAccountConfig ) ;
JsonNode tokenPair = readResponse ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode )
. andExpect ( status ( ) . isOk ( ) ) , JsonNode . class ) ;
validateAndSetJwtToken ( tokenPair , username ) ;
User currentUser = readResponse ( doGet ( "/api/auth/user" )
. andExpect ( status ( ) . isOk ( ) ) , User . class ) ;
assertThat ( currentUser . getId ( ) ) . isEqualTo ( user . getId ( ) ) ;
}
@Test
public void testTwoFa_sms ( ) throws Exception {
configureSmsTwoFa ( ) ;
logInWithPreVerificationToken ( ) ;
doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isOk ( ) ) ;
ArgumentCaptor < String > verificationCodeCaptor = ArgumentCaptor . forClass ( String . class ) ;
verify ( smsService ) . sendSms ( eq ( tenantId ) , any ( ) , any ( ) , verificationCodeCaptor . capture ( ) ) ;
String correctVerificationCode = verificationCodeCaptor . getValue ( ) ;
JsonNode tokenPair = readResponse ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode )
. andExpect ( status ( ) . isOk ( ) ) , JsonNode . class ) ;
validateAndSetJwtToken ( tokenPair , username ) ;
User currentUser = readResponse ( doGet ( "/api/auth/user" )
. andExpect ( status ( ) . isOk ( ) ) , User . class ) ;
assertThat ( currentUser . getId ( ) ) . isEqualTo ( user . getId ( ) ) ;
}
@Test
public void testTwoFaPreVerificationTokenLifetime ( ) throws Exception {
configureTotpTwoFa ( twoFaSettings - > {
twoFaSettings . setTotalAllowedTimeForVerification ( 5 ) ;
} ) ;
logInWithPreVerificationToken ( ) ;
await ( "expiration of the pre-verification token" )
. atLeast ( Duration . ofSeconds ( 3 ) . plusMillis ( 500 ) )
. atMost ( Duration . ofSeconds ( 6 ) )
. untilAsserted ( ( ) - > {
doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isUnauthorized ( ) ) ;
} ) ;
}
@Test
public void testCheckVerificationCode_userBlocked ( ) throws Exception {
configureTotpTwoFa ( twoFaSettings - > {
twoFaSettings . setMaxVerificationFailuresBeforeUserLockout ( 10 ) ;
} ) ;
logInWithPreVerificationToken ( ) ;
Stream . generate ( ( ) - > RandomStringUtils . randomNumeric ( 6 ) )
. limit ( 9 )
. forEach ( incorrectVerificationCode - > {
try {
String errorMessage = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + incorrectVerificationCode )
. andExpect ( status ( ) . isBadRequest ( ) ) ) ;
assertThat ( errorMessage ) . containsIgnoringCase ( "verification code is incorrect" ) ;
} catch ( Exception e ) {
fail ( ) ;
}
} ) ;
String errorMessage = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils . randomNumeric ( 6 ) )
. andExpect ( status ( ) . isUnauthorized ( ) ) ) ;
assertThat ( errorMessage ) . containsIgnoringCase ( "account was locked due to exceeded 2fa verification attempts" ) ;
errorMessage = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils . randomNumeric ( 6 ) )
. andExpect ( status ( ) . isUnauthorized ( ) ) ) ;
assertThat ( errorMessage ) . containsIgnoringCase ( "user is disabled" ) ;
}
@Test
public void testSendVerificationCode_rateLimit ( ) throws Exception {
configureTotpTwoFa ( twoFaSettings - > {
twoFaSettings . setVerificationCodeSendRateLimit ( "3:10" ) ;
} ) ;
logInWithPreVerificationToken ( ) ;
for ( int i = 0 ; i < 3 ; i + + ) {
doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isOk ( ) ) ;
}
String rateLimitExceededError = getErrorMessage ( doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isTooManyRequests ( ) ) ) ;
assertThat ( rateLimitExceededError ) . containsIgnoringCase ( "too many verification code sending requests" ) ;
await ( "verification code sending rate limit resetting" )
. atLeast ( Duration . ofSeconds ( 8 ) )
. atMost ( Duration . ofSeconds ( 12 ) )
. untilAsserted ( ( ) - > {
doPost ( "/api/auth/2fa/verification/send" )
. andExpect ( status ( ) . isOk ( ) ) ;
} ) ;
}
@Test
public void testCheckVerificationCode_rateLimit ( ) throws Exception {
TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa ( twoFaSettings - > {
twoFaSettings . setVerificationCodeCheckRateLimit ( "3:10" ) ;
} ) ;
logInWithPreVerificationToken ( ) ;
for ( int i = 0 ; i < 3 ; i + + ) {
String incorrectVerificationCodeError = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=incorrect" )
. andExpect ( status ( ) . isBadRequest ( ) ) ) ;
assertThat ( incorrectVerificationCodeError ) . containsIgnoringCase ( "verification code is incorrect" ) ;
}
String rateLimitExceededError = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=incorrect" )
. andExpect ( status ( ) . isTooManyRequests ( ) ) ) ;
assertThat ( rateLimitExceededError ) . containsIgnoringCase ( "too many verification code checking requests" ) ;
await ( "verification code checking rate limit resetting" )
. atLeast ( Duration . ofSeconds ( 8 ) )
. atMost ( Duration . ofSeconds ( 12 ) )
. untilAsserted ( ( ) - > {
String incorrectVerificationCodeError = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=incorrect" )
. andExpect ( status ( ) . isBadRequest ( ) ) ) ;
assertThat ( incorrectVerificationCodeError ) . containsIgnoringCase ( "verification code is incorrect" ) ;
} ) ;
doPost ( "/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp ( totpTwoFaAccountConfig ) )
. andExpect ( status ( ) . isOk ( ) ) ;
}
@Test
public void testCheckVerificationCode_invalidVerificationCode ( ) throws Exception {
configureTotpTwoFa ( ) ;
logInWithPreVerificationToken ( ) ;
for ( String invalidVerificationCode : new String [ ] { "1234567" , "ab1212" , "12311 " , "oewkriwejqf" } ) {
String errorMessage = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + invalidVerificationCode )
. andExpect ( status ( ) . isBadRequest ( ) ) ) ;
assertThat ( errorMessage ) . containsIgnoringCase ( "verification code is incorrect" ) ;
}
}
@Test
public void testCheckVerificationCode_codeExpiration ( ) throws Exception {
configureSmsTwoFa ( smsTwoFaProviderConfig - > {
smsTwoFaProviderConfig . setVerificationCodeLifetime ( 10 ) ;
} ) ;
logInWithPreVerificationToken ( ) ;
ArgumentCaptor < String > verificationCodeCaptor = ArgumentCaptor . forClass ( String . class ) ;
doPost ( "/api/auth/2fa/verification/send" ) . andExpect ( status ( ) . isOk ( ) ) ;
verify ( smsService ) . sendSms ( eq ( tenantId ) , any ( ) , any ( ) , verificationCodeCaptor . capture ( ) ) ;
String correctVerificationCode = verificationCodeCaptor . getValue ( ) ;
await ( "verification code expiration" )
. pollDelay ( 10 , TimeUnit . SECONDS )
. atLeast ( 10 , TimeUnit . SECONDS )
. atMost ( 12 , TimeUnit . SECONDS )
. untilAsserted ( ( ) - > {
String incorrectVerificationCodeError = getErrorMessage ( doPost ( "/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode )
. andExpect ( status ( ) . isBadRequest ( ) ) ) ;
assertThat ( incorrectVerificationCodeError ) . containsIgnoringCase ( "verification code is incorrect" ) ;
} ) ;
}
@Test
public void testTwoFa_logLoginAction ( ) throws Exception {
TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa ( ) ;
logInWithPreVerificationToken ( ) ;
await ( "async audit log saving" ) . during ( 1 , TimeUnit . SECONDS ) ;
assertThat ( getLogInAuditLogs ( ) ) . isEmpty ( ) ;
assertThat ( userService . findUserById ( tenantId , user . getId ( ) ) . getAdditionalInfo ( )
. get ( "lastLoginTs" ) ) . isNull ( ) ;
doPost ( "/api/auth/2fa/verification/check?verificationCode=incorrect" )
. andExpect ( status ( ) . isBadRequest ( ) ) ;
await ( "async audit log saving" ) . atMost ( 1 , TimeUnit . SECONDS )
. until ( ( ) - > getLogInAuditLogs ( ) . size ( ) = = 1 ) ;
assertThat ( getLogInAuditLogs ( ) . get ( 0 ) ) . satisfies ( failedLogInAuditLog - > {
assertThat ( failedLogInAuditLog . getActionStatus ( ) ) . isEqualTo ( ActionStatus . FAILURE ) ;
assertThat ( failedLogInAuditLog . getActionFailureDetails ( ) ) . containsIgnoringCase ( "verification code is incorrect" ) ;
assertThat ( failedLogInAuditLog . getUserName ( ) ) . isEqualTo ( username ) ;
} ) ;
doPost ( "/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp ( totpTwoFaAccountConfig ) )
. andExpect ( status ( ) . isOk ( ) ) ;
await ( "async audit log saving" ) . atMost ( 1 , TimeUnit . SECONDS )
. until ( ( ) - > getLogInAuditLogs ( ) . size ( ) = = 2 ) ;
assertThat ( getLogInAuditLogs ( ) . get ( 0 ) ) . satisfies ( successfulLogInAuditLog - > {
assertThat ( successfulLogInAuditLog . getActionStatus ( ) ) . isEqualTo ( ActionStatus . SUCCESS ) ;
assertThat ( successfulLogInAuditLog . getUserName ( ) ) . isEqualTo ( username ) ;
} ) ;
assertThat ( userService . findUserById ( tenantId , user . getId ( ) ) . getAdditionalInfo ( )
. get ( "lastLoginTs" ) . asLong ( ) )
. isGreaterThan ( System . currentTimeMillis ( ) - TimeUnit . SECONDS . toMillis ( 3 ) ) ;
}
private List < AuditLog > getLogInAuditLogs ( ) {
return auditLogService . findAuditLogsByTenantIdAndUserId ( tenantId , user . getId ( ) , List . of ( ActionType . LOGIN ) ,
new TimePageLink ( new PageLink ( 10 , 0 , null , new SortOrder ( "createdTime" , SortOrder . Direction . DESC ) ) , 0L , System . currentTimeMillis ( ) ) ) . getData ( ) ;
}
@Test
public void testAuthWithoutTwoFaAccountConfig ( ) throws ThingsboardException {
configureTotpTwoFa ( ) ;
twoFactorAuthConfigManager . deleteTwoFaAccountConfig ( tenantId , user . getId ( ) ) ;
assertDoesNotThrow ( ( ) - > {
login ( username , password ) ;
} ) ;
}
private void logInWithPreVerificationToken ( ) throws Exception {
LoginRequest loginRequest = new LoginRequest ( username , password ) ;
JwtTokenPair response = readResponse ( doPost ( "/api/auth/login" , loginRequest ) . andExpect ( status ( ) . isOk ( ) ) , JwtTokenPair . class ) ;
assertThat ( response . getToken ( ) ) . isNotNull ( ) ;
assertThat ( response . getRefreshToken ( ) ) . isNull ( ) ;
assertThat ( response . getScope ( ) ) . isEqualTo ( Authority . PRE_VERIFICATION_TOKEN ) ;
this . token = response . getToken ( ) ;
}
private TotpTwoFactorAuthAccountConfig configureTotpTwoFa ( Consumer < TwoFactorAuthSettings > . . . customizer ) throws ThingsboardException {
TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig ( ) ;
totpTwoFaProviderConfig . setIssuerName ( "tb" ) ;
TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings ( ) ;
twoFaSettings . setUseSystemTwoFactorAuthSettings ( false ) ;
twoFaSettings . setProviders ( Arrays . stream ( new TwoFactorAuthProviderConfig [ ] { totpTwoFaProviderConfig } ) . collect ( Collectors . toList ( ) ) ) ;
Arrays . stream ( customizer ) . forEach ( c - > c . accept ( twoFaSettings ) ) ;
twoFactorAuthConfigManager . saveTwoFaSettings ( tenantId , twoFaSettings ) ;
TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = ( TotpTwoFactorAuthAccountConfig ) twoFactorAuthService . generateNewAccountConfig ( user , TwoFactorAuthProviderType . TOTP ) ;
twoFactorAuthConfigManager . saveTwoFaAccountConfig ( tenantId , user . getId ( ) , totpTwoFaAccountConfig ) ;
return totpTwoFaAccountConfig ;
}
private SmsTwoFactorAuthAccountConfig configureSmsTwoFa ( Consumer < SmsTwoFactorAuthProviderConfig > . . . customizer ) throws ThingsboardException {
SmsTwoFactorAuthProviderConfig smsTwoFaProviderConfig = new SmsTwoFactorAuthProviderConfig ( ) ;
smsTwoFaProviderConfig . setVerificationCodeLifetime ( 60 ) ;
smsTwoFaProviderConfig . setSmsVerificationMessageTemplate ( "${verificationCode}" ) ;
Arrays . stream ( customizer ) . forEach ( c - > c . accept ( smsTwoFaProviderConfig ) ) ;
TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings ( ) ;
twoFaSettings . setUseSystemTwoFactorAuthSettings ( false ) ;
twoFaSettings . setProviders ( Arrays . stream ( new TwoFactorAuthProviderConfig [ ] { smsTwoFaProviderConfig } ) . collect ( Collectors . toList ( ) ) ) ;
twoFactorAuthConfigManager . saveTwoFaSettings ( tenantId , twoFaSettings ) ;
SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig ( ) ;
smsTwoFaAccountConfig . setPhoneNumber ( "+38050505050" ) ;
twoFactorAuthConfigManager . saveTwoFaAccountConfig ( tenantId , user . getId ( ) , smsTwoFaAccountConfig ) ;
return smsTwoFaAccountConfig ;
}
private String getCorrectTotp ( TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig ) {
String secret = StringUtils . substringAfterLast ( totpTwoFaAccountConfig . getAuthUrl ( ) , "secret=" ) ;
return new Totp ( secret ) . now ( ) ;
}
//
// @Autowired
// private TwoFaConfigManager twoFaConfigManager;
// @Autowired
// private TwoFactorAuthService twoFactorAuthService;
// @MockBean
// private SmsService smsService;
// @Autowired
// private AuditLogService auditLogService;
// @Autowired
// private UserService userService;
//
// private User user;
// private String username;
// private String password;
//
// @Before
// public void beforeEach() throws Exception {
// username = "mfa@tb.io";
// password = "psswrd";
//
// user = new User();
// user.setAuthority(Authority.TENANT_ADMIN);
// user.setEmail(username);
// user.setTenantId(tenantId);
//
// loginSysAdmin();
// user = createUser(user, password);
// }
//
// @After
// public void afterEach() {
// twoFaConfigManager.deletePlatformTwoFaSettings(tenantId);
// twoFaConfigManager.deletePlatformTwoFaSettings(TenantId.SYS_TENANT_ID);
// }
//
// @Test
// public void testTwoFa_totp() throws Exception {
// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa();
//
// logInWithPreVerificationToken();
//
// doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isOk());
//
// String correctVerificationCode = getCorrectTotp(totpTwoFaAccountConfig);
//
// JsonNode tokenPair = readResponse(doPost( "/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode)
// .andExpect(status().isOk()), JsonNode.class );
// validateAndSetJwtToken(tokenPair, username);
//
// User currentUser = readResponse(doGet("/api/auth/user")
// .andExpect(status().isOk()), User.class );
// assertThat(currentUser.getId()).isEqualTo(user.getId());
// }
//
// @Test
// public void testTwoFa_sms() throws Exception {
// configureSmsTwoFa();
//
// logInWithPreVerificationToken();
//
// doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isOk());
//
// ArgumentCaptor<String> verificationCodeCaptor = ArgumentCaptor.forClass(String.class);
// verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture());
// String correctVerificationCode = verificationCodeCaptor.getValue();
//
// JsonNode tokenPair = readResponse(doPost( "/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode)
// .andExpect(status().isOk()), JsonNode.class );
// validateAndSetJwtToken(tokenPair, username);
//
// User currentUser = readResponse(doGet("/api/auth/user")
// .andExpect(status().isOk()), User.class );
// assertThat(currentUser.getId()).isEqualTo(user.getId());
// }
//
// @Test
// public void testTwoFaPreVerificationTokenLifetime() throws Exception {
// configureTotpTwoFa(twoFaSettings -> {
// twoFaSettings.setTotalAllowedTimeForVerification(5);
// });
//
// logInWithPreVerificationToken();
//
// await( "expiration of the pre-verification token")
// .atLeast(Duration.ofSeconds(3).plusMillis(500))
// .atMost(Duration.ofSeconds(6))
// .untilAsserted(() -> {
// doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isUnauthorized());
// });
// }
//
// @Test
// public void testCheckVerificationCode_userBlocked() throws Exception {
// configureTotpTwoFa(twoFaSettings -> {
// twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
// });
//
// logInWithPreVerificationToken();
//
// Stream.generate(() -> RandomStringUtils.randomNumeric(6))
// .limit(9)
// .forEach(incorrectVerificationCode -> {
// try {
// String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + incorrectVerificationCode)
// .andExpect(status().isBadRequest()));
// assertThat(errorMessage).containsIgnoringCase("verification code is incorrect");
// } catch (Exception e) {
// fail();
// }
// });
//
// String errorMessage = getErrorMessage(doPost( "/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6))
// .andExpect(status().isUnauthorized()));
// assertThat(errorMessage).containsIgnoringCase( "account was locked due to exceeded 2fa verification attempts");
//
// errorMessage = getErrorMessage(doPost( "/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6))
// .andExpect(status().isUnauthorized()));
// assertThat(errorMessage).containsIgnoringCase("user is disabled");
// }
//
// @Test
// public void testSendVerificationCode_rateLimit() throws Exception {
// configureTotpTwoFa(twoFaSettings -> {
// twoFaSettings.setVerificationCodeSendRateLimit("3:10");
// });
//
// logInWithPreVerificationToken();
//
// for (int i = 0; i < 3; i++) {
// doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isOk());
// }
//
// String rateLimitExceededError = getErrorMessage(doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isTooManyRequests()));
// assertThat(rateLimitExceededError).containsIgnoringCase( "too many verification code sending requests");
//
// await( "verification code sending rate limit resetting")
// .atLeast(Duration.ofSeconds(8))
// .atMost(Duration.ofSeconds(12))
// .untilAsserted(() -> {
// doPost("/api/auth/2fa/verification/send")
// .andExpect(status().isOk());
// });
// }
//
// @Test
// public void testCheckVerificationCode_rateLimit() throws Exception {
// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(twoFaSettings -> {
// twoFaSettings.setVerificationCodeCheckRateLimit("3:10");
// });
//
// logInWithPreVerificationToken();
//
// for (int i = 0; i < 3; i++) {
// String incorrectVerificationCodeError = getErrorMessage(doPost( "/api/auth/2fa/verification/check?verificationCode=incorrect")
// .andExpect(status().isBadRequest()));
// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect");
// }
//
// String rateLimitExceededError = getErrorMessage(doPost( "/api/auth/2fa/verification/check?verificationCode=incorrect")
// .andExpect(status().isTooManyRequests()));
// assertThat(rateLimitExceededError).containsIgnoringCase( "too many verification code checking requests");
//
// await( "verification code checking rate limit resetting")
// .atLeast(Duration.ofSeconds(8))
// .atMost(Duration.ofSeconds(12))
// .untilAsserted(() -> {
// String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect")
// .andExpect(status().isBadRequest()));
// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect");
// });
//
// doPost( "/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig))
// .andExpect(status().isOk());
// }
//
// @Test
// public void testCheckVerificationCode_invalidVerificationCode() throws Exception {
// configureTotpTwoFa();
// logInWithPreVerificationToken();
//
// for (String invalidVerificationCode : new String[]{"1234567", "ab1212", "12311 ", "oewkriwejqf"}) {
// String errorMessage = getErrorMessage(doPost( "/api/auth/2fa/verification/check?verificationCode=" + invalidVerificationCode)
// .andExpect(status().isBadRequest()));
// assertThat(errorMessage).containsIgnoringCase("verification code is incorrect");
// }
// }
//
// @Test
// public void testCheckVerificationCode_codeExpiration() throws Exception {
// configureSmsTwoFa(smsTwoFaProviderConfig -> {
// smsTwoFaProviderConfig.setVerificationCodeLifetime(10);
// });
//
// logInWithPreVerificationToken();
//
// ArgumentCaptor<String> verificationCodeCaptor = ArgumentCaptor.forClass(String.class);
// doPost("/api/auth/2fa/verification/send").andExpect(status().isOk());
// verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture());
//
// String correctVerificationCode = verificationCodeCaptor.getValue();
//
// await("verification code expiration")
// .pollDelay(10, TimeUnit.SECONDS)
// .atLeast(10, TimeUnit.SECONDS)
// .atMost(12, TimeUnit.SECONDS)
// .untilAsserted(() -> {
// String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + c orrectVerificationCode)
// .andExpect(status().isBadRequest()));
// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect");
// });
// }
//
// @Test
// public void testTwoFa_logLoginAction() throws Exception {
// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa();
//
// logInWithPreVerificationToken();
// await("async audit log saving").during(1, TimeUnit.SECONDS);
// assertThat(getLogInAuditLogs()).isEmpty();
// assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo()
// .get("lastLoginTs")).isNull();
//
// doPost( "/api/auth/2fa/verification/check?verificationCode=incorrect")
// .andExpect(status().isBadRequest());
//
// await("async audit log saving").atMost(1, TimeUnit.SECONDS)
// .until(() -> getLogInAuditLogs().size() = = 1);
// assertThat(getLogInAuditLogs().get(0)).satisfies(failedLogInAuditLog -> {
// assertThat(failedLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.FAILURE);
// assertThat(failedLogInAuditLog.getActionFailureDetails()).containsIgnoringCase("verification code is incorrect");
// assertThat(failedLogInAuditLog.getUserName()).isEqualTo(username);
// });
//
// doPost( "/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig))
// .andExpect(status().isOk());
// await("async audit log saving").atMost(1, TimeUnit.SECONDS)
// .until(() -> getLogInAuditLogs().size() = = 2);
// assertThat(getLogInAuditLogs().get(0)).satisfies(successfulLogInAuditLog -> {
// assertThat(successfulLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.SUCCESS);
// assertThat(successfulLogInAuditLog.getUserName()).isEqualTo(username);
// });
// assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo()
// .get("lastLoginTs").asLong())
// .isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3));
// }
//
// private List<AuditLog> getLogInAuditLogs() {
// return auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, user.getId(), List.of(ActionType.LOGIN),
// new TimePageLink(new PageLink(10, 0, null, new SortOrder("createdTime", SortOrder.Direction.DESC)), 0L, System.currentTimeMillis())).getData();
// }
//
// @Test
// public void testAuthWithoutTwoFaAccountConfig() throws ThingsboardException {
// configureTotpTwoFa();
// twoFaConfigManager.deleteTwoFaAccountConfig(tenantId, user.getId(), );
//
// assertDoesNotThrow(() -> {
// login(username, password);
// });
// }
//
// private void logInWithPreVerificationToken() throws Exception {
// LoginRequest loginRequest = new LoginRequest(username, password);
//
// JwtTokenPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtTokenPair.class);
// assertThat(response.getToken()).isNotNull();
// assertThat(response.getRefreshToken()).isNull();
// assertThat(response.getScope()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN);
//
// this.token = response.getToken();
// }
//
// private TotpTwoFaAccountConfig configureTotpTwoFa(Consumer<PlatformTwoFaSettings>... customizer) throws ThingsboardException {
// TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig();
// totpTwoFaProviderConfig.setIssuerName("tb");
//
// PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
// twoFaSettings.setUseSystemTwoFactorAuthSettings(false);
// twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList()));
// Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings));
// twoFaConfigManager.savePlatformTwoFaSettings(tenantId, twoFaSettings);
//
// TotpTwoFaAccountConfig totpTwoFaAccountConfig = (TotpTwoFaAccountConfig) twoFactorAuthService.generateNewAccountConfig(user, TwoFaProviderType.TOTP);
// twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), totpTwoFaAccountConfig);
// return totpTwoFaAccountConfig;
// }
//
// private SmsTwoFaAccountConfig configureSmsTwoFa(Consumer<SmsTwoFaProviderConfig>... customizer) throws ThingsboardException {
// SmsTwoFaProviderConfig smsTwoFaProviderConfig = new SmsTwoFaProviderConfig();
// smsTwoFaProviderConfig.setVerificationCodeLifetime(60);
// smsTwoFaProviderConfig.setSmsVerificationMessageTemplate("${verificationCode}");
// Arrays.stream(customizer).forEach(c -> c.accept(smsTwoFaProviderConfig));
//
// PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
// twoFaSettings.setUseSystemTwoFactorAuthSettings(false);
// twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList()));
// twoFaConfigManager.savePlatformTwoFaSettings(tenantId, twoFaSettings);
//
// SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig();
// smsTwoFaAccountConfig.setPhoneNumber("+38050505050");
// twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), smsTwoFaAccountConfig);
// return smsTwoFaAccountConfig;
// }
//
// private String getCorrectTotp(TotpTwoFaAccountConfig totpTwoFaAccountConfig) {
// String secret = StringUtils.substringAfterLast(totpTwoFaAccountConfig.getAuthUrl(), "secret=");
// return new Totp(secret).now();
// }
}