diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 829d6c49b4..527c4cb6de 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -127,7 +127,7 @@ export class AuthService { public checkTwoFaVerificationCode(providerType: TwoFactorAuthProviderType, verificationCode: number): Observable { return this.http.post(`/api/auth/2fa/verification/check?providerType=${providerType}&verificationCode=${verificationCode}`, - null, defaultHttpOptions()).pipe( + null, defaultHttpOptions(false, true)).pipe( tap((loginResponse: LoginResponse) => { this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); } diff --git a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html index b59139b36a..f6592e686e 100644 --- a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html @@ -46,9 +46,18 @@ [attr.inputmode]="inputMode" [pattern]="pattern" autocomplete="off" placeholder="{{ providersData.get(selectedProvider).placeholder | translate }}"/> - + {{ 'security.2fa.dialog.verification-code-invalid' | translate }} + + {{ 'security.2fa.dialog.verification-code-incorrect' | translate }} + + + {{ 'security.2fa.dialog.verification-code-many-request' | translate }} + @@ -59,12 +68,12 @@ {{ 'action.continue' | translate }} -
-
+
+
{{ 'login.resend-code-wait' | translate : {time: countDownTime} }}
-
diff --git a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss index 547b3b88a6..248d719b7b 100644 --- a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss @@ -60,6 +60,14 @@ font: 500 12px / 14px Roboto, "Helvetica Neue", sans-serif; color: rgba(255, 255, 255, 0.8); } + + .action-row:nth-child(n) { + min-height: 36px; + + .action-resend { + min-width: 50%; + } + } } } diff --git a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.ts b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.ts index 33ebd4d979..048fb83bbe 100644 --- a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.ts @@ -28,6 +28,7 @@ import { } from '@shared/models/two-factor-auth.models'; import { TranslateService } from '@ngx-translate/core'; import { interval, Subscription } from 'rxjs'; +import { isEqual } from '@core/utils'; @Component({ selector: 'tb-two-factor-auth-login', @@ -40,14 +41,15 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit private prevProvider: TwoFactorAuthProviderType; private timer: Subscription; private minVerificationPeriod = 0; + private timerID: NodeJS.Timeout; - showResendButton = false; + showResendAction = false; selectedProvider: TwoFactorAuthProviderType; allowProviders: TwoFactorAuthProviderType[] = []; providersData = twoFactorAuthProvidersLoginData; providerDescription = ''; - disabledResendButton = true; + hideResendButton = true; countDownTime = 0; maxLengthInput = 6; @@ -88,7 +90,7 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit }); if (this.selectedProvider !== TwoFactorAuthProviderType.TOTP) { this.sendCode(); - this.showResendButton = true; + this.showResendAction = true; } this.timer = interval(1000).subscribe(() => this.updatedTime()); } @@ -96,12 +98,28 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit ngOnDestroy() { super.ngOnDestroy(); this.timer.unsubscribe(); + clearTimeout(this.timerID); } sendVerificationCode() { if (this.verificationForm.valid && this.selectedProvider) { this.authService.checkTwoFaVerificationCode(this.selectedProvider, this.verificationForm.get('verificationCode').value).subscribe( - () => {} + () => {}, + (error) => { + if (error.status === 400) { + this.verificationForm.get('verificationCode').setErrors({incorrectCode: true}); + } else if (error.status === 429) { + this.verificationForm.get('verificationCode').setErrors({tooManyRequest: true}); + this.timerID = setTimeout(() => { + let errors = this.verificationForm.get('verificationCode').errors; + delete errors.tooManyRequest; + if (isEqual(errors, {})) { + errors = null; + } + this.verificationForm.get('verificationCode').setErrors(errors); + }, 5000); + } + } ); } } @@ -109,7 +127,7 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit selectProvider(type: TwoFactorAuthProviderType) { this.prevProvider = type === null ? this.selectedProvider : null; this.selectedProvider = type; - this.showResendButton = false; + this.showResendAction = false; if (type !== null) { this.verificationForm.get('verificationCode').reset(); const providerConfig = this.providersInfo.find(config => config.type === type); @@ -118,7 +136,7 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit }); if (type !== TwoFactorAuthProviderType.TOTP && type !== TwoFactorAuthProviderType.BACKUP_CODE) { this.sendCode(); - this.showResendButton = true; + this.showResendAction = true; this.minVerificationPeriod = providerConfig?.minVerificationCodeSendPeriod || 30; } if (type === TwoFactorAuthProviderType.BACKUP_CODE) { @@ -150,7 +168,7 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit if ($event) { $event.stopPropagation(); } - this.disabledResendButton = true; + this.hideResendButton = true; this.twoFactorAuthService.requestTwoFaVerificationCodeSend(this.selectedProvider).subscribe(() => { this.countDownTime = this.minVerificationPeriod; }, () => { @@ -171,7 +189,7 @@ export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit if (this.countDownTime > 0) { this.countDownTime--; if (this.countDownTime === 0) { - this.disabledResendButton = false; + this.hideResendButton = false; } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a316993329..25f27ed346 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2633,6 +2633,8 @@ "totp-step-label": "Get app", "verification-code": "6-digit code", "verification-code-invalid": "Invalid verification code format", + "verification-code-incorrect": "Verification code is incorrect", + "verification-code-many-request": "Too many requests check verification code", "verification-step-description": "Enter a 6-digit code we just sent to {{address}}", "verification-step-label": "Verification" },