1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 00:33:44 +00:00

merge main

This commit is contained in:
rr-bw
2024-08-30 11:40:21 -07:00
615 changed files with 20276 additions and 5305 deletions

View File

@@ -14,6 +14,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { ToastService } from "@bitwarden/components";
@Directive()
export class CollectionsComponent implements OnInit {
@@ -39,6 +40,7 @@ export class CollectionsComponent implements OnInit {
private logService: LogService,
private configService: ConfigService,
private accountService: AccountService,
private toastService: ToastService,
) {}
async ngOnInit() {
@@ -82,11 +84,11 @@ export class CollectionsComponent implements OnInit {
})
.map((c) => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectOneCollection"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("selectOneCollection"),
});
return false;
}
this.cipherDomain.collectionIds = selectedCollectionIds;
@@ -94,10 +96,18 @@ export class CollectionsComponent implements OnInit {
this.formPromise = this.saveCollections();
await this.formPromise;
this.onSavedCollections.emit();
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("editedItem"),
});
return true;
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
return false;
}
}

View File

@@ -39,6 +39,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
enum State {
NewUser,
@@ -104,6 +105,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {}
async ngOnInit() {
@@ -275,11 +277,11 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString);
await this.apiService.postAccountKeys(keysRequest);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("accountSuccessfullyCreated"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("accountSuccessfullyCreated"),
});
await this.passwordResetEnrollmentService.enroll(this.data.organizationId);

View File

@@ -6,6 +6,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ToastService } from "@bitwarden/components";
@Directive()
export abstract class CaptchaProtectedComponent {
@@ -17,6 +18,7 @@ export abstract class CaptchaProtectedComponent {
protected environmentService: EnvironmentService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected toastService: ToastService,
) {}
async setupCaptcha() {
@@ -31,10 +33,18 @@ export abstract class CaptchaProtectedComponent {
this.captchaToken = token;
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: error,
});
},
(info: string) => {
this.platformUtilsService.showToast("info", this.i18nService.t("info"), info);
this.toastService.showToast({
variant: "info",
title: this.i18nService.t("info"),
message: info,
});
},
);
}

View File

@@ -15,7 +15,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordColorText } from "../../tools/password-strength/password-strength.component";
@@ -49,6 +49,7 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
protected kdfConfigService: KdfConfigService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {}
async ngOnInit() {
@@ -127,27 +128,27 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return false;
}
if (this.masterPassword.length < this.minimumLength) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordMinimumlength", this.minimumLength),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordMinimumlength", this.minimumLength),
});
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPassDoesntMatch"),
});
return false;
}
@@ -161,11 +162,11 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
this.enforcedPolicyOptions,
)
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordPolicyRequirementsNotMet"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"),
});
return false;
}

View File

@@ -7,6 +7,7 @@ import {
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { ModalService } from "../../services/modal.service";
@@ -27,6 +28,7 @@ export class EnvironmentComponent {
protected environmentService: EnvironmentService,
protected i18nService: I18nService,
private modalService: ModalService,
private toastService: ToastService,
) {
this.environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => {
if (env.getRegion() !== Region.SelfHosted) {
@@ -59,7 +61,11 @@ export class EnvironmentComponent {
notifications: this.notificationsUrl,
});
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("environmentSaved"),
});
this.saved();
}

View File

@@ -7,6 +7,7 @@ import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/passw
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
@Directive()
export class HintComponent implements OnInit {
@@ -23,6 +24,7 @@ export class HintComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService,
private logService: LogService,
private loginEmailService: LoginEmailServiceAbstraction,
protected toastService: ToastService,
) {}
ngOnInit(): void {
@@ -31,26 +33,30 @@ export class HintComponent implements OnInit {
async submit() {
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("emailRequired"),
});
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("invalidEmail"),
});
return;
}
try {
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
await this.formPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("masterPassSent"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("masterPassSent"),
});
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
} else if (this.router != null) {

View File

@@ -30,12 +30,13 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { BiometricsService } from "@bitwarden/common/platform/biometrics/biometric.service";
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
@Directive()
export class LockComponent implements OnInit, OnDestroy {
@@ -84,10 +85,12 @@ export class LockComponent implements OnInit, OnDestroy {
protected userVerificationService: UserVerificationService,
protected pinService: PinServiceAbstraction,
protected biometricStateService: BiometricStateService,
protected biometricsService: BiometricsService,
protected accountService: AccountService,
protected authService: AuthService,
protected kdfConfigService: KdfConfigService,
protected syncService: SyncService,
protected toastService: ToastService,
) {}
async ngOnInit() {
@@ -146,6 +149,13 @@ export class LockComponent implements OnInit, OnDestroy {
return !!userKey;
}
async isBiometricUnlockAvailable(): Promise<boolean> {
if (!(await this.biometricsService.supportsBiometric())) {
return false;
}
return this.biometricsService.isBiometricUnlockAvailable();
}
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinEnabled ? "pin" : "masterPassword");
@@ -158,11 +168,11 @@ export class LockComponent implements OnInit, OnDestroy {
private async handlePinRequiredUnlock() {
if (this.pin == null || this.pin === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("pinRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("pinRequired"),
});
return;
}
@@ -186,36 +196,36 @@ export class LockComponent implements OnInit, OnDestroy {
// Log user out if they have entered an invalid PIN too many times
if (this.invalidPinAttempts >= MAX_INVALID_PIN_ENTRY_ATTEMPTS) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("tooManyInvalidPinEntryAttemptsLoggingOut"),
);
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("tooManyInvalidPinEntryAttemptsLoggingOut"),
});
this.messagingService.send("logout");
return;
}
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidPin"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("invalidPin"),
});
} catch {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("unexpectedError"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("unexpectedError"),
});
}
}
private async handleMasterPasswordRequiredUnlock() {
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return;
}
await this.doUnlockWithMasterPassword();
@@ -249,11 +259,11 @@ export class LockComponent implements OnInit, OnDestroy {
}
if (!passwordValid) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("invalidMasterPassword"),
});
return;
}
@@ -327,7 +337,7 @@ export class LockComponent implements OnInit, OnDestroy {
this.masterPasswordEnabled = await this.userVerificationService.hasMasterPassword();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.supportsBiometric = await this.biometricsService.supportsBiometric();
this.biometricLock =
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
((await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric)) ||

View File

@@ -32,6 +32,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { CaptchaProtectedComponent } from "./captcha-protected.component";
@@ -88,8 +89,9 @@ export class LoginViaAuthRequestComponent
private deviceTrustService: DeviceTrustServiceAbstraction,
private authRequestService: AuthRequestServiceAbstraction,
private loginStrategyService: LoginStrategyServiceAbstraction,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService);
super(environmentService, i18nService, platformUtilsService, toastService);
// TODO: I don't know why this is necessary.
// Why would the existence of the email depend on the navigation?
@@ -105,7 +107,11 @@ export class LoginViaAuthRequestComponent
.subscribe((id) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.verifyAndHandleApprovedAuthReq(id).catch((e: Error) => {
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("error"),
message: e.message,
});
this.logService.error("Failed to use approved auth request: " + e.message);
});
});
@@ -135,7 +141,11 @@ export class LoginViaAuthRequestComponent
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
if (!this.email) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("userEmailMissing"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("userEmailMissing"),
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/login-initiated"]);
@@ -158,7 +168,11 @@ export class LoginViaAuthRequestComponent
this.email = this.loginEmailService.getEmail();
if (!this.email) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("userEmailMissing"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("userEmailMissing"),
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/login"]);
@@ -402,7 +416,11 @@ export class LoginViaAuthRequestComponent
// TODO: this should eventually be enforced via deleting this on the server once it is used
await this.authRequestService.clearAdminAuthRequest(userId);
this.platformUtilsService.showToast("success", null, this.i18nService.t("loginApproved"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("loginApproved"),
});
// Now that we have a decrypted user key in memory, we can check if we
// need to establish trust on the current device

View File

@@ -23,6 +23,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
@@ -39,7 +41,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
showPassword = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginNavigate: (userId: UserId) => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
showLoginWithDevice: boolean;
@@ -91,8 +93,9 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
protected registerRouteService: RegisterRouteService,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService);
super(environmentService, i18nService, platformUtilsService, toastService);
}
async ngOnInit() {
@@ -136,7 +139,11 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
//desktop, browser; This should be removed once all clients use reactive forms
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: errorText,
});
return;
}
@@ -187,7 +194,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
if (this.onSuccessfulLoginNavigate != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.onSuccessfulLoginNavigate();
this.onSuccessfulLoginNavigate(response.userId);
} else {
this.loginEmailService.clearValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
@@ -330,11 +337,11 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
return false;
}
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccured"),
this.i18nService.t("encryptionKeyMigrationRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccured"),
message: this.i18nService.t("encryptionKeyMigrationRequired"),
});
return true;
}

View File

@@ -17,7 +17,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
@@ -97,8 +97,9 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
protected logService: LogService,
protected auditService: AuditService,
protected dialogService: DialogService,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService);
super(environmentService, i18nService, platformUtilsService, toastService);
this.showTerms = !platformUtilsService.isSelfHost();
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
@@ -129,11 +130,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
if (this.isInTrialFlow) {
if (!this.accountCreated) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("trialAccountCreated"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("trialAccountCreated"),
});
}
const loginResponse = await this.logIn(email, masterPassword, this.captchaBypassToken);
if (loginResponse.captchaRequired) {
@@ -141,11 +142,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
this.createdAccount.emit(this.formGroup.value.email);
} else {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("newAccountCreated"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("newAccountCreated"),
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([this.successRoute], { queryParams: { email: email } });
@@ -210,11 +211,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
this.showErrorSummary = true;
if (this.formGroup.get("acceptPolicies").hasError("required")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("acceptPoliciesRequired"),
});
return { isValid: false };
}
@@ -226,7 +227,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
//desktop, browser
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: errorText,
});
return { isValid: false };
}

View File

@@ -9,7 +9,7 @@ import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-con
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
@Directive()
export class RemovePasswordComponent implements OnInit {
@@ -30,6 +30,7 @@ export class RemovePasswordComponent implements OnInit {
private keyConnectorService: KeyConnectorService,
private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogService,
private toastService: ToastService,
) {}
async ngOnInit() {
@@ -47,17 +48,21 @@ export class RemovePasswordComponent implements OnInit {
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedMasterPassword"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("removedMasterPassword"),
});
await this.keyConnectorService.removeConvertAccountRequired();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
}
};
@@ -76,13 +81,21 @@ export class RemovePasswordComponent implements OnInit {
this.leaving = true;
this.actionPromise = this.organizationApiService.leave(this.organization.id);
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("leftOrganization"),
});
await this.keyConnectorService.removeConvertAccountRequired();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e,
});
}
};
}

View File

@@ -32,7 +32,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@@ -74,6 +74,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
dialogService: DialogService,
kdfConfigService: KdfConfigService,
private encryptService: EncryptService,
protected toastService: ToastService,
) {
super(
i18nService,
@@ -87,6 +88,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
kdfConfigService,
masterPasswordService,
accountService,
toastService,
);
}
@@ -137,7 +139,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
)
.subscribe({
error: () => {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
},
});
}
@@ -237,7 +243,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
}

View File

@@ -28,6 +28,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { SsoComponent } from "./sso.component";
@@ -65,7 +66,7 @@ describe("SsoComponent", () => {
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
let mockStateService: MockProxy<StateService>;
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
let mockToastService: MockProxy<ToastService>;
let mockApiService: MockProxy<ApiService>;
let mockCryptoFunctionService: MockProxy<CryptoFunctionService>;
let mockEnvironmentService: MockProxy<EnvironmentService>;
@@ -75,6 +76,7 @@ describe("SsoComponent", () => {
let mockConfigService: MockProxy<ConfigService>;
let mockMasterPasswordService: FakeMasterPasswordService;
let mockAccountService: FakeAccountService;
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
// Mock authService.logIn params
let code: string;
@@ -117,7 +119,7 @@ describe("SsoComponent", () => {
mockSsoLoginService = mock();
mockStateService = mock();
mockPlatformUtilsService = mock();
mockToastService = mock();
mockApiService = mock();
mockCryptoFunctionService = mock();
mockEnvironmentService = mock();
@@ -127,6 +129,7 @@ describe("SsoComponent", () => {
mockConfigService = mock();
mockAccountService = mockAccountServiceWith(userId);
mockMasterPasswordService = new FakeMasterPasswordService();
mockPlatformUtilsService = mock();
// Mock loginStrategyService.logIn params
code = "code";
@@ -196,7 +199,7 @@ describe("SsoComponent", () => {
{ provide: I18nService, useValue: mockI18nService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: StateService, useValue: mockStateService },
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
{ provide: ToastService, useValue: mockToastService },
{ provide: ApiService, useValue: mockApiService },
{ provide: CryptoFunctionService, useValue: mockCryptoFunctionService },
@@ -214,6 +217,7 @@ describe("SsoComponent", () => {
{ provide: ConfigService, useValue: mockConfigService },
{ provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
{ provide: AccountService, useValue: mockAccountService },
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
],
});
@@ -594,12 +598,12 @@ describe("SsoComponent", () => {
expect(mockLogService.error).toHaveBeenCalledTimes(1);
expect(mockLogService.error).toHaveBeenCalledWith(error);
expect(mockPlatformUtilsService.showToast).toHaveBeenCalledTimes(1);
expect(mockPlatformUtilsService.showToast).toHaveBeenCalledWith(
"error",
null,
"ssoKeyConnectorError",
);
expect(mockToastService.showToast).toHaveBeenCalledTimes(1);
expect(mockToastService.showToast).toHaveBeenCalledWith({
variant: "error",
title: null,
message: "ssoKeyConnectorError",
});
expect(mockRouter.navigate).not.toHaveBeenCalled();
});

View File

@@ -26,6 +26,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@Directive()
@@ -71,6 +72,7 @@ export class SsoComponent implements OnInit {
protected configService: ConfigService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {}
async ngOnInit() {
@@ -111,11 +113,11 @@ export class SsoComponent implements OnInit {
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
if (this.identifier == null || this.identifier === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("ssoValidationFailed"),
this.i18nService.t("ssoIdentifierRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("ssoValidationFailed"),
message: this.i18nService.t("ssoIdentifierRequired"),
});
return;
}
@@ -382,11 +384,11 @@ export class SsoComponent implements OnInit {
// TODO: Key Connector Service should pass this error message to the logout callback instead of displaying here
if (e.message === "Key Connector error") {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("ssoKeyConnectorError"),
);
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("ssoKeyConnectorError"),
});
}
}

View File

@@ -20,6 +20,7 @@ import {
TypographyModule,
FormFieldModule,
AsyncActionsModule,
ToastService,
} from "@bitwarden/components";
@Component({
@@ -55,6 +56,7 @@ export class TwoFactorAuthEmailComponent implements OnInit {
protected logService: LogService,
protected apiService: ApiService,
protected appIdService: AppIdService,
private toastService: ToastService,
) {}
async ngOnInit(): Promise<void> {
@@ -74,11 +76,11 @@ export class TwoFactorAuthEmailComponent implements OnInit {
}
if ((await this.loginStrategyService.getEmail()) == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sessionTimeout"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("sessionTimeout"),
});
return;
}
@@ -94,11 +96,11 @@ export class TwoFactorAuthEmailComponent implements OnInit {
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
});
}
} catch (e) {
this.logService.error(e);

View File

@@ -21,6 +21,7 @@ import {
TypographyModule,
FormFieldModule,
AsyncActionsModule,
ToastService,
} from "@bitwarden/components";
@Component({
@@ -56,6 +57,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
protected environmentService: EnvironmentService,
protected twoFactorService: TwoFactorService,
protected route: ActivatedRoute,
private toastService: ToastService,
) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
@@ -85,11 +87,11 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
this.token.emit(token);
},
(error: string) => {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("webauthnCancelOrTimeout"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("webauthnCancelOrTimeout"),
});
},
(info: string) => {
if (info === "ready") {

View File

@@ -1,79 +1,76 @@
<form [bitSubmit]="submitForm" [formGroup]="formGroup" autocomplete="off">
<div class="tw-min-w-96">
<app-two-factor-auth-email
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Email"
/>
<app-two-factor-auth-authenticator
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Authenticator"
/>
<app-two-factor-auth-yubikey
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Yubikey"
/>
<app-two-factor-auth-webauthn
(token)="token = $event; submitForm()"
*ngIf="selectedProviderType === providerType.WebAuthn"
/>
<app-two-factor-auth-duo
(token)="token = $event; submitForm()"
[providerData]="providerData"
<app-two-factor-auth-email
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Email"
/>
<app-two-factor-auth-authenticator
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Authenticator"
/>
<app-two-factor-auth-yubikey
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Yubikey"
/>
<app-two-factor-auth-webauthn
(token)="token = $event; submitForm()"
*ngIf="selectedProviderType === providerType.WebAuthn"
/>
<app-two-factor-auth-duo
(token)="token = $event; submitForm()"
[providerData]="providerData"
*ngIf="
selectedProviderType === providerType.OrganizationDuo ||
selectedProviderType === providerType.Duo
"
#duoComponent
/>
<bit-form-control *ngIf="selectedProviderType != null">
<bit-label>{{ "rememberMe" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="remember" />
</bit-form-control>
<ng-container *ngIf="selectedProviderType == null">
<p bitTypography="body1">{{ "noTwoStepProviders" | i18n }}</p>
<p bitTypography="body1">{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container>
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
<!-- Buttons -->
<div class="tw-flex tw-flex-col tw-space-y-2.5 tw-mb-3">
<button
type="submit"
buttonType="primary"
bitButton
bitFormButton
*ngIf="
selectedProviderType === providerType.OrganizationDuo ||
selectedProviderType === providerType.Duo
selectedProviderType != null &&
selectedProviderType !== providerType.WebAuthn &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
#duoComponent
/>
<bit-form-control *ngIf="selectedProviderType != null">
<bit-label>{{ "rememberMe" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="remember" />
</bit-form-control>
<ng-container *ngIf="selectedProviderType == null">
<p bitTypography="body1">{{ "noTwoStepProviders" | i18n }}</p>
<p bitTypography="body1">{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container>
<hr />
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
<!-- <!-- Buttons -->
<div class="tw-flex tw-flex-col tw-space-y-2.5 tw-mb-3">
<button
type="submit"
buttonType="primary"
bitButton
bitFormButton
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.WebAuthn &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
>
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ actionButtonText }} </span>
</button>
<button
type="button"
buttonType="primary"
bitButton
(click)="launchDuo()"
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "launchDuo" | i18n }}</span>
</button>
>
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ actionButtonText }} </span>
</button>
<button
type="button"
buttonType="primary"
bitButton
(click)="launchDuo()"
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "launchDuo" | i18n }}</span>
</button>
<a routerLink="/login" bitButton buttonType="secondary">
{{ "cancel" | i18n }}
</a>
</div>
<div class="text-center">
<a bitLink href="#" appStopClick (click)="selectOtherTwofactorMethod()">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
</div>
<a routerLink="/login" bitButton buttonType="secondary">
{{ "cancel" | i18n }}
</a>
</div>
<div class="text-center">
<a bitLink href="#" appStopClick (click)="selectOtherTwofactorMethod()">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
</div>
</form>

View File

@@ -35,7 +35,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { TwoFactorAuthComponent } from "./two-factor-auth.component";
@@ -76,6 +76,7 @@ describe("TwoFactorComponent", () => {
let mockMasterPasswordService: FakeMasterPasswordService;
let mockAccountService: FakeAccountService;
let mockDialogService: MockProxy<DialogService>;
let mockToastService: MockProxy<ToastService>;
let mockUserDecryptionOpts: {
noMasterPassword: UserDecryptionOptions;
@@ -113,6 +114,7 @@ describe("TwoFactorComponent", () => {
mockAccountService = mockAccountServiceWith(userId);
mockMasterPasswordService = new FakeMasterPasswordService();
mockDialogService = mock<DialogService>();
mockToastService = mock<ToastService>();
mockUserDecryptionOpts = {
noMasterPassword: new UserDecryptionOptions({
@@ -193,6 +195,7 @@ describe("TwoFactorComponent", () => {
{ provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
{ provide: AccountService, useValue: mockAccountService },
{ provide: DialogService, useValue: mockDialogService },
{ provide: ToastService, useValue: mockToastService },
],
});

View File

@@ -34,6 +34,7 @@ import {
ButtonModule,
DialogService,
FormFieldModule,
ToastService,
} from "@bitwarden/components";
import { CaptchaProtectedComponent } from "../captcha-protected.component";
@@ -142,8 +143,9 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
private accountService: AccountService,
private formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService);
super(environmentService, i18nService, platformUtilsService, toastService);
}
async ngOnInit() {
@@ -184,11 +186,11 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
await this.setupCaptcha();
if (this.token == null || this.token === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("verificationCodeRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("verificationCodeRequired"),
});
return;
}
@@ -202,11 +204,11 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
await this.handleLoginResponse(authResult);
} catch {
this.logService.error("Error submitting two factor token");
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidVerificationCode"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("invalidVerificationCode"),
});
}
}

View File

@@ -97,7 +97,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected accountService: AccountService,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService);
super(environmentService, i18nService, platformUtilsService, toastService);
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
}
@@ -135,7 +135,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.submit();
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: error,
});
},
(info: string) => {
if (info === "ready") {
@@ -201,11 +205,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
await this.setupCaptcha();
if (this.token == null || this.token === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("verificationCodeRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("verificationCodeRequired"),
});
return;
}
@@ -243,11 +247,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
return false;
}
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccured"),
this.i18nService.t("encryptionKeyMigrationRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccured"),
message: this.i18nService.t("encryptionKeyMigrationRequired"),
});
return true;
}
@@ -414,11 +418,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
}
if ((await this.loginStrategyService.getEmail()) == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sessionTimeout"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("sessionTimeout"),
});
return;
}
@@ -434,11 +438,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
});
}
} catch (e) {
this.logService.error(e);

View File

@@ -19,7 +19,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@@ -50,6 +50,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
kdfConfigService: KdfConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
toastService: ToastService,
) {
super(
i18nService,
@@ -63,6 +64,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
kdfConfigService,
masterPasswordService,
accountService,
toastService,
);
}
@@ -77,11 +79,11 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
async setupSubmitActions(): Promise<boolean> {
if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return false;
}
@@ -92,7 +94,11 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
return false;
}
@@ -120,11 +126,11 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.apiService.postPassword(request);
this.platformUtilsService.showToast(
"success",
this.i18nService.t("masterPasswordChanged"),
this.i18nService.t("logBackIn"),
);
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("masterPasswordChanged"),
message: this.i18nService.t("logBackIn"),
});
if (this.onSuccessfulChangePassword != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.

View File

@@ -24,7 +24,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@@ -64,6 +64,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
kdfConfigService: KdfConfigService,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
toastService: ToastService,
) {
super(
i18nService,
@@ -77,6 +78,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
kdfConfigService,
masterPasswordService,
accountService,
toastService,
);
}
@@ -176,11 +178,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
}
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("updatedMasterPassword"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("updatedMasterPassword"),
});
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(

View File

@@ -5,6 +5,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
import { Verification } from "@bitwarden/common/auth/types/verification";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { ModalRef } from "../../components/modal/modal.ref";
@@ -37,6 +38,7 @@ export class UserVerificationPromptComponent {
private formBuilder: FormBuilder,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private toastService: ToastService,
) {}
get secret() {
@@ -56,7 +58,11 @@ export class UserVerificationPromptComponent {
this.invalidSecret = false;
} catch (e) {
this.invalidSecret = true;
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("error"),
message: e.message,
});
return;
}

View File

@@ -2,5 +2,3 @@ export * from "./add-account-credit-dialog/add-account-credit-dialog.component";
export * from "./invoices/invoices.component";
export * from "./invoices/no-invoices.component";
export * from "./manage-tax-information/manage-tax-information.component";
export * from "./select-payment-method/select-payment-method.component";
export * from "./verify-bank-account/verify-bank-account.component";

View File

@@ -1,151 +0,0 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="tw-mb-4 tw-text-lg">
<bit-radio-group formControlName="paymentMethod">
<bit-radio-button id="card-payment-method" [value]="PaymentMethodType.Card">
<bit-label>
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>
{{ "creditCard" | i18n }}
</bit-label>
</bit-radio-button>
<bit-radio-button
id="bank-payment-method"
[value]="PaymentMethodType.BankAccount"
*ngIf="showBankAccount"
>
<bit-label>
<i class="bwi bwi-fw bwi-bank" aria-hidden="true"></i>
{{ "bankAccount" | i18n }}
</bit-label>
</bit-radio-button>
<bit-radio-button
id="paypal-payment-method"
[value]="PaymentMethodType.PayPal"
*ngIf="showPayPal"
>
<bit-label>
<i class="bwi bwi-fw bwi-paypal" aria-hidden="true"></i>
{{ "payPal" | i18n }}
</bit-label>
</bit-radio-button>
<bit-radio-button
id="credit-payment-method"
[value]="PaymentMethodType.Credit"
*ngIf="showAccountCredit"
>
<bit-label>
<i class="bwi bwi-fw bwi-dollar" aria-hidden="true"></i>
{{ "accountCredit" | i18n }}
</bit-label>
</bit-radio-button>
</bit-radio-group>
</div>
<!-- Card -->
<ng-container *ngIf="usingCard">
<div class="tw-grid tw-grid-cols-2 tw-gap-4 tw-mb-4">
<div class="tw-col-span-1">
<label for="stripe-card-number">{{ "number" | i18n }}</label>
<div id="stripe-card-number" class="form-control stripe-form-control"></div>
</div>
<div class="tw-col-span-1 tw-flex tw-items-end">
<img
src="../../images/cards.png"
alt="Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay"
class="tw-max-w-full"
/>
</div>
<div class="tw-col-span-1">
<label for="stripe-card-expiry">{{ "expiration" | i18n }}</label>
<div id="stripe-card-expiry" class="form-control stripe-form-control"></div>
</div>
<div class="tw-col-span-1">
<div class="tw-flex">
<label for="stripe-card-cvc">
{{ "securityCode" | i18n }}
</label>
<a
href="https://www.cvvnumber.com/cvv.html"
tabindex="-1"
target="_blank"
rel="noreferrer"
class="ml-auto"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div id="stripe-card-cvc" class="form-control stripe-form-control"></div>
</div>
</div>
</ng-container>
<!-- Bank Account -->
<ng-container *ngIf="showBankAccount && usingBankAccount">
<app-callout type="warning" title="{{ 'verifyBankAccount' | i18n }}">
{{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}
</app-callout>
<div class="tw-grid tw-grid-cols-2 tw-gap-4" formGroupName="bankInformation">
<bit-form-field class="tw-col-span-1" disableMargin>
<bit-label>{{ "routingNumber" | i18n }}</bit-label>
<input
bitInput
id="routingNumber"
type="text"
formControlName="routingNumber"
required
appInputVerbatim
/>
</bit-form-field>
<bit-form-field class="tw-col-span-1" disableMargin>
<bit-label>{{ "accountNumber" | i18n }}</bit-label>
<input
bitInput
id="accountNumber"
type="text"
formControlName="accountNumber"
required
appInputVerbatim
/>
</bit-form-field>
<bit-form-field class="tw-col-span-1" disableMargin>
<bit-label>{{ "accountHolderName" | i18n }}</bit-label>
<input
id="accountHolderName"
bitInput
type="text"
formControlName="accountHolderName"
required
appInputVerbatim
/>
</bit-form-field>
<bit-form-field class="tw-col-span-1" disableMargin>
<bit-label>{{ "bankAccountType" | i18n }}</bit-label>
<bit-select id="accountHolderType" formControlName="accountHolderType" required>
<bit-option [value]="''" label="-- {{ 'select' | i18n }} --"></bit-option>
<bit-option
[value]="'company'"
label="{{ 'bankAccountTypeCompany' | i18n }}"
></bit-option>
<bit-option
[value]="'individual'"
label="{{ 'bankAccountTypeIndividual' | i18n }}"
></bit-option>
</bit-select>
</bit-form-field>
</div>
</ng-container>
<!-- PayPal -->
<ng-container *ngIf="showPayPal && usingPayPal">
<div class="tw-mb-3">
<div id="braintree-container" class="tw-mb-1 tw-content-center"></div>
<small class="tw-text-muted">{{ "paypalClickSubmit" | i18n }}</small>
</div>
</ng-container>
<!-- Account Credit -->
<ng-container *ngIf="showAccountCredit && usingAccountCredit">
<app-callout type="note">
{{ "makeSureEnoughCredit" | i18n }}
</app-callout>
</ng-container>
<button *ngIf="!!onSubmit" bitButton bitFormButton buttonType="primary" type="submit">
{{ "submit" | i18n }}
</button>
</form>

View File

@@ -1,159 +0,0 @@
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import {
BillingApiServiceAbstraction,
BraintreeServiceAbstraction,
StripeServiceAbstraction,
} from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { TokenizedPaymentMethod } from "@bitwarden/common/billing/models/domain";
@Component({
selector: "app-select-payment-method",
templateUrl: "./select-payment-method.component.html",
})
export class SelectPaymentMethodComponent implements OnInit, OnDestroy {
@Input() protected showAccountCredit: boolean = true;
@Input() protected showBankAccount: boolean = true;
@Input() protected showPayPal: boolean = true;
@Input() private startWith: PaymentMethodType = PaymentMethodType.Card;
@Input() protected onSubmit: (tokenizedPaymentMethod: TokenizedPaymentMethod) => Promise<void>;
private destroy$ = new Subject<void>();
protected formGroup = this.formBuilder.group({
paymentMethod: [this.startWith],
bankInformation: this.formBuilder.group({
routingNumber: ["", [Validators.required]],
accountNumber: ["", [Validators.required]],
accountHolderName: ["", [Validators.required]],
accountHolderType: ["", [Validators.required]],
}),
});
protected PaymentMethodType = PaymentMethodType;
constructor(
private billingApiService: BillingApiServiceAbstraction,
private braintreeService: BraintreeServiceAbstraction,
private formBuilder: FormBuilder,
private stripeService: StripeServiceAbstraction,
) {}
async tokenizePaymentMethod(): Promise<TokenizedPaymentMethod> {
const type = this.selected;
if (this.usingStripe) {
const clientSecret = await this.billingApiService.createSetupIntent(type);
if (this.usingBankAccount) {
const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, {
accountHolderName: this.formGroup.value.bankInformation.accountHolderName,
routingNumber: this.formGroup.value.bankInformation.routingNumber,
accountNumber: this.formGroup.value.bankInformation.accountNumber,
accountHolderType: this.formGroup.value.bankInformation.accountHolderType,
});
return {
type,
token,
};
}
if (this.usingCard) {
const token = await this.stripeService.setupCardPaymentMethod(clientSecret);
return {
type,
token,
};
}
}
if (this.usingPayPal) {
const token = await this.braintreeService.requestPaymentMethod();
return {
type,
token,
};
}
return null;
}
submit = async () => {
const tokenizedPaymentMethod = await this.tokenizePaymentMethod();
await this.onSubmit(tokenizedPaymentMethod);
};
ngOnInit(): void {
this.stripeService.loadStripe(
{
cardNumber: "#stripe-card-number",
cardExpiry: "#stripe-card-expiry",
cardCvc: "#stripe-card-cvc",
},
this.startWith === PaymentMethodType.Card,
);
if (this.showPayPal) {
this.braintreeService.loadBraintree(
"#braintree-container",
this.startWith === PaymentMethodType.PayPal,
);
}
this.formGroup
.get("paymentMethod")
.valueChanges.pipe(takeUntil(this.destroy$))
.subscribe((type) => {
this.onPaymentMethodChange(type);
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.stripeService.unloadStripe();
if (this.showPayPal) {
this.braintreeService.unloadBraintree();
}
}
private onPaymentMethodChange(type: PaymentMethodType): void {
switch (type) {
case PaymentMethodType.Card: {
this.stripeService.mountElements();
break;
}
case PaymentMethodType.PayPal: {
this.braintreeService.createDropin();
break;
}
}
}
private get selected(): PaymentMethodType {
return this.formGroup.value.paymentMethod;
}
protected get usingAccountCredit(): boolean {
return this.selected === PaymentMethodType.Credit;
}
protected get usingBankAccount(): boolean {
return this.selected === PaymentMethodType.BankAccount;
}
protected get usingCard(): boolean {
return this.selected === PaymentMethodType.Card;
}
protected get usingPayPal(): boolean {
return this.selected === PaymentMethodType.PayPal;
}
private get usingStripe(): boolean {
return this.usingBankAccount || this.usingCard;
}
}

View File

@@ -1,18 +0,0 @@
<app-callout type="warning" title="{{ 'verifyBankAccount' | i18n }}">
<p>{{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}</p>
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-form-field class="tw-mr-2 tw-w-40">
<bit-label>{{ "amountX" | i18n: "1" }}</bit-label>
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount1" />
<span bitPrefix>$0.</span>
</bit-form-field>
<bit-form-field class="tw-mr-2 tw-w-40">
<bit-label>{{ "amountX" | i18n: "2" }}</bit-label>
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount2" />
<span bitPrefix>$0.</span>
</bit-form-field>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "submit" | i18n }}
</button>
</form>
</app-callout>

View File

@@ -1,33 +0,0 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
@Component({
selector: "app-verify-bank-account",
templateUrl: "./verify-bank-account.component.html",
})
export class VerifyBankAccountComponent {
@Input() onSubmit?: (amount1: number, amount2: number) => Promise<void>;
@Output() verificationSubmitted = new EventEmitter();
protected formGroup = this.formBuilder.group({
amount1: new FormControl<number>(null, [
Validators.required,
Validators.min(0),
Validators.max(99),
]),
amount2: new FormControl<number>(null, [
Validators.required,
Validators.min(0),
Validators.max(99),
]),
});
constructor(private formBuilder: FormBuilder) {}
submit = async () => {
if (this.onSubmit) {
await this.onSubmit(this.formGroup.value.amount1, this.formGroup.value.amount2);
}
this.verificationSubmitted.emit();
};
}

View File

@@ -1,14 +1,5 @@
<div
#callout
class="callout callout-{{ calloutStyle }}"
[ngClass]="{ clickable: clickable }"
[attr.role]="useAlertRole ? 'alert' : null"
>
<h3 class="callout-heading" *ngIf="title">
<i class="bwi {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
<bit-callout [icon]="icon" [title]="title" [type]="$any(type)" [useAlertRole]="useAlertRole">
<div class="tw-pl-7 tw-m-0" *ngIf="enforcedPolicyOptions">
{{ enforcedPolicyMessage }}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
@@ -32,4 +23,4 @@
</ul>
</div>
<ng-content></ng-content>
</div>
</bit-callout>

View File

@@ -2,16 +2,19 @@ import { Component, Input, OnInit } from "@angular/core";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CalloutTypes } from "@bitwarden/components";
/**
* @deprecated use the CL's `CalloutComponent` instead
*/
@Component({
selector: "app-callout",
templateUrl: "callout.component.html",
})
export class CalloutComponent implements OnInit {
@Input() type = "info";
export class DeprecatedCalloutComponent implements OnInit {
@Input() type: CalloutTypes = "info";
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
@@ -26,34 +29,6 @@ export class CalloutComponent implements OnInit {
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect");
}
if (this.type === "warning" || this.type === "danger") {
if (this.type === "danger") {
this.calloutStyle = "danger";
}
if (this.title === undefined) {
this.title = this.i18nService.t("warning");
}
if (this.icon === undefined) {
this.icon = "bwi-exclamation-triangle";
}
} else if (this.type === "error") {
this.calloutStyle = "danger";
if (this.title === undefined) {
this.title = this.i18nService.t("error");
}
if (this.icon === undefined) {
this.icon = "bwi-error";
}
} else if (this.type === "tip") {
this.calloutStyle = "success";
if (this.title === undefined) {
this.title = this.i18nService.t("tip");
}
if (this.icon === undefined) {
this.icon = "bwi-lightbulb";
}
}
}
getPasswordScoreAlertDisplay() {

View File

@@ -7,13 +7,12 @@ import {
InvoicesComponent,
NoInvoicesComponent,
ManageTaxInformationComponent,
SelectPaymentMethodComponent,
VerifyBankAccountComponent,
} from "@bitwarden/angular/billing/components";
import {
AsyncActionsModule,
AutofocusDirective,
ButtonModule,
CalloutModule,
CheckboxModule,
DialogModule,
FormFieldModule,
@@ -29,7 +28,7 @@ import {
} from "@bitwarden/components";
import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component";
import { CalloutComponent } from "./components/callout.component";
import { DeprecatedCalloutComponent } from "./components/callout.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
import { ApiActionDirective } from "./directives/api-action.directive";
@@ -72,6 +71,7 @@ import { IconComponent } from "./vault/components/icon.component";
FormFieldModule,
SelectModule,
ButtonModule,
CalloutModule,
CheckboxModule,
DialogModule,
TypographyModule,
@@ -88,7 +88,7 @@ import { IconComponent } from "./vault/components/icon.component";
ApiActionDirective,
AutofocusDirective,
BoxRowDirective,
CalloutComponent,
DeprecatedCalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,
@@ -114,8 +114,6 @@ import { IconComponent } from "./vault/components/icon.component";
InvoicesComponent,
NoInvoicesComponent,
ManageTaxInformationComponent,
SelectPaymentMethodComponent,
VerifyBankAccountComponent,
TwoFactorIconComponent,
],
exports: [
@@ -125,7 +123,7 @@ import { IconComponent } from "./vault/components/icon.component";
AutofocusDirective,
ToastModule,
BoxRowDirective,
CalloutComponent,
DeprecatedCalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,
@@ -151,8 +149,6 @@ import { IconComponent } from "./vault/components/icon.component";
InvoicesComponent,
NoInvoicesComponent,
ManageTaxInformationComponent,
SelectPaymentMethodComponent,
VerifyBankAccountComponent,
TwoFactorIconComponent,
],
providers: [

View File

@@ -0,0 +1,83 @@
import { Injector, WritableSignal } from "@angular/core";
import type { FormGroup } from "@angular/forms";
import type { Jsonify, JsonValue } from "type-fest";
type Deserializer<T> = {
/**
* A function to use to safely convert your type from json to your expected type.
*
* @param jsonValue The JSON object representation of your state.
* @returns The fully typed version of your state.
*/
readonly deserializer?: (jsonValue: Jsonify<T>) => T;
};
type BaseCacheOptions<T> = {
/** A unique key for saving the cached value to state */
key: string;
/** An optional injector. Required if the method is called outside of an injection context. */
injector?: Injector;
} & (T extends JsonValue ? Deserializer<T> : Required<Deserializer<T>>);
export type SignalCacheOptions<T> = BaseCacheOptions<T> & {
/** The initial value for the signal. */
initialValue: T;
};
/** Extract the value type from a FormGroup */
type FormValue<TFormGroup extends FormGroup> = TFormGroup["value"];
export type FormCacheOptions<TFormGroup extends FormGroup> = BaseCacheOptions<
FormValue<TFormGroup>
> & {
control: TFormGroup;
};
/**
* Cache for temporary component state
*
* #### Implementations
* - browser extension popup: used to persist UI between popup open and close
* - all other clients: noop
*/
export abstract class ViewCacheService {
/**
* Create a signal from a previously cached value. Whenever the signal is updated, the new value is saved to the cache.
*
* Non browser extension implementations are noop and return a normal signal.
*
* @returns the created signal
*
* @example
* ```ts
* const mySignal = this.viewCacheService.signal({
* key: "popup-search-text"
* initialValue: ""
* });
* ```
*/
abstract signal<T>(options: SignalCacheOptions<T>): WritableSignal<T>;
/**
* - Initialize a form from a cached value
* - Save form value to cache when it changes
* - The form is marked dirty if the restored value is not `undefined`.
*
* Non browser extension implementations are noop and return the original form group.
*
* @example
* ```ts
* this.loginDetailsForm = this.viewCacheService.formGroup({
* key: "vault-login-details-form",
* control: this.formBuilder.group({
* username: [""],
* email: [""],
* })
* });
* ```
**/
abstract formGroup<TFormGroup extends FormGroup>(
options: FormCacheOptions<TFormGroup>,
): TFormGroup;
}

View File

@@ -0,0 +1,33 @@
import { Injectable, signal, WritableSignal } from "@angular/core";
import type { FormGroup } from "@angular/forms";
import {
FormCacheOptions,
SignalCacheOptions,
ViewCacheService,
} from "../abstractions/view-cache.service";
/**
* The functionality of the {@link ViewCacheService} is only needed in the browser extension popup,
* yet is provided to all clients to make sharing components easier.
*
* Non-extension clients use this noop implementation.
* */
@Injectable({
providedIn: "root",
})
export class NoopViewCacheService implements ViewCacheService {
/**
* Return a normal signal.
*/
signal<T>(options: SignalCacheOptions<T>): WritableSignal<T> {
return signal(options.initialValue);
}
/**
* Return the original form group.
**/
formGroup<TFormGroup extends FormGroup>(options: FormCacheOptions<TFormGroup>): TFormGroup {
return options.control;
}
}

View File

@@ -270,8 +270,10 @@ import {
} from "@bitwarden/vault-export-core";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
import { NoopViewCacheService } from "../platform/services/noop-view-cache.service";
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction";
import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
@@ -1292,6 +1294,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultRegistrationFinishService,
deps: [CryptoServiceAbstraction, AccountApiServiceAbstraction],
}),
safeProvider({
provide: ViewCacheService,
useExisting: NoopViewCacheService,
deps: [],
}),
safeProvider({
provide: LoginService,
useClass: DefaultLoginService,

View File

@@ -0,0 +1,32 @@
import { Type, inject } from "@angular/core";
import { Route, Routes } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { componentRouteSwap } from "../../utils/component-route-swap";
/**
* Helper function to swap between two components based on the GeneratorToolsModernization feature flag.
* @param defaultComponent - The current non-refreshed component to render.
* @param refreshedComponent - The new refreshed component to render.
* @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided.
* @param altOptions - The alt route options to apply to the alt component.
*/
export function generatorSwap(
defaultComponent: Type<any>,
refreshedComponent: Type<any>,
options: Route,
altOptions?: Route,
): Routes {
return componentRouteSwap(
defaultComponent,
refreshedComponent,
async () => {
const configService = inject(ConfigService);
return configService.getFeatureFlag(FeatureFlag.GeneratorToolsModernization);
},
options,
altOptions,
);
}