mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 05:30:01 +00:00
WIP store extension state to cache
This commit is contained in:
@@ -2,6 +2,7 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
|
||||
import { TwoFactorFormCacheServiceAbstraction } from "@bitwarden/auth/angular";
|
||||
import {
|
||||
TwoFactorProviderDetails,
|
||||
TwoFactorService,
|
||||
@@ -22,6 +23,7 @@ export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private twoFactorFormCacheService: TwoFactorFormCacheServiceAbstraction,
|
||||
) {
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
|
||||
}
|
||||
@@ -34,6 +36,14 @@ export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponent {
|
||||
await super.choose(p);
|
||||
await this.twoFactorService.setSelectedProvider(p.type);
|
||||
|
||||
const persistedData = await this.twoFactorFormCacheService.getFormData();
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: persistedData?.token || undefined,
|
||||
remember: persistedData?.remember ?? undefined,
|
||||
selectedProviderType: p.type,
|
||||
emailSent: false,
|
||||
});
|
||||
|
||||
this.navigateTo2FA();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
LoginEmailServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { TwoFactorFormCacheServiceAbstraction } from "@bitwarden/auth/angular";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
@@ -67,6 +68,7 @@ export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnIn
|
||||
toastService: ToastService,
|
||||
@Inject(WINDOW) protected win: Window,
|
||||
private browserMessagingApi: ZonedMessageListenerService,
|
||||
private twoFactorFormCacheService: TwoFactorFormCacheServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
loginStrategyService,
|
||||
@@ -127,6 +129,20 @@ export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnIn
|
||||
return;
|
||||
}
|
||||
|
||||
// Load form data from cache if available
|
||||
const persistedData = await this.twoFactorFormCacheService.getFormData();
|
||||
if (persistedData) {
|
||||
if (persistedData.token) {
|
||||
this.token = persistedData.token;
|
||||
}
|
||||
if (persistedData.remember !== undefined) {
|
||||
this.remember = persistedData.remember;
|
||||
}
|
||||
if (persistedData.selectedProviderType !== undefined) {
|
||||
this.selectedProviderType = persistedData.selectedProviderType;
|
||||
}
|
||||
}
|
||||
|
||||
await super.ngOnInit();
|
||||
if (this.selectedProviderType == null) {
|
||||
return;
|
||||
@@ -187,7 +203,15 @@ export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnIn
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
anotherMethod() {
|
||||
async anotherMethod() {
|
||||
// Save form data to cache before navigating to another method
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: this.token,
|
||||
remember: this.remember,
|
||||
selectedProviderType: this.selectedProviderType,
|
||||
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
|
||||
});
|
||||
|
||||
const sso = this.route.snapshot.queryParamMap.get("sso") === "true";
|
||||
|
||||
if (sso) {
|
||||
@@ -257,4 +281,25 @@ export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnIn
|
||||
encodeURIComponent(JSON.stringify(duoHandOffMessage));
|
||||
this.platformUtilsService.launchUri(launchUrl);
|
||||
}
|
||||
|
||||
// Override the submit method to persist form data to cache before submitting
|
||||
async submit() {
|
||||
// Persist form data before submitting
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: this.token,
|
||||
remember: this.remember,
|
||||
selectedProviderType: this.selectedProviderType,
|
||||
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
|
||||
});
|
||||
|
||||
await super.submit();
|
||||
}
|
||||
|
||||
// Override the doSubmit to clear cached data on successful login
|
||||
async doSubmit() {
|
||||
await super.doSubmit();
|
||||
|
||||
// Clear cached data on successful login
|
||||
await this.twoFactorFormCacheService.clearFormData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Observable, from, of, switchMap } from "rxjs";
|
||||
|
||||
import { TwoFactorFormCacheServiceAbstraction, TwoFactorFormData } from "@bitwarden/auth/angular";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
||||
|
||||
|
||||
const STORAGE_KEY = "twoFactorFormData";
|
||||
|
||||
export class ExtensionTwoFactorFormCacheService implements TwoFactorFormCacheServiceAbstraction {
|
||||
constructor(
|
||||
private storageService: AbstractStorageService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
isEnabled$(): Observable<boolean> {
|
||||
return from(this.configService.getFeatureFlag(FeatureFlag.PM9115_TwoFactorFormPersistence));
|
||||
}
|
||||
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return await this.configService.getFeatureFlag(FeatureFlag.PM9115_TwoFactorFormPersistence);
|
||||
}
|
||||
|
||||
formData$(): Observable<TwoFactorFormData | null> {
|
||||
return this.isEnabled$().pipe(
|
||||
switchMap((enabled) => {
|
||||
if (!enabled) {
|
||||
return of(null);
|
||||
}
|
||||
return from(this.storageService.get<TwoFactorFormData>(STORAGE_KEY));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async saveFormData(data: TwoFactorFormData): Promise<void> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.storageService.save(STORAGE_KEY, data);
|
||||
}
|
||||
|
||||
async getFormData(): Promise<TwoFactorFormData | null> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.storageService.get<TwoFactorFormData>(STORAGE_KEY);
|
||||
}
|
||||
|
||||
async clearFormData(): Promise<void> {
|
||||
await this.storageService.remove(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable, from, of, switchMap } from "rxjs";
|
||||
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
||||
|
||||
export interface TwoFactorFormData {
|
||||
token?: string;
|
||||
remember?: boolean;
|
||||
selectedProviderType?: TwoFactorProviderType;
|
||||
emailSent?: boolean;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "twoFactorFormData";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class ExtensionTwoFactorFormCacheService {
|
||||
constructor(
|
||||
private storageService: AbstractStorageService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
isEnabled$(): Observable<boolean> {
|
||||
return from(this.configService.getFeatureFlag(FeatureFlag.PM9115_TwoFactorFormPersistence));
|
||||
}
|
||||
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return await this.configService.getFeatureFlag(FeatureFlag.PM9115_TwoFactorFormPersistence);
|
||||
}
|
||||
|
||||
formData$(): Observable<TwoFactorFormData | null> {
|
||||
return this.isEnabled$().pipe(
|
||||
switchMap((enabled) => {
|
||||
if (!enabled) {
|
||||
return of(null);
|
||||
}
|
||||
return from(this.storageService.get<TwoFactorFormData>(STORAGE_KEY));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async saveFormData(data: TwoFactorFormData): Promise<void> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.storageService.save(STORAGE_KEY, data);
|
||||
}
|
||||
|
||||
async getFormData(): Promise<TwoFactorFormData | null> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.storageService.get<TwoFactorFormData>(STORAGE_KEY);
|
||||
}
|
||||
|
||||
async clearFormData(): Promise<void> {
|
||||
await this.storageService.remove(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,8 @@ import { VaultFilterService } from "../../vault/services/vault-filter.service";
|
||||
import { DebounceNavigationService } from "./debounce-navigation.service";
|
||||
import { InitService } from "./init.service";
|
||||
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
||||
import { TwoFactorFormCacheServiceAbstraction } from "@bitwarden/auth/angular";
|
||||
import { ExtensionTwoFactorFormCacheService } from "../../auth/services/extension-two-factor-form-cache.service";
|
||||
|
||||
const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken<
|
||||
AbstractStorageService & ObservableStorageService
|
||||
@@ -557,6 +559,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ExtensionTwoFactorAuthWebAuthnComponentService,
|
||||
deps: [PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TwoFactorFormCacheServiceAbstraction,
|
||||
useClass: ExtensionTwoFactorFormCacheService,
|
||||
deps: [AbstractStorageService, ConfigService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TwoFactorAuthDuoComponentService,
|
||||
useClass: ExtensionTwoFactorAuthDuoComponentService,
|
||||
@@ -650,6 +657,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ExtensionLoginDecryptionOptionsService,
|
||||
deps: [MessagingServiceAbstraction, Router],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TwoFactorFormCacheServiceAbstraction,
|
||||
useClass: ExtensionTwoFactorFormCacheService,
|
||||
deps: [AbstractStorageService, ConfigService],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./two-factor-form-cache.service.abstraction";
|
||||
@@ -0,0 +1,36 @@
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
|
||||
/**
|
||||
* Interface for two-factor form data
|
||||
*/
|
||||
interface TwoFactorFormData {
|
||||
token?: string;
|
||||
remember?: boolean;
|
||||
selectedProviderType?: TwoFactorProviderType;
|
||||
emailSent?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract service for two-factor form caching
|
||||
*/
|
||||
export abstract class TwoFactorFormCacheServiceAbstraction {
|
||||
/**
|
||||
* Check if the form persistence feature is enabled
|
||||
*/
|
||||
abstract isEnabled(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Save form data to persistent storage
|
||||
*/
|
||||
abstract saveFormData(data: TwoFactorFormData): Promise<void>;
|
||||
|
||||
/**
|
||||
* Retrieve form data from persistent storage
|
||||
*/
|
||||
abstract getFormData(): Promise<TwoFactorFormData | null>;
|
||||
|
||||
/**
|
||||
* Clear form data from persistent storage
|
||||
*/
|
||||
abstract clearFormData(): Promise<void>;
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
<ng-container>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
|
||||
<input bitInput type="text" appAutofocus appInputVerbatim [formControl]="tokenFormControl" />
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
[formControl]="tokenFormControl"
|
||||
(keyup)="onTokenChange($event)"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -32,4 +32,10 @@ import {
|
||||
})
|
||||
export class TwoFactorAuthAuthenticatorComponent {
|
||||
@Input({ required: true }) tokenFormControl: FormControl | undefined = undefined;
|
||||
@Output() tokenChange = new EventEmitter<{ token: string }>();
|
||||
|
||||
onTokenChange(event: Event) {
|
||||
const tokenValue = (event.target as HTMLInputElement).value || "";
|
||||
this.tokenChange.emit({ token: tokenValue });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<bit-form-field class="!tw-mb-0">
|
||||
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
|
||||
<input bitInput type="text" appAutofocus appInputVerbatim [formControl]="tokenFormControl" />
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
[formControl]="tokenFormControl"
|
||||
(keyup)="onTokenChange($event)"
|
||||
/>
|
||||
</bit-form-field>
|
||||
|
||||
<div class="tw-mb-4">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { TwoFactorFormCacheServiceAbstraction } from "../../abstractions/two-factor-form-cache.service.abstraction";
|
||||
import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service";
|
||||
|
||||
@Component({
|
||||
@@ -44,10 +45,10 @@ import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-comp
|
||||
})
|
||||
export class TwoFactorAuthEmailComponent implements OnInit {
|
||||
@Input({ required: true }) tokenFormControl: FormControl | undefined = undefined;
|
||||
@Output() tokenChange = new EventEmitter<{ token: string }>();
|
||||
|
||||
twoFactorEmail: string | undefined = undefined;
|
||||
emailPromise: Promise<any> | undefined = undefined;
|
||||
tokenValue: string = "";
|
||||
emailPromise: Promise<any> | undefined;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@@ -59,6 +60,7 @@ export class TwoFactorAuthEmailComponent implements OnInit {
|
||||
protected appIdService: AppIdService,
|
||||
private toastService: ToastService,
|
||||
private twoFactorAuthEmailComponentService: TwoFactorAuthEmailComponentService,
|
||||
private twoFactorFormCacheService: TwoFactorFormCacheServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -78,11 +80,22 @@ export class TwoFactorAuthEmailComponent implements OnInit {
|
||||
|
||||
this.twoFactorEmail = email2faProviderData.Email;
|
||||
|
||||
if (providers.size > 1) {
|
||||
// Check if email has already been sent according to the cache
|
||||
let cachedData;
|
||||
if (this.twoFactorFormCacheService) {
|
||||
cachedData = await this.twoFactorFormCacheService.getFormData();
|
||||
}
|
||||
|
||||
if (providers.size > 1 && !cachedData?.emailSent) {
|
||||
await this.sendEmail(false);
|
||||
}
|
||||
}
|
||||
|
||||
onTokenChange(event: Event) {
|
||||
const tokenValue = (event.target as HTMLInputElement).value || "";
|
||||
this.tokenChange.emit({ token: tokenValue });
|
||||
}
|
||||
|
||||
async sendEmail(doToast: boolean) {
|
||||
if (this.emailPromise !== undefined) {
|
||||
return;
|
||||
@@ -113,6 +126,16 @@ export class TwoFactorAuthEmailComponent implements OnInit {
|
||||
request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? "";
|
||||
this.emailPromise = this.apiService.postTwoFactorEmail(request);
|
||||
await this.emailPromise;
|
||||
|
||||
// Update cache to indicate email was sent
|
||||
if (this.twoFactorFormCacheService) {
|
||||
const cachedData = (await this.twoFactorFormCacheService.getFormData()) || {};
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
...cachedData,
|
||||
emailSent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (doToast) {
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
|
||||
@@ -2,5 +2,6 @@ export * from "./two-factor-auth-component.service";
|
||||
export * from "./default-two-factor-auth-component.service";
|
||||
export * from "./two-factor-auth.component";
|
||||
export * from "./two-factor-auth.guard";
|
||||
export * from "./abstractions";
|
||||
|
||||
export * from "./child-components";
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
>
|
||||
<app-two-factor-auth-email
|
||||
[tokenFormControl]="tokenFormControl"
|
||||
(tokenChange)="saveFormDataWithPartialData($event)"
|
||||
*ngIf="selectedProviderType === providerType.Email"
|
||||
/>
|
||||
|
||||
<app-two-factor-auth-authenticator
|
||||
[tokenFormControl]="tokenFormControl"
|
||||
(tokenChange)="saveFormDataWithPartialData($event)"
|
||||
*ngIf="selectedProviderType === providerType.Authenticator"
|
||||
/>
|
||||
<app-two-factor-auth-yubikey
|
||||
@@ -36,7 +38,7 @@
|
||||
/>
|
||||
<bit-form-control *ngIf="!hideRememberMe()">
|
||||
<bit-label>{{ "dontAskAgainOnThisDeviceFor30Days" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="remember" />
|
||||
<input type="checkbox" bitCheckbox formControlName="remember" (change)="saveFormData()" />
|
||||
</bit-form-control>
|
||||
|
||||
<app-two-factor-auth-webauthn
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
TwoFactorAuthSecurityKeyIcon,
|
||||
TwoFactorAuthDuoIcon,
|
||||
} from "../icons/two-factor-auth";
|
||||
import { TwoFactorFormCacheServiceAbstraction } from "./abstractions/two-factor-form-cache.service.abstraction";
|
||||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component";
|
||||
@@ -70,6 +71,16 @@ import {
|
||||
TwoFactorOptionsDialogResult,
|
||||
} from "./two-factor-options.component";
|
||||
|
||||
/**
|
||||
* Interface for the cache data structure
|
||||
*/
|
||||
interface TwoFactorFormCacheData {
|
||||
token?: string;
|
||||
remember?: boolean;
|
||||
selectedProviderType?: TwoFactorProviderType;
|
||||
emailSent?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth",
|
||||
@@ -160,6 +171,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||
private environmentService: EnvironmentService,
|
||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||
private twoFactorFormCacheService: TwoFactorFormCacheServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -168,7 +180,29 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.listenForAuthnSessionTimeout();
|
||||
|
||||
await this.setSelected2faProviderType();
|
||||
// Load persisted form data if available
|
||||
let loadedCachedProviderType = false;
|
||||
if (this.twoFactorFormCacheService) {
|
||||
const persistedData = await this.twoFactorFormCacheService.getFormData();
|
||||
if (persistedData) {
|
||||
if (persistedData.token) {
|
||||
this.form.patchValue({ token: persistedData.token });
|
||||
}
|
||||
if (persistedData.remember !== undefined) {
|
||||
this.form.patchValue({ remember: persistedData.remember });
|
||||
}
|
||||
if (persistedData.selectedProviderType !== undefined) {
|
||||
this.selectedProviderType = persistedData.selectedProviderType;
|
||||
loadedCachedProviderType = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only set default 2FA provider type if we don't have one from cache
|
||||
if (!loadedCachedProviderType) {
|
||||
await this.setSelected2faProviderType();
|
||||
}
|
||||
|
||||
await this.set2faProvidersAndData();
|
||||
await this.setAnonLayoutDataByTwoFactorProviderType();
|
||||
|
||||
@@ -181,6 +215,45 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save specific form data fields to the cache
|
||||
*/
|
||||
async saveFormDataWithPartialData(data: Partial<TwoFactorFormCacheData>) {
|
||||
if (this.twoFactorFormCacheService) {
|
||||
// Get current cached data
|
||||
const currentData = (await this.twoFactorFormCacheService.getFormData()) || {};
|
||||
|
||||
// Only update fields that are present in the data object
|
||||
const updatedData: TwoFactorFormCacheData = {
|
||||
...currentData,
|
||||
...Object.entries(data).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as any),
|
||||
};
|
||||
|
||||
await this.twoFactorFormCacheService.saveFormData(updatedData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all current form data to the cache
|
||||
*/
|
||||
async saveFormData() {
|
||||
if (this.twoFactorFormCacheService) {
|
||||
const formData: TwoFactorFormCacheData = {
|
||||
token: this.tokenFormControl.value || undefined,
|
||||
remember: this.rememberFormControl.value ?? undefined,
|
||||
selectedProviderType: this.selectedProviderType,
|
||||
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
|
||||
};
|
||||
|
||||
await this.saveFormDataWithPartialData(formData);
|
||||
}
|
||||
}
|
||||
|
||||
private async setSelected2faProviderType() {
|
||||
const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win);
|
||||
|
||||
@@ -268,6 +341,16 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
// In all flows but WebAuthn, the remember value is taken from the form.
|
||||
const rememberValue = remember ?? this.rememberFormControl.value ?? false;
|
||||
|
||||
// Persist form data before submitting
|
||||
if (this.twoFactorFormCacheService) {
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: tokenValue,
|
||||
remember: rememberValue,
|
||||
selectedProviderType: this.selectedProviderType,
|
||||
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.loginStrategyService.logInTwoFactor(
|
||||
new TokenTwoFactorRequest(this.selectedProviderType, tokenValue, rememberValue),
|
||||
@@ -275,6 +358,12 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
const authResult: AuthResult = await this.formPromise;
|
||||
this.logService.info("Successfully submitted two factor token");
|
||||
|
||||
// Clear persisted data on successful login
|
||||
if (this.twoFactorFormCacheService) {
|
||||
await this.twoFactorFormCacheService.clearFormData();
|
||||
}
|
||||
|
||||
await this.handleAuthResult(authResult);
|
||||
} catch {
|
||||
this.logService.error("Error submitting two factor token");
|
||||
@@ -287,6 +376,16 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
async selectOtherTwoFactorMethod() {
|
||||
// Persist current form data before navigating to another method
|
||||
if (this.twoFactorFormCacheService) {
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: this.tokenFormControl.value || undefined,
|
||||
remember: this.rememberFormControl.value ?? undefined,
|
||||
selectedProviderType: this.selectedProviderType,
|
||||
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
|
||||
});
|
||||
}
|
||||
|
||||
const dialogRef = TwoFactorOptionsComponent.open(this.dialogService);
|
||||
const response: TwoFactorOptionsDialogResult | string | undefined = await lastValueFrom(
|
||||
dialogRef.closed,
|
||||
@@ -300,6 +399,17 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
this.selectedProviderType = response.type;
|
||||
await this.setAnonLayoutDataByTwoFactorProviderType();
|
||||
|
||||
// Update the persisted provider type when a new one is chosen
|
||||
if (this.twoFactorFormCacheService) {
|
||||
const persistedData = await this.twoFactorFormCacheService.getFormData();
|
||||
await this.twoFactorFormCacheService.saveFormData({
|
||||
token: persistedData?.token || undefined,
|
||||
remember: persistedData?.remember ?? undefined,
|
||||
selectedProviderType: response.type,
|
||||
emailSent: false, // Reset email sent state when switching providers
|
||||
});
|
||||
}
|
||||
|
||||
this.form.reset();
|
||||
this.form.updateValueAndValidity();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user