1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 13:10:17 +00:00

Move email sent information to its own cache service

This commit is contained in:
Alec Rippberger
2025-04-10 16:26:44 -05:00
parent 3668fed7b4
commit 3ad40d7082
5 changed files with 102 additions and 22 deletions

View File

@@ -0,0 +1,85 @@
import { inject, Injectable, WritableSignal } from "@angular/core";
import { Jsonify } from "type-fest";
import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
const TWO_FACTOR_AUTH_EMAIL_CACHE_KEY = "two-factor-auth-email-cache";
/**
* Cache model for the email two factor
*/
export class TwoFactorAuthEmailCache {
emailSent: boolean = false;
static fromJSON(obj: Partial<Jsonify<TwoFactorAuthEmailCache>>): TwoFactorAuthEmailCache {
return Object.assign(new TwoFactorAuthEmailCache(), obj);
}
}
/**
* Cache service for the two factor auth email component.
*/
@Injectable()
export class TwoFactorAuthEmailComponentCacheService {
private viewCacheService: ViewCacheService = inject(ViewCacheService);
private configService: ConfigService = inject(ConfigService);
/** True when the feature flag is enabled */
private featureEnabled: boolean = false;
/**
* Signal for the cached email state.
*/
private emailCache: WritableSignal<TwoFactorAuthEmailCache | null> =
this.viewCacheService.signal<TwoFactorAuthEmailCache | null>({
key: TWO_FACTOR_AUTH_EMAIL_CACHE_KEY,
initialValue: null,
deserializer: TwoFactorAuthEmailCache.fromJSON,
});
/**
* Must be called once before interacting with the cached data.
*/
async init() {
this.featureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
}
/**
* Cache the email sent state.
*/
cacheData(data: { emailSent: boolean }): void {
if (!this.featureEnabled) {
return;
}
this.emailCache.set({
emailSent: data.emailSent,
} as TwoFactorAuthEmailCache);
}
/**
* Clear the cached email data.
*/
clearCachedData(): void {
if (!this.featureEnabled) {
return;
}
this.emailCache.set(null);
}
/**
* Get whether the email has been sent.
*/
getCachedData(): TwoFactorAuthEmailCache | null {
if (!this.featureEnabled) {
return null;
}
return this.emailCache();
}
}

View File

@@ -22,6 +22,7 @@ import {
ToastService,
} from "@bitwarden/components";
import { TwoFactorAuthEmailComponentCacheService } from "./two-factor-auth-email-cache.service";
import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service";
@Component({
@@ -40,15 +41,19 @@ import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-comp
AsyncActionsModule,
FormsModule,
],
providers: [
{
provide: TwoFactorAuthEmailComponentCacheService,
},
],
})
export class TwoFactorAuthEmailComponent implements OnInit {
@Input({ required: true }) tokenFormControl: FormControl | undefined = undefined;
@Input({ required: true }) emailSent: boolean = false;
@Output() tokenChange = new EventEmitter<{ token: string }>();
@Output() emailSendEvent = new EventEmitter<void>();
twoFactorEmail: string | undefined = undefined;
emailPromise: Promise<any> | undefined;
emailSent = false;
constructor(
protected i18nService: I18nService,
@@ -60,10 +65,18 @@ export class TwoFactorAuthEmailComponent implements OnInit {
protected appIdService: AppIdService,
private toastService: ToastService,
private twoFactorAuthEmailComponentService: TwoFactorAuthEmailComponentService,
private cacheService: TwoFactorAuthEmailComponentCacheService,
) {}
async ngOnInit(): Promise<void> {
await this.twoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa?.();
await this.cacheService.init();
// Check if email was already sent
const cachedData = this.cacheService.getCachedData();
if (cachedData?.emailSent) {
this.emailSent = true;
}
const providers = await this.twoFactorService.getProviders();
@@ -124,7 +137,8 @@ export class TwoFactorAuthEmailComponent implements OnInit {
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
this.emailSendEvent.emit();
this.emailSent = true;
this.cacheService.cacheData({ emailSent: this.emailSent });
if (doToast) {
this.toastService.showToast({

View File

@@ -14,8 +14,6 @@
<app-two-factor-auth-email
[tokenFormControl]="tokenFormControl"
(tokenChange)="saveFormDataWithPartialData($event)"
[emailSent]="emailSent"
(emailSendEvent)="saveFormDataWithPartialData({ emailSent: true })"
*ngIf="selectedProviderType === providerType.Email"
/>

View File

@@ -78,7 +78,6 @@ interface TwoFactorCacheData {
token?: string;
remember?: boolean;
selectedProviderType?: TwoFactorProviderType;
emailSent?: boolean;
}
@Component({
@@ -114,11 +113,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
loading = true;
/**
* Whether the email has been sent according to the cache
*/
emailSent = false;
orgSsoIdentifier: string | undefined = undefined;
providerType = TwoFactorProviderType;
@@ -206,9 +200,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
this.selectedProviderType = persistedData.selectedProviderType;
loadedCachedProviderType = true;
}
if (persistedData.emailSent !== undefined) {
this.emailSent = persistedData.emailSent;
}
}
// Only set default 2FA provider type if we don't have one from cache
@@ -242,7 +233,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
data?.selectedProviderType ??
currentData?.selectedProviderType ??
TwoFactorProviderType.Authenticator,
emailSent: data?.emailSent ?? currentData?.emailSent ?? false,
});
}
@@ -254,7 +244,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
token: this.tokenFormControl.value || undefined,
remember: this.rememberFormControl.value ?? undefined,
selectedProviderType: this.selectedProviderType,
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
};
await this.saveFormDataWithPartialData(formData);
@@ -351,7 +340,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
token: tokenValue,
remember: rememberValue,
selectedProviderType: this.selectedProviderType,
emailSent: this.selectedProviderType === TwoFactorProviderType.Email,
});
try {
@@ -379,7 +367,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
token: "",
remember: false,
selectedProviderType: this.selectedProviderType,
emailSent: false,
});
const dialogRef = TwoFactorOptionsComponent.open(this.dialogService);
@@ -400,7 +387,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
token: "",
remember: false,
selectedProviderType: response.type,
emailSent: false,
});
this.form.reset();

View File

@@ -15,7 +15,6 @@ export class TwoFactorAuthCache {
token: string | undefined = undefined;
remember: boolean | undefined = undefined;
selectedProviderType: TwoFactorProviderType | undefined = undefined;
emailSent: boolean | undefined = undefined;
static fromJSON(obj: Partial<Jsonify<TwoFactorAuthCache>>): TwoFactorAuthCache {
return Object.assign(new TwoFactorAuthCache(), obj);
@@ -26,7 +25,6 @@ export interface TwoFactorAuthData {
token?: string;
remember?: boolean;
selectedProviderType?: TwoFactorProviderType;
emailSent?: boolean;
}
/**
@@ -76,7 +74,6 @@ export class TwoFactorAuthComponentCacheService {
token: data.token,
remember: data.remember,
selectedProviderType: data.selectedProviderType,
emailSent: data.emailSent,
} as TwoFactorAuthCache);
}