WIP
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<div class="mfa-dialog-overlay" *ngIf="visible" (click)="close()">
|
||||
<div class="mfa-dialog-container" (click)="$event.stopPropagation()">
|
||||
<!-- Background Elements -->
|
||||
<div class="dialog-background-shapes">
|
||||
<div class="shape shape-1"></div>
|
||||
<div class="shape shape-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog Content -->
|
||||
<div class="mfa-dialog-content">
|
||||
<!-- Header -->
|
||||
<div class="mfa-header">
|
||||
<button class="close-button" (click)="close()" title="Close">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="header-content">
|
||||
<div class="mfa-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||
<circle cx="12" cy="16" r="1"></circle>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Two-Factor Authentication</h3>
|
||||
<p>Enter the 6-digit code sent to your device</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MFA Code Input -->
|
||||
<div class="mfa-code-section">
|
||||
<div class="code-inputs-container">
|
||||
<ng-container *ngFor="let code of userInputCodes2; let i = index">
|
||||
<input #codeInput type="number" min="0" max="9" maxlength="1" class="mfa-input"
|
||||
[(ngModel)]="userInputCodes2[i]" name="n{{i+1}}" (keydown)="onKeyDown(i,$event)"
|
||||
(paste)="pasteCode(i,$event)" [autofocus]="i==0"
|
||||
[attr.aria-label]="'MFA code digit ' + (i+1)" title="Enter MFA code digit {{i+1}}">
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div *ngIf="isInvalidCode" class="error-message">
|
||||
<svg class="error-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="15" y1="9" x2="9" y2="15"></line>
|
||||
<line x1="9" y1="9" x2="15" y2="15"></line>
|
||||
</svg>
|
||||
Invalid code. Please try again.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mfa-actions">
|
||||
<button kendoButton type="button" themeColor="secondary" size="medium" (click)="resendMFCode()"
|
||||
[disabled]="processing || resendCountDown > 0" class="resend-button">
|
||||
<span class="button-content">
|
||||
<svg class="button-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23,4 23,10 17,10"></polyline>
|
||||
<polyline points="1,20 1,14 7,14"></polyline>
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
|
||||
</svg>
|
||||
{{ resendCodeText }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button kendoButton type="button" themeColor="primary" size="large" (click)="submitCode()"
|
||||
[disabled]="processing || !allowSubmit" class="verify-button">
|
||||
<span class="button-content">
|
||||
<kendo-loader *ngIf="processing" size="small"></kendo-loader>
|
||||
<svg *ngIf="!processing" class="button-icon" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
{{ processing ? 'Verifying...' : 'Verify Code' }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="help-text">
|
||||
<p>Didn't receive the code? Check your spam folder or try resending.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,418 @@
|
||||
.mfa-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.mfa-dialog-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: blur(20px);
|
||||
overflow: hidden;
|
||||
animation: dialogSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes dialogSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Background Shapes
|
||||
.dialog-background-shapes {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.shape {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(30, 64, 175, 0.05);
|
||||
animation: float 8s ease-in-out infinite;
|
||||
|
||||
&.shape-1 {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
top: 15%;
|
||||
right: 10%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
&.shape-2 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
bottom: 20%;
|
||||
left: 15%;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-15px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog Content
|
||||
.mfa-dialog-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
// Header
|
||||
.mfa-header {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
background: #f8f9fa;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #6b7280;
|
||||
|
||||
&:hover {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-content {
|
||||
.mfa-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto 1rem;
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(30, 58, 138, 0.3);
|
||||
|
||||
svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 0.5rem 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #6b7280;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MFA Code Section
|
||||
.mfa-code-section {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.code-inputs-container {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.mfa-input {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
background: #ffffff;
|
||||
transition: all 0.2s ease;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
border-color: #1e40af;
|
||||
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover:not(:focus) {
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #fecaca;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
animation: errorShake 0.5s ease-in-out;
|
||||
|
||||
.error-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes errorShake {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.mfa-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.resend-button {
|
||||
flex: 1;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e5e7eb;
|
||||
color: #6b7280;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #e9ecef;
|
||||
border-color: #d1d5db;
|
||||
color: #495057;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.button-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.verify-button {
|
||||
flex: 2;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(30, 58, 138, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 35px rgba(30, 58, 138, 0.4);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.button-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Help Text
|
||||
.help-text {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
color: #9ca3af;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile Responsive
|
||||
@media (max-width: 480px) {
|
||||
.mfa-dialog-overlay {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.mfa-dialog-container {
|
||||
max-width: 100%;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.mfa-dialog-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.mfa-header {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.header-content {
|
||||
.mfa-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mfa-code-section {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.code-inputs-container {
|
||||
gap: 0.5rem;
|
||||
|
||||
.mfa-input {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mfa-actions {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
|
||||
.resend-button,
|
||||
.verify-button {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.verify-button {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.mfa-dialog-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.code-inputs-container {
|
||||
gap: 0.4rem;
|
||||
|
||||
.mfa-input {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import { Component, ElementRef, QueryList, ViewChildren, Output, EventEmitter, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||
import { IndicatorsModule } from '@progress/kendo-angular-indicators';
|
||||
import { AuthService, LoginCredentials, LoginResultType } from '../services/auth.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
const CODE_LENGTH = 6;
|
||||
|
||||
@Component({
|
||||
selector: 'app-mfa-dialog',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ButtonsModule,
|
||||
IndicatorsModule
|
||||
],
|
||||
templateUrl: './mfa-dialog.component.html',
|
||||
styleUrls: ['./mfa-dialog.component.scss']
|
||||
})
|
||||
export class MfaDialogComponent {
|
||||
@ViewChildren('codeInput') codeInputs!: QueryList<ElementRef>;
|
||||
@Output() mfaSuccess = new EventEmitter<any>();
|
||||
@Output() mfaCancel = new EventEmitter<void>();
|
||||
@Input() visible = false;
|
||||
|
||||
token: string = '';
|
||||
userInputCodes: (string | null)[] = [];
|
||||
userInputCodes2: (string | null)[] = [];
|
||||
loginData!: LoginCredentials;
|
||||
processing = false;
|
||||
allowSubmit = false;
|
||||
isInvalidCode = false;
|
||||
resendCountDown = 30;
|
||||
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
ngOnInit() {
|
||||
for (let i = 0; i < CODE_LENGTH; i++) {
|
||||
this.userInputCodes.push(null);
|
||||
this.userInputCodes2.push(null);
|
||||
}
|
||||
this.setReSendCountDown();
|
||||
}
|
||||
|
||||
pasteCode(index: number, event: ClipboardEvent) {
|
||||
event.preventDefault();
|
||||
const data = event.clipboardData?.getData('text/plain') || '';
|
||||
let pasteCode = data.replace(new RegExp("[^0-9]", 'g'), "");
|
||||
for (let i = index; i < CODE_LENGTH; i++) {
|
||||
if (pasteCode.length > i) {
|
||||
const code = pasteCode[i];
|
||||
let input = this.codeInputs.find((element, j) => j === i);
|
||||
if (input) {
|
||||
input.nativeElement.value = code;
|
||||
this.userInputCodes[i] = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.validate(5);
|
||||
}
|
||||
|
||||
public onKeyDown(index: number, e: KeyboardEvent): void {
|
||||
const el: HTMLInputElement = e.target as HTMLInputElement;
|
||||
if (e.ctrlKey && e.key.toUpperCase() == 'V') {
|
||||
return;
|
||||
} else {
|
||||
let nextFocusInput: ElementRef | undefined = undefined;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
nextFocusInput = this.getInputElements(index, -1);
|
||||
if (nextFocusInput) nextFocusInput.nativeElement.focus();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
nextFocusInput = this.getInputElements(index, 1);
|
||||
if (nextFocusInput) nextFocusInput.nativeElement.focus();
|
||||
break;
|
||||
case 'Backspace':
|
||||
if (el.value) {
|
||||
el.value = '';
|
||||
} else {
|
||||
nextFocusInput = this.getInputElements(index, -1);
|
||||
}
|
||||
break;
|
||||
case 'Delete':
|
||||
el.value = '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextFocusInput) {
|
||||
nextFocusInput.nativeElement.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
let isReplacing = el.selectionStart != el.selectionEnd;
|
||||
|
||||
if (this.isEditingKeyPress(e)) {
|
||||
e.preventDefault();
|
||||
if (new RegExp("[0-9]", 'g').test(e.key)) {
|
||||
this.userInputCodes[index] = e.key;
|
||||
el.value = e.key;
|
||||
let nextInput = this.getInputElements(index, 1);
|
||||
if (nextInput) nextInput.nativeElement.focus();
|
||||
} else {
|
||||
el.value = '';
|
||||
this.userInputCodes[index] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isInvalidCode = false;
|
||||
this.validate(index);
|
||||
}
|
||||
|
||||
private getInputElements(index: number, indexOffset: number): ElementRef | undefined {
|
||||
let nextInput = this.codeInputs.find((element, i) => i === (index + indexOffset));
|
||||
return nextInput;
|
||||
}
|
||||
|
||||
private isEditingKeyPress(e: KeyboardEvent): boolean {
|
||||
return e.key.length === 1 || e.key === 'Backspace' || e.key === 'Delete' || e.key === 'ArrowLeft' || e.key === 'ArrowRight';
|
||||
}
|
||||
|
||||
validate(focusIndex: number) {
|
||||
this.allowSubmit = !this.userInputCodes.some(n => n == null);
|
||||
this.token = this.userInputCodes.map(n => n != null ? n : '').join('');
|
||||
if (this.token && this.token.length == 6 && focusIndex == 5) {
|
||||
this.submitCode();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visible = false;
|
||||
this.mfaCancel.emit();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true;
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.userInputCodes = [];
|
||||
this.userInputCodes2 = [];
|
||||
this.token = '';
|
||||
this.isInvalidCode = false;
|
||||
this.processing = false;
|
||||
this.allowSubmit = false;
|
||||
|
||||
for (let i = 0; i < CODE_LENGTH; i++) {
|
||||
this.userInputCodes.push(null);
|
||||
this.userInputCodes2.push(null);
|
||||
}
|
||||
|
||||
// Focus on first input
|
||||
setTimeout(() => {
|
||||
const firstInput = document.querySelector('.mfa-input:first-child') as HTMLInputElement;
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
submitCode() {
|
||||
this.processing = true;
|
||||
this.loginData.mfaCode = this.token;
|
||||
|
||||
// Check if this is token-based authentication
|
||||
if ((this.loginData as any).tokenUser) {
|
||||
// Handle token-based MFA verification
|
||||
this.authService.verifyMfaForToken(this.token, (this.loginData as any).tokenUser).subscribe({
|
||||
next: (result) => {
|
||||
this.processing = false;
|
||||
|
||||
if (result.result === LoginResultType.Success) {
|
||||
this.mfaSuccess.emit(result.responseData);
|
||||
this.visible = false;
|
||||
} else {
|
||||
this.isInvalidCode = true;
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
this.processing = false;
|
||||
this.isInvalidCode = true;
|
||||
console.error('MFA verification error:', error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Handle regular login MFA verification
|
||||
this.authService.login(this.loginData).subscribe({
|
||||
next: (result) => {
|
||||
this.processing = false;
|
||||
|
||||
if (result.result === LoginResultType.Success) {
|
||||
this.mfaSuccess.emit(result.responseData);
|
||||
this.visible = false;
|
||||
} else {
|
||||
this.isInvalidCode = true;
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
this.processing = false;
|
||||
this.isInvalidCode = true;
|
||||
console.error('MFA verification error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setReSendCountDown() {
|
||||
if (this.resendCountDown > 0) {
|
||||
setTimeout(() => {
|
||||
this.resendCountDown--;
|
||||
this.setReSendCountDown();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
resendMFCode() {
|
||||
this.resendCountDown = 30;
|
||||
this.loginData.mfaCode = '';
|
||||
|
||||
// Simulate resend MFA code - replace with actual service call
|
||||
console.log('Resending MFA code to:', this.loginData.email);
|
||||
// Check if this is token-based authentication
|
||||
if ((this.loginData as any).tokenUser) {
|
||||
// Handle token-based MFA verification
|
||||
this.authService.verifyMfaForToken(this.token, (this.loginData as any).tokenUser).pipe(
|
||||
take(1)
|
||||
).subscribe(result => {
|
||||
this.setReSendCountDown();
|
||||
});
|
||||
} else {
|
||||
//TODO: Implement resend MFA code for regular login
|
||||
}
|
||||
this.setReSendCountDown();
|
||||
}
|
||||
|
||||
public get resendCodeText(): string {
|
||||
return 'Resend Code' + (this.resendCountDown > 0 ? ` (${this.resendCountDown})` : '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user