mirror of
https://github.com/bitwarden/browser
synced 2026-02-13 06:54:07 +00:00
Merge branch 'main' into ps/extension-refresh
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -36,7 +36,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
|
||||
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 {
|
||||
@@ -90,6 +90,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
protected authService: AuthService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected syncService: SyncService,
|
||||
protected toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -167,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;
|
||||
}
|
||||
|
||||
@@ -195,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();
|
||||
@@ -258,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,6 +24,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
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 {
|
||||
@@ -92,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() {
|
||||
@@ -135,7 +137,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;
|
||||
}
|
||||
|
||||
@@ -327,11 +333,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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="info">
|
||||
{{ "makeSureEnoughCredit" | i18n }}
|
||||
</app-callout>
|
||||
</ng-container>
|
||||
<button *ngIf="!!onSubmit" bitButton bitFormButton buttonType="primary" type="submit">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
InvoicesComponent,
|
||||
NoInvoicesComponent,
|
||||
ManageTaxInformationComponent,
|
||||
SelectPaymentMethodComponent,
|
||||
VerifyBankAccountComponent,
|
||||
} from "@bitwarden/angular/billing/components";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
@@ -116,8 +114,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
InvoicesComponent,
|
||||
NoInvoicesComponent,
|
||||
ManageTaxInformationComponent,
|
||||
SelectPaymentMethodComponent,
|
||||
VerifyBankAccountComponent,
|
||||
TwoFactorIconComponent,
|
||||
],
|
||||
exports: [
|
||||
@@ -153,8 +149,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
InvoicesComponent,
|
||||
NoInvoicesComponent,
|
||||
ManageTaxInformationComponent,
|
||||
SelectPaymentMethodComponent,
|
||||
VerifyBankAccountComponent,
|
||||
TwoFactorIconComponent,
|
||||
],
|
||||
providers: [
|
||||
|
||||
32
libs/angular/src/tools/generator/generator-swap.ts
Normal file
32
libs/angular/src/tools/generator/generator-swap.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CalloutModule,
|
||||
DialogModule,
|
||||
DialogService,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { ActiveClientVerificationOption } from "./active-client-verification-option.enum";
|
||||
@@ -58,6 +59,7 @@ export class UserVerificationDialogComponent {
|
||||
private userVerificationService: UserVerificationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -256,19 +258,27 @@ export class UserVerificationDialogComponent {
|
||||
|
||||
// Only pin should ever get here, but added this check to be safe.
|
||||
if (this.activeClientVerificationOption === ActiveClientVerificationOption.Pin) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("error"),
|
||||
this.i18nService.t("invalidPin"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("error"),
|
||||
message: this.i18nService.t("invalidPin"),
|
||||
});
|
||||
} else {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unexpectedError"),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Catch handles OTP and MP verification scenarios as those throw errors on verification failure instead of returning false like PIN and biometrics.
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +90,6 @@ import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
|
||||
import { EventRequest } from "../models/request/event.request";
|
||||
import { KdfRequest } from "../models/request/kdf.request";
|
||||
import { KeysRequest } from "../models/request/keys.request";
|
||||
import { OrganizationImportRequest } from "../models/request/organization-import.request";
|
||||
import { PreloginRequest } from "../models/request/prelogin.request";
|
||||
import { RegisterRequest } from "../models/request/register.request";
|
||||
import { StorageRequest } from "../models/request/storage.request";
|
||||
@@ -301,7 +300,6 @@ export abstract class ApiService {
|
||||
deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise<any>;
|
||||
|
||||
getSync: () => Promise<SyncResponse>;
|
||||
postPublicImportDirectory: (request: OrganizationImportRequest) => Promise<any>;
|
||||
|
||||
getSettingsDomains: () => Promise<DomainsResponse>;
|
||||
putSettingsDomains: (request: UpdateDomainsRequest) => Promise<DomainsResponse>;
|
||||
|
||||
@@ -23,6 +23,8 @@ export const EVENTS = {
|
||||
VISIBILITYCHANGE: "visibilitychange",
|
||||
MOUSEENTER: "mouseenter",
|
||||
MOUSELEAVE: "mouseleave",
|
||||
MOUSEUP: "mouseup",
|
||||
SUBMIT: "submit",
|
||||
} as const;
|
||||
|
||||
export const ClearClipboardDelay = {
|
||||
@@ -58,6 +60,8 @@ export const AUTOFILL_OVERLAY_HANDLE_REPOSITION = "autofill-overlay-handle-repos
|
||||
|
||||
export const UPDATE_PASSKEYS_HEADINGS_ON_SCROLL = "update-passkeys-headings-on-scroll";
|
||||
|
||||
export const AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT = "autofill-trigger-form-field-submit";
|
||||
|
||||
export const AutofillOverlayVisibility = {
|
||||
Off: 0,
|
||||
OnButtonClick: 1,
|
||||
@@ -101,3 +105,5 @@ export const ExtensionCommand = {
|
||||
} as const;
|
||||
|
||||
export type ExtensionCommandType = (typeof ExtensionCommand)[keyof typeof ExtensionCommand];
|
||||
|
||||
export const CLEAR_NOTIFICATION_LOGIN_DATA_DURATION = 60 * 1000; // 1 minute
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { TokenizedPaymentMethodRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-method.request";
|
||||
import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request";
|
||||
import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request";
|
||||
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { PaymentInformationResponse } from "@bitwarden/common/billing/models/response/payment-information.response";
|
||||
import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response";
|
||||
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
@@ -33,6 +33,8 @@ export abstract class BillingApiServiceAbstraction {
|
||||
organizationId: string,
|
||||
) => Promise<OrganizationBillingMetadataResponse>;
|
||||
|
||||
getOrganizationPaymentMethod: (organizationId: string) => Promise<PaymentMethodResponse>;
|
||||
|
||||
getPlans: () => Promise<ListResponse<PlanResponse>>;
|
||||
|
||||
getProviderClientInvoiceReport: (providerId: string, invoiceId: string) => Promise<string>;
|
||||
@@ -43,37 +45,31 @@ export abstract class BillingApiServiceAbstraction {
|
||||
|
||||
getProviderInvoices: (providerId: string) => Promise<InvoicesResponse>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
getProviderPaymentInformation: (providerId: string) => Promise<PaymentInformationResponse>;
|
||||
|
||||
getProviderSubscription: (providerId: string) => Promise<ProviderSubscriptionResponse>;
|
||||
|
||||
updateOrganizationPaymentMethod: (
|
||||
organizationId: string,
|
||||
request: UpdatePaymentMethodRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
updateOrganizationTaxInformation: (
|
||||
organizationId: string,
|
||||
request: ExpandedTaxInfoUpdateRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
updateProviderClientOrganization: (
|
||||
providerId: string,
|
||||
organizationId: string,
|
||||
request: UpdateClientOrganizationRequest,
|
||||
) => Promise<any>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
updateProviderPaymentMethod: (
|
||||
providerId: string,
|
||||
request: TokenizedPaymentMethodRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
updateProviderTaxInformation: (
|
||||
providerId: string,
|
||||
request: ExpandedTaxInfoUpdateRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
verifyProviderBankAccount: (
|
||||
providerId: string,
|
||||
verifyOrganizationBankAccount: (
|
||||
organizationId: string,
|
||||
request: VerifyBankAccountRequest,
|
||||
) => Promise<void>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from "./account/billing-account-profile-state.service";
|
||||
export * from "./billilng-api.service.abstraction";
|
||||
export * from "./billing-api.service.abstraction";
|
||||
export * from "./organization-billing.service";
|
||||
export * from "./payment-processors/braintree.service.abstraction";
|
||||
export * from "./payment-processors/stripe.service.abstraction";
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
export * from "./bank-account";
|
||||
export * from "./masked-payment-method";
|
||||
export * from "./tax-information";
|
||||
export * from "./tokenized-payment-method";
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { MaskedPaymentMethodResponse } from "@bitwarden/common/billing/models/response/masked-payment-method.response";
|
||||
|
||||
export class MaskedPaymentMethod {
|
||||
type: PaymentMethodType;
|
||||
description: string;
|
||||
needsVerification: boolean;
|
||||
|
||||
static from(response: MaskedPaymentMethodResponse | undefined) {
|
||||
if (response === undefined) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...response,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { TokenizedPaymentMethod } from "@bitwarden/common/billing/models/domain";
|
||||
|
||||
export class TokenizedPaymentMethodRequest {
|
||||
type: PaymentMethodType;
|
||||
token: string;
|
||||
|
||||
static From(tokenizedPaymentMethod: TokenizedPaymentMethod): TokenizedPaymentMethodRequest {
|
||||
const request = new TokenizedPaymentMethodRequest();
|
||||
request.type = tokenizedPaymentMethod.type;
|
||||
request.token = tokenizedPaymentMethod.token;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
|
||||
export type TokenizedPaymentMethod = {
|
||||
export class TokenizedPaymentSourceRequest {
|
||||
type: PaymentMethodType;
|
||||
token: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request";
|
||||
|
||||
export class UpdatePaymentMethodRequest {
|
||||
paymentSource: TokenizedPaymentSourceRequest;
|
||||
taxInformation: ExpandedTaxInfoUpdateRequest;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class OrganizationRisksSubscriptionFailureResponse extends BaseResponse {
|
||||
organizationId: string;
|
||||
risksSubscriptionFailure: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.organizationId = this.getResponseProperty("OrganizationId");
|
||||
this.risksSubscriptionFailure = this.getResponseProperty("RisksSubscriptionFailure");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,25 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
import { MaskedPaymentMethodResponse } from "./masked-payment-method.response";
|
||||
import { PaymentSourceResponse } from "./payment-source.response";
|
||||
import { TaxInfoResponse } from "./tax-info.response";
|
||||
|
||||
export class PaymentInformationResponse extends BaseResponse {
|
||||
export class PaymentMethodResponse extends BaseResponse {
|
||||
accountCredit: number;
|
||||
paymentMethod?: MaskedPaymentMethodResponse;
|
||||
paymentSource?: PaymentSourceResponse;
|
||||
subscriptionStatus?: string;
|
||||
taxInformation?: TaxInfoResponse;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.accountCredit = this.getResponseProperty("AccountCredit");
|
||||
|
||||
const paymentMethod = this.getResponseProperty("PaymentMethod");
|
||||
if (paymentMethod) {
|
||||
this.paymentMethod = new MaskedPaymentMethodResponse(paymentMethod);
|
||||
const paymentSource = this.getResponseProperty("PaymentSource");
|
||||
if (paymentSource) {
|
||||
this.paymentSource = new PaymentSourceResponse(paymentSource);
|
||||
}
|
||||
|
||||
this.subscriptionStatus = this.getResponseProperty("SubscriptionStatus");
|
||||
|
||||
const taxInformation = this.getResponseProperty("TaxInformation");
|
||||
if (taxInformation) {
|
||||
this.taxInformation = new TaxInfoResponse(taxInformation);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
||||
|
||||
export class MaskedPaymentMethodResponse extends BaseResponse {
|
||||
export class PaymentSourceResponse extends BaseResponse {
|
||||
type: PaymentMethodType;
|
||||
description: string;
|
||||
needsVerification: boolean;
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||
import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request";
|
||||
import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request";
|
||||
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
@@ -9,10 +12,7 @@ import { BillingApiServiceAbstraction } from "../../billing/abstractions";
|
||||
import { PaymentMethodType } from "../../billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request";
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
import { TokenizedPaymentMethodRequest } from "../../billing/models/request/tokenized-payment-method.request";
|
||||
import { VerifyBankAccountRequest } from "../../billing/models/request/verify-bank-account.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { PaymentInformationResponse } from "../../billing/models/response/payment-information.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request";
|
||||
@@ -85,6 +85,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return new OrganizationBillingMetadataResponse(r);
|
||||
}
|
||||
|
||||
async getOrganizationPaymentMethod(organizationId: string): Promise<PaymentMethodResponse> {
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/billing/payment-method",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
return new PaymentMethodResponse(response);
|
||||
}
|
||||
|
||||
async getPlans(): Promise<ListResponse<PlanResponse>> {
|
||||
const r = await this.apiService.send("GET", "/plans", null, false, true);
|
||||
return new ListResponse(r, PlanResponse);
|
||||
@@ -123,19 +136,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return new InvoicesResponse(response);
|
||||
}
|
||||
|
||||
async getProviderPaymentInformation(providerId: string): Promise<PaymentInformationResponse> {
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/payment-information",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
return new PaymentInformationResponse(response);
|
||||
}
|
||||
|
||||
async getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse> {
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
@@ -149,6 +149,32 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return new ProviderSubscriptionResponse(response);
|
||||
}
|
||||
|
||||
async updateOrganizationPaymentMethod(
|
||||
organizationId: string,
|
||||
request: UpdatePaymentMethodRequest,
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/billing/payment-method",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async updateOrganizationTaxInformation(
|
||||
organizationId: string,
|
||||
request: ExpandedTaxInfoUpdateRequest,
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/billing/tax-information",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async updateProviderClientOrganization(
|
||||
providerId: string,
|
||||
organizationId: string,
|
||||
@@ -163,19 +189,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async updateProviderPaymentMethod(
|
||||
providerId: string,
|
||||
request: TokenizedPaymentMethodRequest,
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
"PUT",
|
||||
"/providers/" + providerId + "/billing/payment-method",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async updateProviderTaxInformation(providerId: string, request: ExpandedTaxInfoUpdateRequest) {
|
||||
return await this.apiService.send(
|
||||
"PUT",
|
||||
@@ -186,10 +199,13 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async verifyProviderBankAccount(providerId: string, request: VerifyBankAccountRequest) {
|
||||
async verifyOrganizationBankAccount(
|
||||
organizationId: string,
|
||||
request: VerifyBankAccountRequest,
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
"POST",
|
||||
"/providers/" + providerId + "/billing/payment-method/verify-bank-account",
|
||||
"/organizations/" + organizationId + "/billing/payment-method/verify-bank-account",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
export enum EventSystemUser {
|
||||
SCIM = 1,
|
||||
DomainVerification = 2,
|
||||
PublicApi = 3,
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ export enum FeatureFlag {
|
||||
GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor",
|
||||
DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2",
|
||||
AccountDeprovisioning = "pm-10308-account-deprovisioning",
|
||||
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
|
||||
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
|
||||
}
|
||||
|
||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||
@@ -74,6 +76,8 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
|
||||
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
|
||||
[FeatureFlag.AccountDeprovisioning]: FALSE,
|
||||
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
|
||||
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
|
||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||
|
||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||
|
||||
@@ -117,6 +117,13 @@ export class StateService<
|
||||
state.accounts = {};
|
||||
}
|
||||
state.accounts[userId] = this.createAccount();
|
||||
|
||||
if (diskAccount == null) {
|
||||
// Return early because we can't set the diskAccount.profile
|
||||
// if diskAccount itself is null
|
||||
return state;
|
||||
}
|
||||
|
||||
state.accounts[userId].profile = diskAccount.profile;
|
||||
return state;
|
||||
});
|
||||
|
||||
@@ -102,7 +102,6 @@ import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
|
||||
import { EventRequest } from "../models/request/event.request";
|
||||
import { KdfRequest } from "../models/request/kdf.request";
|
||||
import { KeysRequest } from "../models/request/keys.request";
|
||||
import { OrganizationImportRequest } from "../models/request/organization-import.request";
|
||||
import { PreloginRequest } from "../models/request/prelogin.request";
|
||||
import { RegisterRequest } from "../models/request/register.request";
|
||||
import { StorageRequest } from "../models/request/storage.request";
|
||||
@@ -893,10 +892,6 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new ListResponse(r, PlanResponse);
|
||||
}
|
||||
|
||||
async postPublicImportDirectory(request: OrganizationImportRequest): Promise<any> {
|
||||
return this.send("POST", "/public/organization/import", request, true, false);
|
||||
}
|
||||
|
||||
async getTaxRates(): Promise<ListResponse<TaxRateResponse>> {
|
||||
const r = await this.send("GET", "/plans/sales-tax-rates/", null, true, true);
|
||||
return new ListResponse(r, TaxRateResponse);
|
||||
|
||||
@@ -257,8 +257,8 @@ describe("UserStateSubject", () => {
|
||||
|
||||
let actual: TestType = null;
|
||||
subject.subscribe({
|
||||
error: (value) => {
|
||||
actual = value;
|
||||
error: (value: unknown) => {
|
||||
actual = value as any;
|
||||
},
|
||||
});
|
||||
subject.error(expected);
|
||||
@@ -275,8 +275,8 @@ describe("UserStateSubject", () => {
|
||||
|
||||
let actual: TestType = null;
|
||||
subject.subscribe({
|
||||
error: (value) => {
|
||||
actual = value;
|
||||
error: (value: unknown) => {
|
||||
actual = value as any;
|
||||
},
|
||||
});
|
||||
subject.error("expectedError");
|
||||
@@ -415,8 +415,8 @@ describe("UserStateSubject", () => {
|
||||
|
||||
let error = false;
|
||||
subject.subscribe({
|
||||
error: (e) => {
|
||||
error = e;
|
||||
error: (e: unknown) => {
|
||||
error = e as any;
|
||||
},
|
||||
});
|
||||
singleUserId$.next(errorUserId);
|
||||
@@ -434,8 +434,8 @@ describe("UserStateSubject", () => {
|
||||
|
||||
let actual = false;
|
||||
subject.subscribe({
|
||||
error: (e) => {
|
||||
actual = e;
|
||||
error: (e: unknown) => {
|
||||
actual = e as any;
|
||||
},
|
||||
});
|
||||
singleUserId$.error(expected);
|
||||
@@ -454,8 +454,8 @@ describe("UserStateSubject", () => {
|
||||
|
||||
let actual = false;
|
||||
subject.subscribe({
|
||||
error: (e) => {
|
||||
actual = e;
|
||||
error: (e: unknown) => {
|
||||
actual = e as any;
|
||||
},
|
||||
});
|
||||
when$.error(expected);
|
||||
@@ -464,4 +464,14 @@ describe("UserStateSubject", () => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("userId", () => {
|
||||
it("returns the userId to which the subject is bound", () => {
|
||||
const state = new FakeSingleUserState<TestType>(SomeUser, { foo: "init" });
|
||||
const singleUserId$ = new Subject<UserId>();
|
||||
const subject = new UserStateSubject(state, { singleUserId$ });
|
||||
|
||||
expect(subject.userId).toEqual(SomeUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
ignoreElements,
|
||||
endWith,
|
||||
startWith,
|
||||
Observable,
|
||||
Subscription,
|
||||
} from "rxjs";
|
||||
import { Simplify } from "type-fest";
|
||||
|
||||
@@ -59,7 +61,10 @@ export type UserStateSubjectDependencies<State, Dependency> = Simplify<
|
||||
* @template State the state stored by the subject
|
||||
* @template Dependencies use-specific dependencies provided by the user.
|
||||
*/
|
||||
export class UserStateSubject<State, Dependencies = null> implements SubjectLike<State> {
|
||||
export class UserStateSubject<State, Dependencies = null>
|
||||
extends Observable<State>
|
||||
implements SubjectLike<State>
|
||||
{
|
||||
/**
|
||||
* Instantiates the user state subject
|
||||
* @param state the backing store of the subject
|
||||
@@ -76,6 +81,8 @@ export class UserStateSubject<State, Dependencies = null> implements SubjectLike
|
||||
private state: SingleUserState<State>,
|
||||
private dependencies: UserStateSubjectDependencies<State, Dependencies>,
|
||||
) {
|
||||
super();
|
||||
|
||||
// normalize dependencies
|
||||
const when$ = (this.dependencies.when$ ?? new BehaviorSubject(true)).pipe(
|
||||
distinctUntilChanged(),
|
||||
@@ -114,6 +121,12 @@ export class UserStateSubject<State, Dependencies = null> implements SubjectLike
|
||||
});
|
||||
}
|
||||
|
||||
/** The userId to which the subject is bound.
|
||||
*/
|
||||
get userId() {
|
||||
return this.state.userId;
|
||||
}
|
||||
|
||||
next(value: State) {
|
||||
this.input?.next(value);
|
||||
}
|
||||
@@ -130,7 +143,7 @@ export class UserStateSubject<State, Dependencies = null> implements SubjectLike
|
||||
* @param observer listening for events
|
||||
* @returns the subscription
|
||||
*/
|
||||
subscribe(observer: Partial<Observer<State>> | ((value: State) => void)): Unsubscribable {
|
||||
subscribe(observer?: Partial<Observer<State>> | ((value: State) => void) | null): Subscription {
|
||||
return this.output.subscribe(observer);
|
||||
}
|
||||
|
||||
|
||||
48
libs/common/src/tools/types.ts
Normal file
48
libs/common/src/tools/types.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Simplify } from "type-fest";
|
||||
|
||||
/** Constraints that are shared by all primitive field types */
|
||||
type PrimitiveConstraint = {
|
||||
/** presence indicates the field is required */
|
||||
required?: true;
|
||||
};
|
||||
|
||||
/** Constraints that are shared by string fields */
|
||||
type StringConstraints = {
|
||||
/** minimum string length. When absent, min length is 0. */
|
||||
minLength?: number;
|
||||
|
||||
/** maximum string length. When absent, max length is unbounded. */
|
||||
maxLength?: number;
|
||||
};
|
||||
|
||||
/** Constraints that are shared by number fields */
|
||||
type NumberConstraints = {
|
||||
/** minimum number value. When absent, min value is unbounded. */
|
||||
min?: number;
|
||||
|
||||
/** maximum number value. When absent, min value is unbounded. */
|
||||
max?: number;
|
||||
|
||||
/** presence indicates the field only accepts integer values */
|
||||
integer?: true;
|
||||
|
||||
/** requires the number be a multiple of the step value */
|
||||
step?: number;
|
||||
};
|
||||
|
||||
/** Utility type that transforms keys of T into their supported
|
||||
* validators.
|
||||
*/
|
||||
export type Constraints<T> = {
|
||||
[Key in keyof T]: Simplify<
|
||||
PrimitiveConstraint &
|
||||
(T[Key] extends string
|
||||
? StringConstraints
|
||||
: T[Key] extends number
|
||||
? NumberConstraints
|
||||
: never)
|
||||
>;
|
||||
};
|
||||
|
||||
/** utility type for methods that evaluate constraints generically. */
|
||||
export type AnyConstraint = PrimitiveConstraint & StringConstraints & NumberConstraints;
|
||||
48
libs/tools/generator/components/src/dependencies.ts
Normal file
48
libs/tools/generator/components/src/dependencies.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import {
|
||||
CardComponent,
|
||||
CheckboxModule,
|
||||
ColorPasswordModule,
|
||||
FormFieldModule,
|
||||
InputModule,
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
} from "@bitwarden/components";
|
||||
import { CredentialGeneratorService } from "@bitwarden/generator-core";
|
||||
|
||||
/** Shared module containing generator component dependencies */
|
||||
@NgModule({
|
||||
imports: [SectionComponent, SectionHeaderComponent, CardComponent],
|
||||
exports: [
|
||||
JslibModule,
|
||||
JslibServicesModule,
|
||||
FormFieldModule,
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
ColorPasswordModule,
|
||||
InputModule,
|
||||
CheckboxModule,
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
CardComponent,
|
||||
],
|
||||
providers: [
|
||||
safeProvider({
|
||||
provide: CredentialGeneratorService,
|
||||
useClass: CredentialGeneratorService,
|
||||
deps: [StateProvider, PolicyService],
|
||||
}),
|
||||
],
|
||||
declarations: [],
|
||||
})
|
||||
export class DependenciesModule {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { PassphraseSettingsComponent } from "./passphrase-settings.component";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<bit-section>
|
||||
<bit-section-header *ngIf="showHeader">
|
||||
<h6 bitTypography="h6">{{ "options" | i18n }}</h6>
|
||||
</bit-section-header>
|
||||
<form class="box" [formGroup]="settings" class="tw-container">
|
||||
<div class="tw-mb-4">
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "numWords" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
formControlName="numWords"
|
||||
id="num-words"
|
||||
type="number"
|
||||
[min]="minNumWords"
|
||||
[max]="maxNumWords"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</bit-card>
|
||||
</div>
|
||||
<div>
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "wordSeparator" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="wordSeparator" id="word-separator" type="text" />
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox formControlName="capitalize" id="capitalize" type="checkbox" />
|
||||
<bit-label>{{ "capitalize" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox formControlName="includeNumber" id="include-number" type="checkbox" />
|
||||
<bit-label>{{ "includeNumber" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</bit-card>
|
||||
</div>
|
||||
</form>
|
||||
</bit-section>
|
||||
@@ -0,0 +1,139 @@
|
||||
import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, skip, takeUntil, Subject } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
Generators,
|
||||
CredentialGeneratorService,
|
||||
PassphraseGenerationOptions,
|
||||
} from "@bitwarden/generator-core";
|
||||
|
||||
import { DependenciesModule } from "./dependencies";
|
||||
import { completeOnAccountSwitch, toValidators } from "./util";
|
||||
|
||||
const Controls = Object.freeze({
|
||||
numWords: "numWords",
|
||||
includeNumber: "includeNumber",
|
||||
capitalize: "capitalize",
|
||||
wordSeparator: "wordSeparator",
|
||||
});
|
||||
|
||||
/** Options group for passphrases */
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "bit-passphrase-settings",
|
||||
templateUrl: "passphrase-settings.component.html",
|
||||
imports: [DependenciesModule],
|
||||
})
|
||||
export class PassphraseSettingsComponent implements OnInit, OnDestroy {
|
||||
/** Instantiates the component
|
||||
* @param accountService queries user availability
|
||||
* @param generatorService settings and policy logic
|
||||
* @param formBuilder reactive form controls
|
||||
*/
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private generatorService: CredentialGeneratorService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
/** Binds the passphrase component to a specific user's settings.
|
||||
* When this input is not provided, the form binds to the active
|
||||
* user
|
||||
*/
|
||||
@Input()
|
||||
userId: UserId | null;
|
||||
|
||||
/** When `true`, an options header is displayed by the component. Otherwise, the header is hidden. */
|
||||
@Input()
|
||||
showHeader: boolean = true;
|
||||
|
||||
/** Emits settings updates and completes if the settings become unavailable.
|
||||
* @remarks this does not emit the initial settings. If you would like
|
||||
* to receive live settings updates including the initial update,
|
||||
* use `CredentialGeneratorService.settings$(...)` instead.
|
||||
*/
|
||||
@Output()
|
||||
readonly onUpdated = new EventEmitter<PassphraseGenerationOptions>();
|
||||
|
||||
protected settings = this.formBuilder.group({
|
||||
[Controls.numWords]: [Generators.Passphrase.settings.initial.numWords],
|
||||
[Controls.wordSeparator]: [Generators.Passphrase.settings.initial.wordSeparator],
|
||||
[Controls.capitalize]: [Generators.Passphrase.settings.initial.capitalize],
|
||||
[Controls.includeNumber]: [Generators.Passphrase.settings.initial.includeNumber],
|
||||
});
|
||||
|
||||
async ngOnInit() {
|
||||
const singleUserId$ = this.singleUserId$();
|
||||
const settings = await this.generatorService.settings(Generators.Passphrase, { singleUserId$ });
|
||||
|
||||
// skips reactive event emissions to break a subscription cycle
|
||||
settings.pipe(takeUntil(this.destroyed$)).subscribe((s) => {
|
||||
this.settings.patchValue(s, { emitEvent: false });
|
||||
});
|
||||
|
||||
// the first emission is the current value; subsequent emissions are updates
|
||||
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);
|
||||
|
||||
// dynamic policy enforcement
|
||||
this.generatorService
|
||||
.policy$(Generators.Passphrase, { userId$: singleUserId$ })
|
||||
.pipe(takeUntil(this.destroyed$))
|
||||
.subscribe((policy) => {
|
||||
this.settings
|
||||
.get(Controls.numWords)
|
||||
.setValidators(toValidators(Controls.numWords, Generators.Passphrase, policy));
|
||||
|
||||
this.settings
|
||||
.get(Controls.wordSeparator)
|
||||
.setValidators(toValidators(Controls.wordSeparator, Generators.Passphrase, policy));
|
||||
|
||||
// forward word boundaries to the template (can't do it through the rx form)
|
||||
// FIXME: move the boundary logic fully into the policy evaluator
|
||||
this.minNumWords =
|
||||
policy.numWords?.min ?? Generators.Passphrase.settings.constraints.numWords.min;
|
||||
this.maxNumWords =
|
||||
policy.numWords?.max ?? Generators.Passphrase.settings.constraints.numWords.max;
|
||||
|
||||
this.toggleEnabled(Controls.capitalize, !policy.policy.capitalize);
|
||||
this.toggleEnabled(Controls.includeNumber, !policy.policy.includeNumber);
|
||||
});
|
||||
|
||||
// now that outputs are set up, connect inputs
|
||||
this.settings.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(settings);
|
||||
}
|
||||
|
||||
/** attribute binding for numWords[min] */
|
||||
protected minNumWords: number;
|
||||
|
||||
/** attribute binding for numWords[max] */
|
||||
protected maxNumWords: number;
|
||||
|
||||
private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.settings.get(setting).enable();
|
||||
} else {
|
||||
this.settings.get(setting).disable();
|
||||
}
|
||||
}
|
||||
|
||||
private singleUserId$() {
|
||||
// FIXME: this branch should probably scan for the user and make sure
|
||||
// the account is unlocked
|
||||
if (this.userId) {
|
||||
return new BehaviorSubject(this.userId as UserId).asObservable();
|
||||
}
|
||||
|
||||
return this.accountService.activeAccount$.pipe(
|
||||
completeOnAccountSwitch(),
|
||||
takeUntil(this.destroyed$),
|
||||
);
|
||||
}
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
68
libs/tools/generator/components/src/util.ts
Normal file
68
libs/tools/generator/components/src/util.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ValidatorFn, Validators } from "@angular/forms";
|
||||
import { map, pairwise, pipe, skipWhile, startWith, takeWhile } from "rxjs";
|
||||
|
||||
import { AnyConstraint, Constraints } from "@bitwarden/common/tools/types";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CredentialGeneratorConfiguration } from "@bitwarden/generator-core";
|
||||
|
||||
export function completeOnAccountSwitch() {
|
||||
return pipe(
|
||||
map(({ id }: { id: UserId | null }) => id),
|
||||
skipWhile((id) => !id),
|
||||
startWith(null as UserId),
|
||||
pairwise(),
|
||||
takeWhile(([prev, next]) => (prev ?? next) === next),
|
||||
map(([_, id]) => id),
|
||||
);
|
||||
}
|
||||
|
||||
export function toValidators<Policy, Settings>(
|
||||
target: keyof Settings,
|
||||
configuration: CredentialGeneratorConfiguration<Settings, Policy>,
|
||||
policy?: Constraints<Settings>,
|
||||
) {
|
||||
const validators: Array<ValidatorFn> = [];
|
||||
|
||||
// widen the types to avoid typecheck issues
|
||||
const config: AnyConstraint = configuration.settings.constraints[target];
|
||||
const runtime: AnyConstraint = policy[target];
|
||||
|
||||
const required = getConstraint("required", config, runtime) ?? false;
|
||||
if (required) {
|
||||
validators.push(Validators.required);
|
||||
}
|
||||
|
||||
const maxLength = getConstraint("maxLength", config, runtime);
|
||||
if (maxLength !== undefined) {
|
||||
validators.push(Validators.maxLength(maxLength));
|
||||
}
|
||||
|
||||
const minLength = getConstraint("minLength", config, runtime);
|
||||
if (minLength !== undefined) {
|
||||
validators.push(Validators.minLength(config.minLength));
|
||||
}
|
||||
|
||||
const min = getConstraint("min", config, runtime);
|
||||
if (min !== undefined) {
|
||||
validators.push(Validators.min(min));
|
||||
}
|
||||
|
||||
const max = getConstraint("max", config, runtime);
|
||||
if (max === undefined) {
|
||||
validators.push(Validators.max(max));
|
||||
}
|
||||
|
||||
return validators;
|
||||
}
|
||||
|
||||
function getConstraint<Key extends keyof AnyConstraint>(
|
||||
key: Key,
|
||||
config: AnyConstraint,
|
||||
policy?: AnyConstraint,
|
||||
) {
|
||||
if (policy && key in policy) {
|
||||
return policy[key] ?? config[key];
|
||||
} else if (key in config) {
|
||||
return config[key];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { PassphraseGeneratorPolicy } from "../types";
|
||||
|
||||
/** The default options for password generation policy. */
|
||||
export const DisabledPassphraseGeneratorPolicy: PassphraseGeneratorPolicy = Object.freeze({
|
||||
minNumberWords: 0,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { PasswordGeneratorPolicy } from "../types";
|
||||
|
||||
/** The default options for password generation policy. */
|
||||
export const DisabledPasswordGeneratorPolicy: PasswordGeneratorPolicy = Object.freeze({
|
||||
minLength: 0,
|
||||
useUppercase: false,
|
||||
useLowercase: false,
|
||||
useNumbers: false,
|
||||
numberCount: 0,
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
});
|
||||
31
libs/tools/generator/core/src/data/generators.ts
Normal file
31
libs/tools/generator/core/src/data/generators.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { PASSPHRASE_SETTINGS } from "../strategies/storage";
|
||||
import { PassphraseGenerationOptions, PassphraseGeneratorPolicy } from "../types";
|
||||
import { CredentialGeneratorConfiguration } from "../types/credential-generator-configuration";
|
||||
|
||||
import { DefaultPassphraseBoundaries } from "./default-passphrase-boundaries";
|
||||
import { DefaultPassphraseGenerationOptions } from "./default-passphrase-generation-options";
|
||||
import { Policies } from "./policies";
|
||||
|
||||
const PASSPHRASE = Object.freeze({
|
||||
settings: {
|
||||
initial: DefaultPassphraseGenerationOptions,
|
||||
constraints: {
|
||||
numWords: {
|
||||
min: DefaultPassphraseBoundaries.numWords.min,
|
||||
max: DefaultPassphraseBoundaries.numWords.max,
|
||||
},
|
||||
wordSeparator: { maxLength: 1 },
|
||||
},
|
||||
account: PASSPHRASE_SETTINGS,
|
||||
},
|
||||
policy: Policies.Passphrase,
|
||||
} satisfies CredentialGeneratorConfiguration<
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy
|
||||
>);
|
||||
|
||||
/** Generator configurations */
|
||||
export const Generators = Object.freeze({
|
||||
/** Passphrase generator configuration */
|
||||
Passphrase: PASSPHRASE,
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./generators";
|
||||
export * from "./default-addy-io-options";
|
||||
export * from "./default-catchall-options";
|
||||
export * from "./default-duck-duck-go-options";
|
||||
@@ -11,8 +12,6 @@ export * from "./default-passphrase-generation-options";
|
||||
export * from "./default-password-generation-options";
|
||||
export * from "./default-subaddress-generator-options";
|
||||
export * from "./default-simple-login-options";
|
||||
export * from "./disabled-passphrase-generator-policy";
|
||||
export * from "./disabled-password-generator-policy";
|
||||
export * from "./forwarders";
|
||||
export * from "./integrations";
|
||||
export * from "./policies";
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
import { DisabledPassphraseGeneratorPolicy, DisabledPasswordGeneratorPolicy } from "../data";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import {
|
||||
passphraseLeastPrivilege,
|
||||
passwordLeastPrivilege,
|
||||
PassphraseGeneratorOptionsEvaluator,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
} from "../policies";
|
||||
import { PassphraseGeneratorPolicy, PasswordGeneratorPolicy, PolicyConfiguration } from "../types";
|
||||
import {
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy,
|
||||
PasswordGenerationOptions,
|
||||
PasswordGeneratorPolicy,
|
||||
PolicyConfiguration,
|
||||
} from "../types";
|
||||
|
||||
const PASSPHRASE = Object.freeze({
|
||||
disabledValue: DisabledPassphraseGeneratorPolicy,
|
||||
type: PolicyType.PasswordGenerator,
|
||||
disabledValue: Object.freeze({
|
||||
minNumberWords: 0,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
}),
|
||||
combine: passphraseLeastPrivilege,
|
||||
createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy),
|
||||
} as PolicyConfiguration<PassphraseGeneratorPolicy, PassphraseGeneratorOptionsEvaluator>);
|
||||
createEvaluatorV2: (policy) => new PassphraseGeneratorOptionsEvaluator(policy),
|
||||
} as PolicyConfiguration<PassphraseGeneratorPolicy, PassphraseGenerationOptions>);
|
||||
|
||||
const PASSWORD = Object.freeze({
|
||||
disabledValue: DisabledPasswordGeneratorPolicy,
|
||||
type: PolicyType.PasswordGenerator,
|
||||
disabledValue: Object.freeze({
|
||||
minLength: 0,
|
||||
useUppercase: false,
|
||||
useLowercase: false,
|
||||
useNumbers: false,
|
||||
numberCount: 0,
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
}),
|
||||
combine: passwordLeastPrivilege,
|
||||
createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy),
|
||||
} as PolicyConfiguration<PasswordGeneratorPolicy, PasswordGeneratorOptionsEvaluator>);
|
||||
} as PolicyConfiguration<PasswordGeneratorPolicy, PasswordGenerationOptions>);
|
||||
|
||||
/** Policy configurations */
|
||||
export const Policies = Object.freeze({
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// The root module interface has API stability guarantees
|
||||
export * from "./abstractions";
|
||||
export * from "./data";
|
||||
export { createRandomizer } from "./factories";
|
||||
export * from "./types";
|
||||
export { CredentialGeneratorService } from "./services";
|
||||
|
||||
// These internal interfacess are exposed for use by other generator modules
|
||||
// They are unstable and may change arbitrarily
|
||||
export * as engine from "./engine";
|
||||
export * as integration from "./integration";
|
||||
export * as policies from "./policies";
|
||||
export * as rx from "./rx";
|
||||
export * as services from "./services";
|
||||
export * as strategies from "./strategies";
|
||||
export * from "./types";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisabledPassphraseGeneratorPolicy, DefaultPassphraseBoundaries } from "../data";
|
||||
import { Policies, DefaultPassphraseBoundaries } from "../data";
|
||||
import { PassphraseGenerationOptions } from "../types";
|
||||
|
||||
import { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
|
||||
@@ -6,7 +6,7 @@ import { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-opti
|
||||
describe("Password generator options builder", () => {
|
||||
describe("constructor()", () => {
|
||||
it("should set the policy object to a copy of the input policy", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.minNumberWords = 10; // arbitrary change for deep equality check
|
||||
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
@@ -16,7 +16,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set default boundaries when a default policy is used", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.numWords).toEqual(DefaultPassphraseBoundaries.numWords);
|
||||
@@ -25,7 +25,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2])(
|
||||
"should use the default word boundaries when they are greater than `policy.minNumberWords` (= %i)",
|
||||
(minNumberWords) => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.minNumberWords = minNumberWords;
|
||||
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
@@ -37,7 +37,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([8, 12, 18])(
|
||||
"should use `policy.minNumberWords` (= %i) when it is greater than the default minimum words",
|
||||
(minNumberWords) => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.minNumberWords = minNumberWords;
|
||||
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
@@ -50,7 +50,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([150, 300, 9000])(
|
||||
"should use `policy.minNumberWords` (= %i) when it is greater than the default boundaries",
|
||||
(minNumberWords) => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.minNumberWords = minNumberWords;
|
||||
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
@@ -63,14 +63,14 @@ describe("Password generator options builder", () => {
|
||||
|
||||
describe("policyInEffect", () => {
|
||||
it("should return false when the policy has no effect", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true when the policy has a numWords greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.minNumberWords = DefaultPassphraseBoundaries.numWords.min + 1;
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -78,7 +78,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has capitalize enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.capitalize = true;
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -86,7 +86,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has includeNumber enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.includeNumber = true;
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -98,7 +98,7 @@ describe("Password generator options builder", () => {
|
||||
// All tests should freeze the options to ensure they are not modified
|
||||
|
||||
it("should set `capitalize` to `false` when the policy does not override it", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({});
|
||||
|
||||
@@ -108,7 +108,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `capitalize` to `true` when the policy overrides it", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.capitalize = true;
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ capitalize: false });
|
||||
@@ -119,7 +119,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `includeNumber` to false when the policy does not override it", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({});
|
||||
|
||||
@@ -129,7 +129,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `includeNumber` to true when the policy overrides it", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
policy.includeNumber = true;
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ includeNumber: false });
|
||||
@@ -140,7 +140,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `numWords` to the minimum value when it isn't supplied", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({});
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("Password generator options builder", () => {
|
||||
(numWords) => {
|
||||
expect(numWords).toBeLessThan(DefaultPassphraseBoundaries.numWords.min);
|
||||
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ numWords });
|
||||
|
||||
@@ -170,7 +170,7 @@ describe("Password generator options builder", () => {
|
||||
expect(numWords).toBeGreaterThanOrEqual(DefaultPassphraseBoundaries.numWords.min);
|
||||
expect(numWords).toBeLessThanOrEqual(DefaultPassphraseBoundaries.numWords.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ numWords });
|
||||
|
||||
@@ -185,7 +185,7 @@ describe("Password generator options builder", () => {
|
||||
(numWords) => {
|
||||
expect(numWords).toBeGreaterThan(DefaultPassphraseBoundaries.numWords.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ numWords });
|
||||
|
||||
@@ -196,7 +196,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
@@ -214,7 +214,7 @@ describe("Password generator options builder", () => {
|
||||
// All tests should freeze the options to ensure they are not modified
|
||||
|
||||
it("should return the input options without altering them", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ wordSeparator: "%" });
|
||||
|
||||
@@ -224,7 +224,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `wordSeparator` to '-' when it isn't supplied and there is no policy override", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({});
|
||||
|
||||
@@ -234,7 +234,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should leave `wordSeparator` as the empty string '' when it is the empty string", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ wordSeparator: "" });
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Passphrase.disabledValue);
|
||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { PolicyEvaluator } from "../abstractions";
|
||||
import { DefaultPassphraseGenerationOptions, DefaultPassphraseBoundaries } from "../data";
|
||||
import { Boundary, PassphraseGenerationOptions, PassphraseGeneratorPolicy } from "../types";
|
||||
@@ -5,7 +7,9 @@ import { Boundary, PassphraseGenerationOptions, PassphraseGeneratorPolicy } from
|
||||
/** Enforces policy for passphrase generation options.
|
||||
*/
|
||||
export class PassphraseGeneratorOptionsEvaluator
|
||||
implements PolicyEvaluator<PassphraseGeneratorPolicy, PassphraseGenerationOptions>
|
||||
implements
|
||||
PolicyEvaluator<PassphraseGeneratorPolicy, PassphraseGenerationOptions>,
|
||||
Constraints<PassphraseGenerationOptions>
|
||||
{
|
||||
// This design is not ideal, but it is a step towards a more robust passphrase
|
||||
// generator. Ideally, `sanitize` would be implemented on an options class,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { PolicyId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { DisabledPassphraseGeneratorPolicy } from "../data";
|
||||
import { Policies } from "../data";
|
||||
|
||||
import { passphraseLeastPrivilege } from "./passphrase-least-privilege";
|
||||
|
||||
@@ -26,17 +26,17 @@ describe("passphraseLeastPrivilege", () => {
|
||||
it("should return the accumulator when the policy type does not apply", () => {
|
||||
const policy = createPolicy({}, PolicyType.RequireSso);
|
||||
|
||||
const result = passphraseLeastPrivilege(DisabledPassphraseGeneratorPolicy, policy);
|
||||
const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPassphraseGeneratorPolicy);
|
||||
expect(result).toEqual(Policies.Passphrase.disabledValue);
|
||||
});
|
||||
|
||||
it("should return the accumulator when the policy is not enabled", () => {
|
||||
const policy = createPolicy({}, PolicyType.PasswordGenerator, false);
|
||||
|
||||
const result = passphraseLeastPrivilege(DisabledPassphraseGeneratorPolicy, policy);
|
||||
const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPassphraseGeneratorPolicy);
|
||||
expect(result).toEqual(Policies.Passphrase.disabledValue);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -46,8 +46,8 @@ describe("passphraseLeastPrivilege", () => {
|
||||
])("should take the %p from the policy", (input, value) => {
|
||||
const policy = createPolicy({ [input]: value });
|
||||
|
||||
const result = passphraseLeastPrivilege(DisabledPassphraseGeneratorPolicy, policy);
|
||||
const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual({ ...DisabledPassphraseGeneratorPolicy, [input]: value });
|
||||
expect(result).toEqual({ ...Policies.Passphrase.disabledValue, [input]: value });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DefaultPasswordBoundaries, DisabledPasswordGeneratorPolicy } from "../data";
|
||||
import { DefaultPasswordBoundaries, Policies } from "../data";
|
||||
import { PasswordGenerationOptions } from "../types";
|
||||
|
||||
import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator";
|
||||
@@ -8,7 +8,7 @@ describe("Password generator options builder", () => {
|
||||
|
||||
describe("constructor()", () => {
|
||||
it("should set the policy object to a copy of the input policy", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.minLength = 10; // arbitrary change for deep equality check
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -18,7 +18,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set default boundaries when a default policy is used", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe("Password generator options builder", () => {
|
||||
(minLength) => {
|
||||
expect(minLength).toBeLessThan(DefaultPasswordBoundaries.length.min);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.minLength = minLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -47,7 +47,7 @@ describe("Password generator options builder", () => {
|
||||
expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.min);
|
||||
expect(expectedLength).toBeLessThanOrEqual(DefaultPasswordBoundaries.length.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.minLength = expectedLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -62,7 +62,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedLength) => {
|
||||
expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.minLength = expectedLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -78,7 +78,7 @@ describe("Password generator options builder", () => {
|
||||
expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.min);
|
||||
expect(expectedMinDigits).toBeLessThanOrEqual(DefaultPasswordBoundaries.minDigits.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.numberCount = expectedMinDigits;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -93,7 +93,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedMinDigits) => {
|
||||
expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.max);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.numberCount = expectedMinDigits;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -113,7 +113,7 @@ describe("Password generator options builder", () => {
|
||||
DefaultPasswordBoundaries.minSpecialCharacters.max,
|
||||
);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.specialCount = expectedSpecialCharacters;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -132,7 +132,7 @@ describe("Password generator options builder", () => {
|
||||
DefaultPasswordBoundaries.minSpecialCharacters.max,
|
||||
);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.specialCount = expectedSpecialCharacters;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -151,7 +151,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedLength, numberCount, specialCount) => {
|
||||
expect(expectedLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.numberCount = numberCount;
|
||||
policy.specialCount = specialCount;
|
||||
|
||||
@@ -164,14 +164,14 @@ describe("Password generator options builder", () => {
|
||||
|
||||
describe("policyInEffect", () => {
|
||||
it("should return false when the policy has no effect", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true when the policy has a minlength greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.minLength = DefaultPasswordBoundaries.length.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has a number count greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.numberCount = DefaultPasswordBoundaries.minDigits.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -187,7 +187,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has a special character count greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.specialCount = DefaultPasswordBoundaries.minSpecialCharacters.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -195,7 +195,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has uppercase enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useUppercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -203,7 +203,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has lowercase enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useLowercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -211,7 +211,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has numbers enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useNumbers = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -219,7 +219,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should return true when the policy has special characters enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useSpecial = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -237,7 +237,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.uppercase` to '%s' when `policy.useUppercase` is false and `options.uppercase` is '%s'",
|
||||
(expectedUppercase, uppercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useUppercase = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, uppercase });
|
||||
@@ -251,7 +251,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.uppercase` (= %s) to true when `policy.useUppercase` is true",
|
||||
(uppercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useUppercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, uppercase });
|
||||
@@ -269,7 +269,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.lowercase` to '%s' when `policy.useLowercase` is false and `options.lowercase` is '%s'",
|
||||
(expectedLowercase, lowercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useLowercase = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, lowercase });
|
||||
@@ -283,7 +283,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.lowercase` (= %s) to true when `policy.useLowercase` is true",
|
||||
(lowercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useLowercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, lowercase });
|
||||
@@ -301,7 +301,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.number` to '%s' when `policy.useNumbers` is false and `options.number` is '%s'",
|
||||
(expectedNumber, number) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useNumbers = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number });
|
||||
@@ -315,7 +315,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.number` (= %s) to true when `policy.useNumbers` is true",
|
||||
(number) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useNumbers = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number });
|
||||
@@ -333,7 +333,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.special` to '%s' when `policy.useSpecial` is false and `options.special` is '%s'",
|
||||
(expectedSpecial, special) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useSpecial = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special });
|
||||
@@ -347,7 +347,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.special` (= %s) to true when `policy.useSpecial` is true",
|
||||
(special) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.useSpecial = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special });
|
||||
@@ -361,7 +361,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.length` (= %i) to the minimum it is less than the minimum length",
|
||||
(length) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeLessThan(builder.length.min);
|
||||
|
||||
@@ -376,7 +376,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([5, 10, 50, 100, 128])(
|
||||
"should not change `options.length` (= %i) when it is within the boundaries",
|
||||
(length) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeGreaterThanOrEqual(builder.length.min);
|
||||
expect(length).toBeLessThanOrEqual(builder.length.max);
|
||||
@@ -392,7 +392,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([129, 500, 9000])(
|
||||
"should set `options.length` (= %i) to the maximum length when it is exceeded",
|
||||
(length) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeGreaterThan(builder.length.max);
|
||||
|
||||
@@ -414,7 +414,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.number === %s` when `options.minNumber` (= %i) is set to a value greater than 0",
|
||||
(expectedNumber, minNumber) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, minNumber });
|
||||
|
||||
@@ -425,7 +425,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should set `options.minNumber` to the minimum value when `options.number` is true", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number: true });
|
||||
|
||||
@@ -435,7 +435,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `options.minNumber` to 0 when `options.number` is false", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number: false });
|
||||
|
||||
@@ -447,7 +447,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.minNumber` (= %i) to the minimum it is less than the minimum number",
|
||||
(minNumber) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.numberCount = 5; // arbitrary value greater than minNumber
|
||||
expect(minNumber).toBeLessThan(policy.numberCount);
|
||||
|
||||
@@ -463,7 +463,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 3, 5, 7, 9])(
|
||||
"should not change `options.minNumber` (= %i) when it is within the boundaries",
|
||||
(minNumber) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minNumber).toBeGreaterThanOrEqual(builder.minDigits.min);
|
||||
expect(minNumber).toBeLessThanOrEqual(builder.minDigits.max);
|
||||
@@ -479,7 +479,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([10, 20, 400])(
|
||||
"should set `options.minNumber` (= %i) to the maximum digit boundary when it is exceeded",
|
||||
(minNumber) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minNumber).toBeGreaterThan(builder.minDigits.max);
|
||||
|
||||
@@ -501,7 +501,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.special === %s` when `options.minSpecial` (= %i) is set to a value greater than 0",
|
||||
(expectedSpecial, minSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, minSpecial });
|
||||
|
||||
@@ -512,7 +512,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should set `options.minSpecial` to the minimum value when `options.special` is true", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special: true });
|
||||
|
||||
@@ -522,7 +522,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `options.minSpecial` to 0 when `options.special` is false", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special: false });
|
||||
|
||||
@@ -534,7 +534,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.minSpecial` (= %i) to the minimum it is less than the minimum special characters",
|
||||
(minSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
policy.specialCount = 5; // arbitrary value greater than minSpecial
|
||||
expect(minSpecial).toBeLessThan(policy.specialCount);
|
||||
|
||||
@@ -550,7 +550,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 3, 5, 7, 9])(
|
||||
"should not change `options.minSpecial` (= %i) when it is within the boundaries",
|
||||
(minSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minSpecial).toBeGreaterThanOrEqual(builder.minSpecialCharacters.min);
|
||||
expect(minSpecial).toBeLessThanOrEqual(builder.minSpecialCharacters.max);
|
||||
@@ -566,7 +566,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([10, 20, 400])(
|
||||
"should set `options.minSpecial` (= %i) to the maximum special character boundary when it is exceeded",
|
||||
(minSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minSpecial).toBeGreaterThan(builder.minSpecialCharacters.max);
|
||||
|
||||
@@ -579,7 +579,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
@@ -602,7 +602,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minLowercase === %i` when `options.lowercase` is %s",
|
||||
(expectedMinLowercase, lowercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ lowercase, ...defaultOptions });
|
||||
|
||||
@@ -618,7 +618,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minUppercase === %i` when `options.uppercase` is %s",
|
||||
(expectedMinUppercase, uppercase) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ uppercase, ...defaultOptions });
|
||||
|
||||
@@ -634,7 +634,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minNumber === %i` when `options.number` is %s and `options.minNumber` is not set",
|
||||
(expectedMinNumber, number) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ number, ...defaultOptions });
|
||||
|
||||
@@ -652,7 +652,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.number === %s` when `options.minNumber` is %i and `options.number` is not set",
|
||||
(expectedNumber, minNumber) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ minNumber, ...defaultOptions });
|
||||
|
||||
@@ -668,7 +668,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minSpecial === %i` when `options.special` is %s and `options.minSpecial` is not set",
|
||||
(special, expectedMinSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ special, ...defaultOptions });
|
||||
|
||||
@@ -686,7 +686,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.special === %s` when `options.minSpecial` is %i and `options.special` is not set",
|
||||
(minSpecial, expectedSpecial) => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ minSpecial, ...defaultOptions });
|
||||
|
||||
@@ -707,7 +707,7 @@ describe("Password generator options builder", () => {
|
||||
const sumOfMinimums = minLowercase + minUppercase + minNumber + minSpecial;
|
||||
expect(sumOfMinimums).toBeLessThan(DefaultPasswordBoundaries.length.min);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
minLowercase,
|
||||
@@ -732,7 +732,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedMinLength, minLowercase, minUppercase, minNumber, minSpecial) => {
|
||||
expect(expectedMinLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min);
|
||||
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
minLowercase,
|
||||
@@ -749,7 +749,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const policy = Object.assign({}, Policies.Password.disabledValue);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { PolicyId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { DisabledPasswordGeneratorPolicy } from "../data";
|
||||
import { Policies } from "../data";
|
||||
|
||||
import { passwordLeastPrivilege } from "./password-least-privilege";
|
||||
|
||||
@@ -26,17 +26,17 @@ describe("passwordLeastPrivilege", () => {
|
||||
it("should return the accumulator when the policy type does not apply", () => {
|
||||
const policy = createPolicy({}, PolicyType.RequireSso);
|
||||
|
||||
const result = passwordLeastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPasswordGeneratorPolicy);
|
||||
expect(result).toEqual(Policies.Password.disabledValue);
|
||||
});
|
||||
|
||||
it("should return the accumulator when the policy is not enabled", () => {
|
||||
const policy = createPolicy({}, PolicyType.PasswordGenerator, false);
|
||||
|
||||
const result = passwordLeastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPasswordGeneratorPolicy);
|
||||
expect(result).toEqual(Policies.Password.disabledValue);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -50,8 +50,8 @@ describe("passwordLeastPrivilege", () => {
|
||||
])("should take the %p from the policy", (input, value, expected) => {
|
||||
const policy = createPolicy({ [input]: value });
|
||||
|
||||
const result = passwordLeastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy);
|
||||
|
||||
expect(result).toEqual({ ...DisabledPasswordGeneratorPolicy, [expected]: value });
|
||||
expect(result).toEqual({ ...Policies.Password.disabledValue, [expected]: value });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,19 @@ export function mapPolicyToEvaluator<Policy, Evaluator>(
|
||||
);
|
||||
}
|
||||
|
||||
/** Maps an administrative console policy to a policy evaluator using the provided configuration.
|
||||
* @param configuration the configuration that constructs the evaluator.
|
||||
*/
|
||||
export function mapPolicyToEvaluatorV2<Policy, Evaluator>(
|
||||
configuration: PolicyConfiguration<Policy, Evaluator>,
|
||||
) {
|
||||
return pipe(
|
||||
reduceCollection(configuration.combine, configuration.disabledValue),
|
||||
distinctIfShallowMatch(),
|
||||
map(configuration.createEvaluatorV2),
|
||||
);
|
||||
}
|
||||
|
||||
/** Constructs a method that maps a policy to the default (no-op) policy. */
|
||||
export function newDefaultEvaluator<Target>() {
|
||||
return () => {
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
import { OrganizationId, PolicyId, UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { FakeStateProvider, FakeAccountService, awaitAsync } from "../../../../../common/spec";
|
||||
import { PolicyEvaluator } from "../abstractions";
|
||||
import { CredentialGeneratorConfiguration } from "../types";
|
||||
|
||||
import { CredentialGeneratorService } from "./credential-generator.service";
|
||||
|
||||
// arbitrary settings types
|
||||
type SomeSettings = { foo: string };
|
||||
type SomePolicy = { fooPolicy: boolean };
|
||||
|
||||
// settings storage location
|
||||
const SettingsKey = new UserKeyDefinition<SomeSettings>(GENERATOR_DISK, "SomeSettings", {
|
||||
deserializer: (value) => value,
|
||||
clearOn: [],
|
||||
});
|
||||
|
||||
// fake policy
|
||||
const policyService = mock<PolicyService>();
|
||||
const somePolicy = new Policy({
|
||||
data: { fooPolicy: true },
|
||||
type: PolicyType.PasswordGenerator,
|
||||
id: "" as PolicyId,
|
||||
organizationId: "" as OrganizationId,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// fake the configuration
|
||||
const SomeConfiguration: CredentialGeneratorConfiguration<SomeSettings, SomePolicy> = {
|
||||
settings: {
|
||||
initial: { foo: "initial" },
|
||||
constraints: { foo: {} },
|
||||
account: SettingsKey,
|
||||
},
|
||||
policy: {
|
||||
type: PolicyType.PasswordGenerator,
|
||||
disabledValue: {
|
||||
fooPolicy: false,
|
||||
},
|
||||
combine: (acc, policy) => {
|
||||
return { fooPolicy: acc.fooPolicy || policy.data.fooPolicy };
|
||||
},
|
||||
createEvaluator: () => {
|
||||
throw new Error("this should never be called");
|
||||
},
|
||||
createEvaluatorV2: (policy) => {
|
||||
return {
|
||||
foo: {},
|
||||
policy,
|
||||
policyInEffect: policy.fooPolicy,
|
||||
applyPolicy: (settings) => {
|
||||
return policy.fooPolicy ? { foo: `apply(${settings.foo})` } : settings;
|
||||
},
|
||||
sanitize: (settings) => {
|
||||
return policy.fooPolicy ? { foo: `sanitize(${settings.foo})` } : settings;
|
||||
},
|
||||
} as PolicyEvaluator<SomePolicy, SomeSettings> & Constraints<SomeSettings>;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// fake user information
|
||||
const SomeUser = "SomeUser" as UserId;
|
||||
const AnotherUser = "SomeOtherUser" as UserId;
|
||||
const accountService = new FakeAccountService({
|
||||
[SomeUser]: {
|
||||
name: "some user",
|
||||
email: "some.user@example.com",
|
||||
emailVerified: true,
|
||||
},
|
||||
[AnotherUser]: {
|
||||
name: "some other user",
|
||||
email: "some.other.user@example.com",
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
// fake state
|
||||
const stateProvider = new FakeStateProvider(accountService);
|
||||
|
||||
describe("CredentialGeneratorService", () => {
|
||||
beforeEach(async () => {
|
||||
await accountService.switchAccount(SomeUser);
|
||||
policyService.getAll$.mockImplementation(() => new BehaviorSubject([]).asObservable());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("settings$", () => {
|
||||
it("defaults to the configuration's initial settings if settings aren't found", async () => {
|
||||
await stateProvider.setUserState(SettingsKey, null, SomeUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
|
||||
const result = await firstValueFrom(generator.settings$(SomeConfiguration));
|
||||
|
||||
expect(result).toEqual(SomeConfiguration.settings.initial);
|
||||
});
|
||||
|
||||
it("reads from the active user's configuration-defined storage", async () => {
|
||||
const settings = { foo: "value" };
|
||||
await stateProvider.setUserState(SettingsKey, settings, SomeUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
|
||||
const result = await firstValueFrom(generator.settings$(SomeConfiguration));
|
||||
|
||||
expect(result).toEqual(settings);
|
||||
});
|
||||
|
||||
it("applies policy to the loaded settings", async () => {
|
||||
const settings = { foo: "value" };
|
||||
await stateProvider.setUserState(SettingsKey, settings, SomeUser);
|
||||
const policy$ = new BehaviorSubject([somePolicy]);
|
||||
policyService.getAll$.mockReturnValue(policy$);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
|
||||
const result = await firstValueFrom(generator.settings$(SomeConfiguration));
|
||||
|
||||
expect(result).toEqual({ foo: "sanitize(apply(value))" });
|
||||
});
|
||||
|
||||
it("follows changes to the active user", async () => {
|
||||
const someSettings = { foo: "value" };
|
||||
const anotherSettings = { foo: "another" };
|
||||
await stateProvider.setUserState(SettingsKey, someSettings, SomeUser);
|
||||
await stateProvider.setUserState(SettingsKey, anotherSettings, AnotherUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const results: any = [];
|
||||
const sub = generator.settings$(SomeConfiguration).subscribe((r) => results.push(r));
|
||||
|
||||
await accountService.switchAccount(AnotherUser);
|
||||
await awaitAsync();
|
||||
sub.unsubscribe();
|
||||
|
||||
const [someResult, anotherResult] = results;
|
||||
expect(someResult).toEqual(someSettings);
|
||||
expect(anotherResult).toEqual(anotherSettings);
|
||||
});
|
||||
|
||||
it("reads an arbitrary user's settings", async () => {
|
||||
await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser);
|
||||
const anotherSettings = { foo: "another" };
|
||||
await stateProvider.setUserState(SettingsKey, anotherSettings, AnotherUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId$ = new BehaviorSubject(AnotherUser).asObservable();
|
||||
|
||||
const result = await firstValueFrom(generator.settings$(SomeConfiguration, { userId$ }));
|
||||
|
||||
expect(result).toEqual(anotherSettings);
|
||||
});
|
||||
|
||||
it("follows changes to the arbitrary user", async () => {
|
||||
const someSettings = { foo: "value" };
|
||||
await stateProvider.setUserState(SettingsKey, someSettings, SomeUser);
|
||||
const anotherSettings = { foo: "another" };
|
||||
await stateProvider.setUserState(SettingsKey, anotherSettings, AnotherUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
const results: any = [];
|
||||
const sub = generator
|
||||
.settings$(SomeConfiguration, { userId$ })
|
||||
.subscribe((r) => results.push(r));
|
||||
|
||||
userId.next(AnotherUser);
|
||||
await awaitAsync();
|
||||
sub.unsubscribe();
|
||||
|
||||
const [someResult, anotherResult] = results;
|
||||
expect(someResult).toEqual(someSettings);
|
||||
expect(anotherResult).toEqual(anotherSettings);
|
||||
});
|
||||
|
||||
it("errors when the arbitrary user's stream errors", async () => {
|
||||
await stateProvider.setUserState(SettingsKey, null, SomeUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
let error = null;
|
||||
|
||||
generator.settings$(SomeConfiguration, { userId$ }).subscribe({
|
||||
error: (e: unknown) => {
|
||||
error = e;
|
||||
},
|
||||
});
|
||||
userId.error({ some: "error" });
|
||||
await awaitAsync();
|
||||
|
||||
expect(error).toEqual({ some: "error" });
|
||||
});
|
||||
|
||||
it("completes when the arbitrary user's stream completes", async () => {
|
||||
await stateProvider.setUserState(SettingsKey, null, SomeUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
let completed = false;
|
||||
|
||||
generator.settings$(SomeConfiguration, { userId$ }).subscribe({
|
||||
complete: () => {
|
||||
completed = true;
|
||||
},
|
||||
});
|
||||
userId.complete();
|
||||
await awaitAsync();
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
|
||||
it("ignores repeated arbitrary user emissions", async () => {
|
||||
await stateProvider.setUserState(SettingsKey, null, SomeUser);
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
let count = 0;
|
||||
|
||||
const sub = generator.settings$(SomeConfiguration, { userId$ }).subscribe({
|
||||
next: () => {
|
||||
count++;
|
||||
},
|
||||
});
|
||||
await awaitAsync();
|
||||
userId.next(SomeUser);
|
||||
await awaitAsync();
|
||||
userId.next(SomeUser);
|
||||
await awaitAsync();
|
||||
sub.unsubscribe();
|
||||
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("settings", () => {
|
||||
it("writes to the user's state", async () => {
|
||||
const singleUserId$ = new BehaviorSubject(SomeUser).asObservable();
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const subject = await generator.settings(SomeConfiguration, { singleUserId$ });
|
||||
|
||||
subject.next({ foo: "next value" });
|
||||
await awaitAsync();
|
||||
const result = await firstValueFrom(stateProvider.getUserState$(SettingsKey, SomeUser));
|
||||
|
||||
expect(result).toEqual({ foo: "next value" });
|
||||
});
|
||||
|
||||
it("waits for the user to become available", async () => {
|
||||
const singleUserId = new BehaviorSubject(null);
|
||||
const singleUserId$ = singleUserId.asObservable();
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
|
||||
let completed = false;
|
||||
const promise = generator.settings(SomeConfiguration, { singleUserId$ }).then((settings) => {
|
||||
completed = true;
|
||||
return settings;
|
||||
});
|
||||
await awaitAsync();
|
||||
expect(completed).toBeFalsy();
|
||||
singleUserId.next(SomeUser);
|
||||
const result = await promise;
|
||||
|
||||
expect(result.userId).toEqual(SomeUser);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy$", () => {
|
||||
it("creates a disabled policy evaluator when there is no policy", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId$ = new BehaviorSubject(SomeUser).asObservable();
|
||||
|
||||
const result = await firstValueFrom(generator.policy$(SomeConfiguration, { userId$ }));
|
||||
|
||||
expect(result.policy).toEqual(SomeConfiguration.policy.disabledValue);
|
||||
expect(result.policyInEffect).toBeFalsy();
|
||||
});
|
||||
|
||||
it("creates an active policy evaluator when there is a policy", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId$ = new BehaviorSubject(SomeUser).asObservable();
|
||||
const policy$ = new BehaviorSubject([somePolicy]);
|
||||
policyService.getAll$.mockReturnValue(policy$);
|
||||
|
||||
const result = await firstValueFrom(generator.policy$(SomeConfiguration, { userId$ }));
|
||||
|
||||
expect(result.policy).toEqual({ fooPolicy: true });
|
||||
expect(result.policyInEffect).toBeTruthy();
|
||||
});
|
||||
|
||||
it("follows policy emissions", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
const somePolicySubject = new BehaviorSubject([somePolicy]);
|
||||
policyService.getAll$.mockReturnValueOnce(somePolicySubject.asObservable());
|
||||
const emissions: any = [];
|
||||
const sub = generator
|
||||
.policy$(SomeConfiguration, { userId$ })
|
||||
.subscribe((policy) => emissions.push(policy));
|
||||
|
||||
// swap the active policy for an inactive policy
|
||||
somePolicySubject.next([]);
|
||||
await awaitAsync();
|
||||
sub.unsubscribe();
|
||||
const [someResult, anotherResult] = emissions;
|
||||
|
||||
expect(someResult.policy).toEqual({ fooPolicy: true });
|
||||
expect(someResult.policyInEffect).toBeTruthy();
|
||||
expect(anotherResult.policy).toEqual(SomeConfiguration.policy.disabledValue);
|
||||
expect(anotherResult.policyInEffect).toBeFalsy();
|
||||
});
|
||||
|
||||
it("follows user emissions", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable();
|
||||
const anotherPolicy$ = new BehaviorSubject([]).asObservable();
|
||||
policyService.getAll$.mockReturnValueOnce(somePolicy$).mockReturnValueOnce(anotherPolicy$);
|
||||
const emissions: any = [];
|
||||
const sub = generator
|
||||
.policy$(SomeConfiguration, { userId$ })
|
||||
.subscribe((policy) => emissions.push(policy));
|
||||
|
||||
// swapping the user invokes the return for `anotherPolicy$`
|
||||
userId.next(AnotherUser);
|
||||
await awaitAsync();
|
||||
sub.unsubscribe();
|
||||
const [someResult, anotherResult] = emissions;
|
||||
|
||||
expect(someResult.policy).toEqual({ fooPolicy: true });
|
||||
expect(someResult.policyInEffect).toBeTruthy();
|
||||
expect(anotherResult.policy).toEqual(SomeConfiguration.policy.disabledValue);
|
||||
expect(anotherResult.policyInEffect).toBeFalsy();
|
||||
});
|
||||
|
||||
it("errors when the user errors", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
const expectedError = { some: "error" };
|
||||
|
||||
let actualError: any = null;
|
||||
generator.policy$(SomeConfiguration, { userId$ }).subscribe({
|
||||
error: (e: unknown) => {
|
||||
actualError = e;
|
||||
},
|
||||
});
|
||||
userId.error(expectedError);
|
||||
await awaitAsync();
|
||||
|
||||
expect(actualError).toEqual(expectedError);
|
||||
});
|
||||
|
||||
it("completes when the user completes", async () => {
|
||||
const generator = new CredentialGeneratorService(stateProvider, policyService);
|
||||
const userId = new BehaviorSubject(SomeUser);
|
||||
const userId$ = userId.asObservable();
|
||||
|
||||
let completed = false;
|
||||
generator.policy$(SomeConfiguration, { userId$ }).subscribe({
|
||||
complete: () => {
|
||||
completed = true;
|
||||
},
|
||||
});
|
||||
userId.complete();
|
||||
await awaitAsync();
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
endWith,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
ignoreElements,
|
||||
map,
|
||||
mergeMap,
|
||||
Observable,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { SingleUserDependency, UserDependency } from "@bitwarden/common/tools/dependencies";
|
||||
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { PolicyEvaluator } from "../abstractions";
|
||||
import { mapPolicyToEvaluatorV2 } from "../rx";
|
||||
import { CredentialGeneratorConfiguration as Configuration } from "../types/credential-generator-configuration";
|
||||
|
||||
type Policy$Dependencies = UserDependency;
|
||||
type Settings$Dependencies = Partial<UserDependency>;
|
||||
// FIXME: once the modernization is complete, switch the type parameters
|
||||
// in `PolicyEvaluator<P, S>` and bake-in the constraints type.
|
||||
type Evaluator<Settings, Policy> = PolicyEvaluator<Policy, Settings> & Constraints<Settings>;
|
||||
|
||||
export class CredentialGeneratorService {
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private policyService: PolicyService,
|
||||
) {}
|
||||
|
||||
/** Get the settings for the provided configuration
|
||||
* @param configuration determines which generator's settings are loaded
|
||||
* @param dependencies.userId$ identifies the user to which the settings are bound.
|
||||
* If this parameter is not provided, the observable follows the active user and
|
||||
* may not complete.
|
||||
* @returns an observable that emits settings
|
||||
* @remarks the observable enforces policies on the settings
|
||||
*/
|
||||
settings$<Settings, Policy>(
|
||||
configuration: Configuration<Settings, Policy>,
|
||||
dependencies?: Settings$Dependencies,
|
||||
) {
|
||||
const userId$ = dependencies?.userId$ ?? this.stateProvider.activeUserId$;
|
||||
const completion$ = userId$.pipe(ignoreElements(), endWith(true));
|
||||
|
||||
const state$ = userId$.pipe(
|
||||
filter((userId) => !!userId),
|
||||
distinctUntilChanged(),
|
||||
switchMap((userId) => {
|
||||
const state$ = this.stateProvider
|
||||
.getUserState$(configuration.settings.account, userId)
|
||||
.pipe(takeUntil(completion$));
|
||||
|
||||
return state$;
|
||||
}),
|
||||
map((settings) => settings ?? structuredClone(configuration.settings.initial)),
|
||||
);
|
||||
|
||||
const settings$ = combineLatest([state$, this.policy$(configuration, { userId$ })]).pipe(
|
||||
map(([settings, policy]) => {
|
||||
// FIXME: create `onLoadApply` that wraps these operations
|
||||
const applied = policy.applyPolicy(settings);
|
||||
const sanitized = policy.sanitize(applied);
|
||||
return sanitized;
|
||||
}),
|
||||
);
|
||||
|
||||
return settings$;
|
||||
}
|
||||
|
||||
/** Get a subject bound to a specific user's settings
|
||||
* @param configuration determines which generator's settings are loaded
|
||||
* @param dependencies.singleUserId$ identifies the user to which the settings are bound
|
||||
* @returns a promise that resolves with the subject once
|
||||
* `dependencies.singleUserId$` becomes available.
|
||||
* @remarks the subject enforces policy for the settings
|
||||
*/
|
||||
async settings<Settings, Policy>(
|
||||
configuration: Configuration<Settings, Policy>,
|
||||
dependencies: SingleUserDependency,
|
||||
) {
|
||||
const userId = await firstValueFrom(
|
||||
dependencies.singleUserId$.pipe(filter((userId) => !!userId)),
|
||||
);
|
||||
const state = this.stateProvider.getUser(userId, configuration.settings.account);
|
||||
|
||||
// FIXME: apply policy to the settings - this should happen *within* the subject.
|
||||
// Note that policies could be evaluated when the settings are saved or when they
|
||||
// are loaded. The existing subject presently could only apply settings on save
|
||||
// (by wiring the policy in as a dependency and applying with "nextState"), and
|
||||
// even that has a limitation since arbitrary dependencies do not trigger state
|
||||
// emissions.
|
||||
const subject = new UserStateSubject(state, dependencies);
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
/** Get the policy for the provided configuration
|
||||
* @param dependencies.userId$ determines which user's policy is loaded
|
||||
* @returns an observable that emits the policy once `dependencies.userId$`
|
||||
* and the policy become available.
|
||||
*/
|
||||
policy$<Settings, Policy>(
|
||||
configuration: Configuration<Settings, Policy>,
|
||||
dependencies: Policy$Dependencies,
|
||||
): Observable<Evaluator<Settings, Policy>> {
|
||||
const completion$ = dependencies.userId$.pipe(ignoreElements(), endWith(true));
|
||||
|
||||
const policy$ = dependencies.userId$.pipe(
|
||||
mergeMap((userId) => {
|
||||
// complete policy emissions otherwise `mergeMap` holds `policy$` open indefinitely
|
||||
const policies$ = this.policyService
|
||||
.getAll$(configuration.policy.type, userId)
|
||||
.pipe(takeUntil(completion$));
|
||||
return policies$;
|
||||
}),
|
||||
mapPolicyToEvaluatorV2(configuration.policy),
|
||||
);
|
||||
|
||||
return policy$;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { DefaultGeneratorService } from "./default-generator.service";
|
||||
export { CredentialGeneratorService } from "./credential-generator.service";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { DefaultPassphraseGenerationOptions, DisabledPassphraseGeneratorPolicy } from "../data";
|
||||
import { DefaultPassphraseGenerationOptions, Policies } from "../data";
|
||||
import { PasswordRandomizer } from "../engine";
|
||||
import { PassphraseGeneratorOptionsEvaluator } from "../policies";
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("Password generation strategy", () => {
|
||||
const evaluator = await firstValueFrom(evaluator$);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PassphraseGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject(DisabledPassphraseGeneratorPolicy);
|
||||
expect(evaluator.policy).toMatchObject(Policies.Passphrase.disabledValue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { DefaultPasswordGenerationOptions, DisabledPasswordGeneratorPolicy } from "../data";
|
||||
import { DefaultPasswordGenerationOptions, Policies } from "../data";
|
||||
import { PasswordRandomizer } from "../engine";
|
||||
import { PasswordGeneratorOptionsEvaluator } from "../policies";
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("Password generation strategy", () => {
|
||||
const evaluator = await firstValueFrom(evaluator$);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject(DisabledPasswordGeneratorPolicy);
|
||||
expect(evaluator.policy).toMatchObject(Policies.Password.disabledValue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { PolicyConfiguration } from "../types";
|
||||
|
||||
export type CredentialGeneratorConfiguration<Settings, Policy> = {
|
||||
settings: {
|
||||
/** value used when an account's settings haven't been initialized */
|
||||
initial: Readonly<Partial<Settings>>;
|
||||
|
||||
constraints: Constraints<Settings>;
|
||||
|
||||
/** storage location for account-global settings */
|
||||
account: UserKeyDefinition<Settings>;
|
||||
};
|
||||
|
||||
/** defines how to construct policy for this settings instance */
|
||||
policy: PolicyConfiguration<Policy, Settings>;
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./boundary";
|
||||
export * from "./catchall-generator-options";
|
||||
export * from "./credential-generator-configuration";
|
||||
export * from "./eff-username-generator-options";
|
||||
export * from "./forwarder-options";
|
||||
export * from "./generator-options";
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { PolicyEvaluator } from "../abstractions";
|
||||
|
||||
/** Determines how to construct a password generator policy */
|
||||
export type PolicyConfiguration<Policy, Evaluator> = {
|
||||
export type PolicyConfiguration<Policy, Settings> = {
|
||||
type: PolicyType;
|
||||
|
||||
/** The value of the policy when it is not in effect. */
|
||||
disabledValue: Policy;
|
||||
|
||||
@@ -12,5 +18,12 @@ export type PolicyConfiguration<Policy, Evaluator> = {
|
||||
|
||||
/** Converts policy service data into an actionable policy.
|
||||
*/
|
||||
createEvaluator: (policy: Policy) => Evaluator;
|
||||
createEvaluator: (policy: Policy) => PolicyEvaluator<Policy, Settings>;
|
||||
|
||||
/** Converts policy service data into an actionable policy.
|
||||
* @remarks this version includes constraints needed for the reactive forms;
|
||||
* it was introduced so that the constraints can be incrementally introduced
|
||||
* as the new UI is built.
|
||||
*/
|
||||
createEvaluatorV2?: (policy: Policy) => PolicyEvaluator<Policy, Settings> & Constraints<Settings>;
|
||||
};
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
GeneratorService,
|
||||
DefaultPassphraseGenerationOptions,
|
||||
DefaultPasswordGenerationOptions,
|
||||
DisabledPassphraseGeneratorPolicy,
|
||||
DisabledPasswordGeneratorPolicy,
|
||||
Policies,
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy,
|
||||
PasswordGenerationOptions,
|
||||
@@ -39,7 +38,7 @@ const PasswordGeneratorOptionsEvaluator = policies.PasswordGeneratorOptionsEvalu
|
||||
|
||||
function createPassphraseGenerator(
|
||||
options: PassphraseGenerationOptions = {},
|
||||
policy: PassphraseGeneratorPolicy = DisabledPassphraseGeneratorPolicy,
|
||||
policy: PassphraseGeneratorPolicy = Policies.Passphrase.disabledValue,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PassphraseGenerationOptions, PassphraseGeneratorPolicy>>({
|
||||
@@ -64,7 +63,7 @@ function createPassphraseGenerator(
|
||||
|
||||
function createPasswordGenerator(
|
||||
options: PasswordGenerationOptions = {},
|
||||
policy: PasswordGeneratorPolicy = DisabledPasswordGeneratorPolicy,
|
||||
policy: PasswordGeneratorPolicy = Policies.Password.disabledValue,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PasswordGenerationOptions, PasswordGeneratorPolicy>>({
|
||||
|
||||
Reference in New Issue
Block a user