1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Merged with master and fixed conflicts

This commit is contained in:
gbubemismith
2023-05-08 12:16:25 -04:00
1131 changed files with 52396 additions and 51717 deletions

View File

@@ -9,12 +9,13 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { KdfType } from "@bitwarden/common/enums/kdfType";
import { KdfType } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
import { PasswordColorText } from "../../shared/components/password-strength/password-strength.component";
@Directive()
@@ -42,7 +43,8 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected stateService: StateService
protected stateService: StateService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -157,37 +159,34 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
const weakPassword = strengthResult != null && strengthResult.score < 3;
if (weakPassword && this.leakedPassword) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakAndBreachedMasterPasswordDesc"),
this.i18nService.t("weakAndExposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakAndExposedMasterPassword" },
content: { key: "weakAndBreachedMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return false;
}
} else {
if (weakPassword) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return false;
}
}
if (this.leakedPassword) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("exposedMasterPasswordDesc"),
this.i18nService.t("exposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "exposedMasterPassword" },
content: { key: "exposedMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return false;
}
@@ -198,12 +197,13 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: SimpleDialogType.WARNING,
});
if (confirmed) {
this.messagingService.send("logout");
}

View File

@@ -1,6 +1,6 @@
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Subject } from "rxjs";
import { firstValueFrom, Subject } from "rxjs";
import { concatMap, take, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -13,13 +13,20 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions";
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class LockComponent implements OnInit, OnDestroy {
@@ -29,18 +36,21 @@ export class LockComponent implements OnInit, OnDestroy {
email: string;
pinLock = false;
webVaultHostname = "";
formPromise: Promise<any>;
formPromise: Promise<MasterPasswordPolicyResponse>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
hideInput: boolean;
protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
protected onSuccessfulSubmit: () => Promise<void>;
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions = undefined;
private destroy$ = new Subject<void>();
constructor(
@@ -56,7 +66,11 @@ export class LockComponent implements OnInit, OnDestroy {
protected apiService: ApiService,
protected logService: LogService,
private keyConnectorService: KeyConnectorService,
protected ngZone: NgZone
protected ngZone: NgZone,
protected policyApiService: PolicyApiServiceAbstraction,
protected policyService: InternalPolicyService,
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -84,12 +98,13 @@ export class LockComponent implements OnInit, OnDestroy {
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: SimpleDialogType.WARNING,
});
if (confirmed) {
this.messagingService.send("logout");
}
@@ -103,7 +118,7 @@ export class LockComponent implements OnInit, OnDestroy {
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
if (success) {
await this.doContinue();
await this.doContinue(false);
}
return success;
@@ -208,7 +223,8 @@ export class LockComponent implements OnInit, OnDestroy {
request.masterPasswordHash = serverKeyHash;
try {
this.formPromise = this.apiService.postAccountVerifyPassword(request);
await this.formPromise;
const response = await this.formPromise;
this.enforcedMasterPasswordOptions = MasterPasswordPolicyOptions.fromResponse(response);
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
@@ -239,18 +255,39 @@ export class LockComponent implements OnInit, OnDestroy {
await this.cryptoService.encrypt(key.key, pinKey)
);
}
await this.setKeyAndContinue(key);
await this.setKeyAndContinue(key, true);
}
private async setKeyAndContinue(key: SymmetricCryptoKey) {
private async setKeyAndContinue(key: SymmetricCryptoKey, evaluatePasswordAfterUnlock = false) {
await this.cryptoService.setKey(key);
await this.doContinue();
await this.doContinue(evaluatePasswordAfterUnlock);
}
private async doContinue() {
private async doContinue(evaluatePasswordAfterUnlock: boolean) {
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send("unlocked");
if (evaluatePasswordAfterUnlock) {
try {
// If we do not have any saved policies, attempt to load them from the service
if (this.enforcedMasterPasswordOptions == undefined) {
this.enforcedMasterPasswordOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$()
);
}
if (this.requirePasswordChange()) {
await this.stateService.setForcePasswordResetReason(
ForceResetPasswordReason.WeakMasterPassword
);
this.router.navigate([this.forcePasswordResetRoute]);
return;
}
} catch (e) {
// Do not prevent unlock if there is an error evaluating policies
this.logService.error(e);
}
}
if (this.onSuccessfulSubmit != null) {
await this.onSuccessfulSubmit();
} else if (this.router != null) {
@@ -283,4 +320,28 @@ export class LockComponent implements OnInit, OnDestroy {
webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
/**
* Checks if the master password meets the enforced policy requirements
* If not, returns false
*/
private requirePasswordChange(): boolean {
if (
this.enforcedMasterPasswordOptions == undefined ||
!this.enforcedMasterPasswordOptions.enforceOnLogin
) {
return false;
}
const passwordStrength = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.email
)?.score;
return !this.policyService.evaluateMasterPassword(
passwordStrength,
this.masterPassword,
this.enforcedMasterPasswordOptions
);
}
}

View File

@@ -16,6 +16,7 @@ import { ValidationService } from "@bitwarden/common/abstractions/validation.ser
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { PasswordlessLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
import { PasswordlessCreateAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-create-auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
@@ -73,7 +74,7 @@ export class LoginWithDeviceComponent
//gets signalR push notification
this.authService
.getPushNotifcationObs$()
.getPushNotificationObs$()
.pipe(takeUntil(this.destroy$))
.subscribe((id) => {
this.confirmResponse(id);
@@ -125,7 +126,7 @@ export class LoginWithDeviceComponent
return;
}
const credentials = await this.buildLoginCredntials(requestId, response);
const credentials = await this.buildLoginCredentials(requestId, response);
const loginResponse = await this.authService.logIn(credentials);
if (loginResponse.requiresTwoFactor) {
@@ -134,7 +135,7 @@ export class LoginWithDeviceComponent
} else {
this.router.navigate([this.twoFactorRoute]);
}
} else if (loginResponse.forcePasswordReset) {
} else if (loginResponse.forcePasswordReset != ForceResetPasswordReason.None) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
@@ -188,7 +189,7 @@ export class LoginWithDeviceComponent
);
}
private async buildLoginCredntials(
private async buildLoginCredentials(
requestId: string,
response: AuthRequestResponse
): Promise<PasswordlessLogInCredentials> {

View File

@@ -1,4 +1,4 @@
import { Directive, NgZone, OnInit } from "@angular/core";
import { Directive, ElementRef, NgZone, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { take } from "rxjs/operators";
@@ -18,6 +18,7 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { PasswordLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
import { Utils } from "@bitwarden/common/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@@ -26,6 +27,8 @@ import { CaptchaProtectedComponent } from "./captcha-protected.component";
@Directive()
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
@ViewChild("masterPasswordInput", { static: true }) masterPasswordInput: ElementRef;
showPassword = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
@@ -142,15 +145,13 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
} else {
this.router.navigate([this.twoFactorRoute]);
}
} else if (response.forcePasswordReset) {
} else if (response.forcePasswordReset != ForceResetPasswordReason.None) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
@@ -238,10 +239,24 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
toggleValidateEmail(value: boolean) {
this.validatedEmail = value;
if (!value) {
// Reset master password only when going from validated to not validated (not you btn press)
if (!this.validatedEmail) {
// Reset master password only when going from validated to not validated
// so that autofill can work properly
this.formGroup.controls.masterPassword.reset();
} else {
// Mark MP as untouched so that, when users enter email and hit enter,
// the MP field doesn't load with validation errors
this.formGroup.controls.masterPassword.markAsUntouched();
// When email is validated, focus on master password after
// waiting for input to be rendered
if (this.ngZone.isStable) {
this.masterPasswordInput?.nativeElement?.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
this.masterPasswordInput?.nativeElement?.focus();
});
}
}
}

View File

@@ -9,6 +9,8 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class RemovePasswordComponent implements OnInit {
actionPromise: Promise<void | boolean>;
@@ -26,7 +28,8 @@ export class RemovePasswordComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private keyConnectorService: KeyConnectorService,
private organizationApiService: OrganizationApiServiceAbstraction
private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -55,13 +58,12 @@ export class RemovePasswordComponent implements OnInit {
}
async leave() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
this.organization.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: this.organization.name,
content: { key: "leaveOrganizationConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}

View File

@@ -11,6 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { SsoLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
import { Utils } from "@bitwarden/common/misc/utils";
@@ -185,7 +186,7 @@ export class SsoComponent {
const response = await this.formPromise;
if (response.requiresTwoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
await this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute], {
queryParams: {
@@ -196,7 +197,7 @@ export class SsoComponent {
}
} else if (response.resetMasterPassword) {
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate();
await this.onSuccessfulLoginChangePasswordNavigate();
} else {
this.router.navigate([this.changePasswordRoute], {
queryParams: {
@@ -204,20 +205,18 @@ export class SsoComponent {
},
});
}
} else if (response.forcePasswordReset) {
} else if (response.forcePasswordReset !== ForceResetPasswordReason.None) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
await this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
await this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
await this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}

View File

@@ -15,6 +15,7 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
@@ -196,24 +197,22 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.captchaToken
);
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.handleCaptchaRequired(response)) {
return;
}
if (this.onSuccessfulLogin != null) {
this.loginService.clearValues();
this.onSuccessfulLogin();
await this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = "set-password";
}
if (response.forcePasswordReset) {
if (response.forcePasswordReset !== ForceResetPasswordReason.None) {
this.successRoute = "update-temp-password";
}
if (this.onSuccessfulLoginNavigate != null) {
this.loginService.clearValues();
this.onSuccessfulLoginNavigate();
await this.onSuccessfulLoginNavigate();
} else {
this.loginService.clearValues();
this.router.navigate([this.successRoute], {
@@ -233,10 +232,20 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
return;
}
if (this.authService.email == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sessionTimeout")
);
return;
}
try {
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
request.ssoEmail2FaSessionToken = this.authService.ssoEmail2FaSessionToken;
request.deviceIdentifier = await this.appIdService.getAppId();
request.authRequestAccessCode = this.authService.accessCode;
request.authRequestId = this.authService.authRequestId;

View File

@@ -18,6 +18,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { Verification } from "@bitwarden/common/types/verification";
import { DialogServiceAbstraction } from "../../services/dialog";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@Directive()
@@ -41,7 +43,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
private apiService: ApiService,
stateService: StateService,
private userVerificationService: UserVerificationService,
private logService: LogService
private logService: LogService,
dialogService: DialogServiceAbstraction
) {
super(
i18nService,
@@ -50,7 +53,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
passwordGenerationService,
platformUtilsService,
policyService,
stateService
stateService,
dialogService
);
}

View File

@@ -1,4 +1,5 @@
import { Directive } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@@ -7,14 +8,21 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { Verification } from "@bitwarden/common/types/verification";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogServiceAbstraction } from "../../services/dialog";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@Directive()
@@ -23,9 +31,18 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword = false;
reason: ForceResetPasswordReason = ForceResetPasswordReason.None;
verification: Verification = {
type: VerificationType.MasterPassword,
secret: "",
};
onSuccessfulChangePassword: () => Promise<any>;
get requireCurrentPassword(): boolean {
return this.reason === ForceResetPasswordReason.WeakMasterPassword;
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
@@ -36,7 +53,10 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
private apiService: ApiService,
stateService: StateService,
private syncService: SyncService,
private logService: LogService
private logService: LogService,
private userVerificationService: UserVerificationService,
private router: Router,
dialogService: DialogServiceAbstraction
) {
super(
i18nService,
@@ -45,13 +65,29 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
passwordGenerationService,
platformUtilsService,
policyService,
stateService
stateService,
dialogService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
super.ngOnInit();
this.reason = await this.stateService.getForcePasswordResetReason();
// If we somehow end up here without a reason, go back to the home page
if (this.reason == ForceResetPasswordReason.None) {
this.router.navigate(["/"]);
return;
}
await super.ngOnInit();
}
get masterPasswordWarningText(): string {
return this.reason == ForceResetPasswordReason.WeakMasterPassword
? this.i18nService.t("updateWeakMasterPasswordWarning")
: this.i18nService.t("updateMasterPasswordWarning");
}
togglePassword(confirmField: boolean) {
@@ -104,14 +140,15 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
encKey: [SymmetricCryptoKey, EncString]
) {
try {
// Create request
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
switch (this.reason) {
case ForceResetPasswordReason.AdminForcePasswordReset:
this.formPromise = this.updateTempPassword(masterPasswordHash, encKey);
break;
case ForceResetPasswordReason.WeakMasterPassword:
this.formPromise = this.updatePassword(masterPasswordHash, encKey);
break;
}
// Update user's password
this.formPromise = this.apiService.putUpdateTempPassword(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
@@ -119,6 +156,8 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
this.i18nService.t("updatedMasterPassword")
);
await this.stateService.setForcePasswordResetReason(ForceResetPasswordReason.None);
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
@@ -128,4 +167,30 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
this.logService.error(e);
}
}
private async updateTempPassword(
masterPasswordHash: string,
encKey: [SymmetricCryptoKey, EncString]
) {
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
return this.apiService.putUpdateTempPassword(request);
}
private async updatePassword(
newMasterPasswordHash: string,
encKey: [SymmetricCryptoKey, EncString]
) {
const request = await this.userVerificationService.buildRequest(
this.verification,
PasswordRequest
);
request.masterPasswordHint = this.hint;
request.newMasterPasswordHash = newMasterPasswordHash;
request.key = encKey[1].encryptedString;
return this.apiService.postPassword(request);
}
}

View File

@@ -2,9 +2,11 @@ import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
@Injectable()
export class AuthGuard implements CanActivate {
@@ -12,7 +14,8 @@ export class AuthGuard implements CanActivate {
private authService: AuthService,
private router: Router,
private messagingService: MessagingService,
private keyConnectorService: KeyConnectorService
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
@@ -37,6 +40,13 @@ export class AuthGuard implements CanActivate {
return this.router.createUrlTree(["/remove-password"]);
}
if (
!routerState.url.includes("update-temp-password") &&
(await this.stateService.getForcePasswordResetReason()) != ForceResetPasswordReason.None
) {
return this.router.createUrlTree(["/update-temp-password"]);
}
return true;
}
}

View File

@@ -17,7 +17,7 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { PasswordLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
import { RegisterResponse } from "@bitwarden/common/auth/models/response/register.response";
import { DEFAULT_KDF_CONFIG, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { DEFAULT_KDF_CONFIG, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
@@ -25,6 +25,7 @@ import { RegisterRequest } from "@bitwarden/common/models/request/register.reque
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { CaptchaProtectedComponent } from "../auth/components/captcha-protected.component";
import { DialogServiceAbstraction, SimpleDialogType } from "../services/dialog";
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator";
@@ -90,7 +91,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
environmentService: EnvironmentService,
protected logService: LogService,
protected auditService: AuditService
protected auditService: AuditService,
protected dialogService: DialogServiceAbstraction
) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
@@ -227,35 +229,32 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
(await this.auditService.passwordLeaked(this.formGroup.controls.masterPassword.value)) > 0;
if (passwordWeak && passwordLeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakAndBreachedMasterPasswordDesc"),
this.i18nService.t("weakAndExposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakAndExposedMasterPassword" },
content: { key: "weakAndBreachedMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return { isValid: false };
}
} else if (passwordWeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return { isValid: false };
}
} else if (passwordLeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("exposedMasterPasswordDesc"),
this.i18nService.t("exposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const result = await this.dialogService.openSimpleDialog({
title: { key: "exposedMasterPassword" },
content: { key: "exposedMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
});
if (!result) {
return { isValid: false };
}

View File

@@ -14,8 +14,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
import { DEFAULT_KDF_TYPE, DEFAULT_KDF_CONFIG } from "@bitwarden/common/enums/kdfType";
import { HashPurpose, DEFAULT_KDF_TYPE, DEFAULT_KDF_CONFIG } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
@@ -24,6 +23,7 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/ge
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "../auth/components/change-password.component";
import { DialogServiceAbstraction } from "../services/dialog";
@Directive()
export class SetPasswordComponent extends BaseChangePasswordComponent {
@@ -51,7 +51,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
private route: ActivatedRoute,
stateService: StateService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService
private organizationUserService: OrganizationUserService,
dialogService: DialogServiceAbstraction
) {
super(
i18nService,
@@ -60,7 +61,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
passwordGenerationService,
platformUtilsService,
policyService,
stateService
stateService,
dialogService
);
}

View File

@@ -1,4 +1,4 @@
import { Directive, Input, OnDestroy, OnInit } from "@angular/core";
import { Directive, Input, OnChanges, OnDestroy, OnInit } from "@angular/core";
import {
AbstractControl,
ControlValueAccessor,
@@ -6,16 +6,16 @@ import {
ValidationErrors,
Validator,
} from "@angular/forms";
import { combineLatestWith, Subject, takeUntil } from "rxjs";
import { filter, Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
@Directive()
export class VaultTimeoutInputComponent
implements ControlValueAccessor, Validator, OnInit, OnDestroy
implements ControlValueAccessor, Validator, OnInit, OnDestroy, OnChanges
{
get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
@@ -38,7 +38,7 @@ export class VaultTimeoutInputComponent
}),
});
@Input() vaultTimeouts: { name: string; value: number }[];
@Input() vaultTimeoutOptions: { name: string; value: number }[];
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
@@ -55,38 +55,37 @@ export class VaultTimeoutInputComponent
async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
.pipe(combineLatestWith(this.policyService.policies$), takeUntil(this.destroy$))
.subscribe(([policyAppliesToActiveUser, policies]) => {
if (policyAppliesToActiveUser) {
const vaultTimeoutPolicy = policies.find(
(policy) => policy.type === PolicyType.MaximumVaultTimeout && policy.enabled
);
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
this.applyVaultTimeoutPolicy();
}
.get$(PolicyType.MaximumVaultTimeout)
.pipe(
filter((policy) => policy != null),
takeUntil(this.destroy$)
)
.subscribe((policy) => {
this.vaultTimeoutPolicy = policy;
this.applyVaultTimeoutPolicy();
});
// eslint-disable-next-line rxjs/no-async-subscribe
this.form.valueChanges.subscribe(async (value) => {
this.onChange(this.getVaultTimeout(value));
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
if (this.onChange) {
this.onChange(this.getVaultTimeout(value));
}
});
// Assign the previous value to the custom fields
this.form.get("vaultTimeout").valueChanges.subscribe((value) => {
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return;
}
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
this.form.controls.vaultTimeout.valueChanges
.pipe(
filter((value) => value !== VaultTimeoutInputComponent.CUSTOM_VALUE),
takeUntil(this.destroy$)
)
.subscribe((_) => {
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
});
});
});
}
ngOnDestroy() {
@@ -95,10 +94,14 @@ export class VaultTimeoutInputComponent
}
ngOnChanges() {
this.vaultTimeouts.push({
name: this.i18nService.t("custom"),
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
});
if (
!this.vaultTimeoutOptions.find((p) => p.value === VaultTimeoutInputComponent.CUSTOM_VALUE)
) {
this.vaultTimeoutOptions.push({
name: this.i18nService.t("custom"),
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
});
}
}
getVaultTimeout(value: any) {
@@ -114,7 +117,7 @@ export class VaultTimeoutInputComponent
return;
}
if (this.vaultTimeouts.every((p) => p.value !== value)) {
if (this.vaultTimeoutOptions.every((p) => p.value !== value)) {
this.form.setValue({
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
custom: {
@@ -166,7 +169,7 @@ export class VaultTimeoutInputComponent
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
this.vaultTimeoutOptions = this.vaultTimeoutOptions.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&

View File

@@ -5,11 +5,8 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import {
isNotProviderUser,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums/organization-user-status-type";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { Utils } from "@bitwarden/common/misc/utils";
@@ -54,13 +51,10 @@ export class ShareComponent implements OnInit, OnDestroy {
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly);
this.organizations$ = this.organizationService.organizations$.pipe(
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
map((orgs) => {
return orgs
.filter(
(o) =>
o.enabled && o.status === OrganizationUserStatusType.Confirmed && isNotProviderUser(o)
)
.filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed)
.sort(Utils.getSortFunction(this.i18nService, "name"));
})
);

View File

@@ -3,7 +3,6 @@ import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { CalloutComponent } from "./components/callout.component";
import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component";
import { BitwardenToastModule } from "./components/toastr.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
@@ -28,6 +27,7 @@ import { SearchPipe } from "./pipes/search.pipe";
import { UserNamePipe } from "./pipes/user-name.pipe";
import { UserTypePipe } from "./pipes/user-type.pipe";
import { PasswordStrengthComponent } from "./shared/components/password-strength/password-strength.component";
import { ExportScopeCalloutComponent } from "./tools/export/components/export-scope-callout.component";
import { IconComponent } from "./vault/components/icon.component";
@NgModule({

View File

@@ -17,7 +17,7 @@ export class ColorPasswordPipe implements PipeTransform {
protected generateTemplate(
password: string,
templateGenerator: (chararacter: string, type: string, index?: number) => string
templateGenerator: (character: string, type: string, index?: number) => string
) {
// Convert to an array to handle cases that stings have special characters, ie: emoji.
const passwordArray = Array.from(password);

View File

@@ -1,7 +1,7 @@
import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums/organization-user-type";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
@Pipe({
name: "userType",

View File

@@ -24,7 +24,7 @@ $card-icons-dark: (
);
.credit-card-icon {
display: block; // Resolves the parent container being slighly to big
display: block; // Resolves the parent container being slightly to big
height: 19px;
width: 24px;
background-size: contain;

View File

@@ -0,0 +1,26 @@
import { Dialog, DialogRef } from "@angular/cdk/dialog";
import { SimpleDialogOptions } from "./simple-dialog-options";
export abstract class DialogServiceAbstraction extends Dialog {
/**
* Opens a simple dialog, returns true if the user accepted the dialog.
*
* @param {SimpleDialogOptions} simpleDialogOptions - An object containing options for the dialog.
* @returns `boolean` - True if the user accepted the dialog, false otherwise.
*/
openSimpleDialog: (simpleDialogOptions: SimpleDialogOptions) => Promise<boolean>;
/**
* Opens a simple dialog.
*
* @deprecated Use `openSimpleDialogAcceptedPromise` instead. If you find a use case for the `dialogRef`
* please let #wg-component-library know and we can un-deprecate this method.
*
* @param {SimpleDialogOptions} simpleDialogOptions - An object containing options for the dialog.
* @returns `DialogRef` - The reference to the opened dialog.
* Contains a closed observable which can be subscribed to for determining which button
* a user pressed (see `SimpleDialogCloseType`)
*/
openSimpleDialogRef: (simpleDialogOptions: SimpleDialogOptions) => DialogRef;
}

View File

@@ -0,0 +1,65 @@
import {
DialogRef,
DialogConfig,
Dialog,
DEFAULT_DIALOG_CONFIG,
DIALOG_SCROLL_STRATEGY,
} from "@angular/cdk/dialog";
import { Overlay, OverlayContainer } from "@angular/cdk/overlay";
import { ComponentType } from "@angular/cdk/portal";
import { Inject, Injectable, Injector, Optional, SkipSelf, TemplateRef } from "@angular/core";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { DialogServiceAbstraction } from "./dialog.service.abstraction";
import { SimpleDialogOptions } from "./simple-dialog-options";
import { Translation } from "./translation";
// This is a temporary base class for Dialogs. It is intended to be removed once the Component Library is adopted by each app.
@Injectable()
export abstract class DialogService extends Dialog implements DialogServiceAbstraction {
constructor(
/** Parent class constructor */
_overlay: Overlay,
_injector: Injector,
@Optional() @Inject(DEFAULT_DIALOG_CONFIG) _defaultOptions: DialogConfig,
@Optional() @SkipSelf() _parentDialog: Dialog,
_overlayContainer: OverlayContainer,
@Inject(DIALOG_SCROLL_STRATEGY) scrollStrategy: any,
protected i18nService: I18nService
) {
super(_overlay, _injector, _defaultOptions, _parentDialog, _overlayContainer, scrollStrategy);
}
async openSimpleDialog(options: SimpleDialogOptions): Promise<boolean> {
throw new Error("Method not implemented.");
}
openSimpleDialogRef(simpleDialogOptions: SimpleDialogOptions): DialogRef {
throw new Error("Method not implemented.");
}
override open<R = unknown, D = unknown, C = unknown>(
componentOrTemplateRef: ComponentType<C> | TemplateRef<C>,
config?: DialogConfig<D, DialogRef<R, C>>
): DialogRef<R, C> {
throw new Error("Method not implemented.");
}
protected translate(translation: string | Translation, defaultKey?: string): string {
if (translation == null && defaultKey == null) {
return null;
}
if (translation == null) {
return this.i18nService.t(defaultKey);
}
// Translation interface use implies we must localize.
if (typeof translation === "object") {
return this.i18nService.t(translation.key, ...(translation.placeholders ?? []));
}
return translation;
}
}

View File

@@ -0,0 +1,6 @@
export * from "./dialog.service.abstraction";
export * from "./simple-dialog-options";
export * from "./simple-dialog-type.enum";
export * from "./simple-dialog-close-type.enum";
export * from "./dialog.service";
export * from "./translation";

View File

@@ -0,0 +1,4 @@
export enum SimpleDialogCloseType {
ACCEPT = "accept",
CANCEL = "cancel",
}

View File

@@ -0,0 +1,51 @@
import { SimpleDialogType } from "./simple-dialog-type.enum";
import { Translation } from "./translation";
// Using type lets devs skip optional params w/out having to pass undefined.
/**
*
* @typedef {Object} SimpleDialogOptions - A configuration type for the Simple Dialog component
*/
export type SimpleDialogOptions = {
/**
* Dialog title.
*
* If not localized, pass in a `Translation`. */
title: string | Translation;
/** Dialog content.
*
* If not localized, pass in a `Translation`. */
content: string | Translation;
/** Dialog type. It controls default icons and icon colors. */
type: SimpleDialogType;
/** Dialog custom icon class.
*
* If not provided, a standard icon will be inferred from type.
* Note: icon color is enforced based on dialog type. */
icon?: string;
/** Dialog custom accept button text.
*
* If not provided, ("yes" | i18n) will be used.
*
* If not localized, pass in a `Translation` */
acceptButtonText?: string | Translation;
/**
* Dialog custom cancel button text.
*
* If not provided, ("no" | i18n) will be used.
*
* If custom acceptButtonText is passed in, ("cancel" | i18n) will be used.
*
* If null is provided, the cancel button will be removed.
*
* If not localized, pass in a `Translation` */
cancelButtonText?: string | Translation;
/** Whether or not the user can use escape or clicking the backdrop to close the dialog */
disableClose?: boolean;
};

View File

@@ -0,0 +1,7 @@
export enum SimpleDialogType {
PRIMARY = "primary",
SUCCESS = "success",
INFO = "info",
WARNING = "warning",
DANGER = "danger",
}

View File

@@ -0,0 +1,4 @@
export interface Translation {
key: string;
placeholders?: Array<string | number>;
}

View File

@@ -1,4 +1,4 @@
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { LOCALE_ID, NgModule } from "@angular/core";
import { AvatarUpdateService as AccountUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/abstractions/anonymousHub.service";
@@ -14,7 +14,6 @@ import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/file-upload/file-upload.service";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "@bitwarden/common/abstractions/formValidationErrors.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
@@ -23,8 +22,8 @@ import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/comm
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/abstractions/organization-domain/org-domain-api.service.abstraction";
import {
OrgDomainServiceAbstraction,
OrgDomainInternalServiceAbstraction,
OrgDomainServiceAbstraction,
} from "@bitwarden/common/abstractions/organization-domain/org-domain.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -94,7 +93,6 @@ import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/servi
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { ExportService } from "@bitwarden/common/services/export.service";
import { FileUploadService } from "@bitwarden/common/services/file-upload/file-upload.service";
import { FormValidationErrorsService } from "@bitwarden/common/services/formValidationErrors.service";
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
@@ -138,6 +136,10 @@ import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
import {
VaultExportService,
VaultExportServiceAbstraction,
} from "@bitwarden/exporter/vault-export";
import { AuthGuard } from "../auth/guards/auth.guard";
import { LockGuard } from "../auth/guards/lock.guard";
@@ -232,6 +234,9 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
StateServiceAbstraction,
TwoFactorServiceAbstraction,
I18nServiceAbstraction,
EncryptService,
PasswordGenerationServiceAbstraction,
PolicyServiceAbstraction,
],
},
{
@@ -251,8 +256,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
searchService: SearchServiceAbstraction,
stateService: StateServiceAbstraction,
encryptService: EncryptService,
fileUploadService: CipherFileUploadServiceAbstraction
@@ -262,8 +266,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
settingsService,
apiService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
searchService,
stateService,
encryptService,
fileUploadService
@@ -273,8 +276,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
SettingsServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
SearchServiceAbstraction,
StateServiceAbstraction,
EncryptService,
CipherFileUploadServiceAbstraction,
@@ -464,8 +466,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
provide: VaultExportServiceAbstraction,
useClass: VaultExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
@@ -478,7 +480,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction],
deps: [LogService, I18nServiceAbstraction],
},
{
provide: NotificationsServiceAbstraction,
@@ -613,7 +615,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
{
provide: ConfigServiceAbstraction,
useClass: ConfigService,
deps: [StateServiceAbstraction, ConfigApiServiceAbstraction],
deps: [StateServiceAbstraction, ConfigApiServiceAbstraction, AuthServiceAbstraction],
},
{
provide: ConfigApiServiceAbstraction,

View File

@@ -1,4 +1,4 @@
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { ThemeType } from "@bitwarden/common/enums";
export interface Theme {
configuredTheme: ThemeType;

View File

@@ -1,4 +1,4 @@
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { ThemeType } from "@bitwarden/common/enums";
import { Theme } from "./theme";

View File

@@ -1,6 +1,6 @@
import { Observable } from "rxjs";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { ThemeType } from "@bitwarden/common/enums";
import { Theme } from "./theme";

View File

@@ -3,7 +3,7 @@ import { Inject, Injectable } from "@angular/core";
import { BehaviorSubject, filter, fromEvent, Observable } from "rxjs";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { ThemeType } from "@bitwarden/common/enums";
import { WINDOW } from "../injection-tokens";

View File

@@ -98,32 +98,13 @@ export class PasswordStrengthComponent implements OnChanges {
const strengthResult = this.passwordGenerationService.passwordStrength(
masterPassword,
this.getPasswordStrengthUserInput()
this.email,
this.name?.trim().toLowerCase().split(" ")
);
this.passwordStrengthResult.emit(strengthResult);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}
getPasswordStrengthUserInput() {
let userInput: string[] = [];
const email = this.email;
const name = this.name;
const atPosition = email?.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (name != null && name !== "") {
userInput = userInput.concat(name.trim().toLowerCase().split(" "));
}
return userInput;
}
setPasswordScoreText(color: string, text: string) {
color = color.slice(3);
this.passwordScoreColor.emit({ color: color, text: text });

View File

@@ -4,16 +4,17 @@ import { merge, takeUntil, Subject, startWith } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { EncryptedExportType } from "@bitwarden/common/enums/encryptedExportType";
import { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
import { DialogServiceAbstraction, SimpleDialogType } from "../../../services/dialog";
@Directive()
export class ExportComponent implements OnInit, OnDestroy {
@@ -42,14 +43,15 @@ export class ExportComponent implements OnInit, OnDestroy {
protected cryptoService: CryptoService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected exportService: ExportService,
protected exportService: VaultExportServiceAbstraction,
protected eventCollectionService: EventCollectionService,
private policyService: PolicyService,
protected win: Window,
private logService: LogService,
private userVerificationService: UserVerificationService,
private formBuilder: UntypedFormBuilder,
protected fileDownloadService: FileDownloadService
protected fileDownloadService: FileDownloadService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -127,25 +129,22 @@ export class ExportComponent implements OnInit, OnDestroy {
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
"<p>" +
return await this.dialogService.openSimpleDialog({
title: { key: "confirmVaultExport" },
content:
this.i18nService.t("encExportKeyWarningDesc") +
"<p>" +
" " +
this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning",
true
);
acceptButtonText: { key: "exportVault" },
type: SimpleDialogType.WARNING,
});
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t("exportWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning"
);
return await this.dialogService.openSimpleDialog({
title: { key: "confirmVaultExport" },
content: { key: "exportWarningDesc" },
acceptButtonText: { key: "exportVault" },
type: SimpleDialogType.WARNING,
});
}
}

View File

@@ -211,7 +211,7 @@ export class GeneratorComponent implements OnInit {
}
private normalizePasswordOptions() {
// Application level normalize options depedent on class variables
// Application level normalize options dependent on class variables
this.passwordOptions.ambiguous = !this.avoidAmbiguous;
if (

View File

@@ -9,7 +9,7 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
@@ -19,6 +19,8 @@ import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class AddEditComponent implements OnInit, OnDestroy {
@Input() sendId: string;
@@ -60,7 +62,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected policyService: PolicyService,
private logService: LogService,
protected stateService: StateService,
protected sendApiService: SendApiService
protected sendApiService: SendApiService,
protected dialogService: DialogServiceAbstraction
) {
this.typeOptions = [
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
@@ -229,15 +232,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
if (this.deletePromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning",
false,
this.componentName != "" ? this.componentName + " .modal-content" : null
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteSend" },
content: { key: "deleteSendConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -308,14 +309,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
);
} else {
await this.platformUtilsService.showDialog(
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
null,
this.i18nService.t("ok"),
null,
"success",
null
);
await this.dialogService.openSimpleDialog({
title: "",
content: { key: this.editMode ? "editedSend" : "createdSend" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.SUCCESS,
});
await this.copyLinkToClipboard(this.link);
}
}

View File

@@ -22,7 +22,7 @@ enum BrowserPath {
enum DateField {
DeletionDate = "deletion",
ExpriationDate = "expiration",
ExpirationDate = "expiration",
}
// Value = hours
@@ -135,10 +135,10 @@ export class EffluxDatesComponent implements OnInit {
}
default: {
const now = new Date();
const miliseconds = now.setTime(
const milliseconds = now.setTime(
now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
return new Date(milliseconds).toString();
}
}
}
@@ -166,10 +166,10 @@ export class EffluxDatesComponent implements OnInit {
}
default: {
const now = new Date();
const miliseconds = now.setTime(
const milliseconds = now.setTime(
now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
return new Date(milliseconds).toString();
}
}
}
@@ -180,7 +180,7 @@ export class EffluxDatesComponent implements OnInit {
}
get safariExpirationTimePresetOptions() {
return this.safariTimePresetOptions(DateField.ExpriationDate);
return this.safariTimePresetOptions(DateField.ExpirationDate);
}
private get nextWeek(): Date {
@@ -285,7 +285,7 @@ export class EffluxDatesComponent implements OnInit {
// loop through each hour on a 12 hour system
for (let h = 1; h <= 12; h++) {
// loop through each minute in the hour using the skip to incriment
// loop through each minute in the hour using the skip to increment
for (let m = 0; m < 60; m += minuteIncrementer) {
// init the final strings that will be added to the lists
let hour = h.toString();
@@ -333,7 +333,7 @@ export class EffluxDatesComponent implements OnInit {
// determine if an unsupported value already exists on the send & add that to the top of the option list
// example: if the Send was created with a different client
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
if (field === DateField.ExpirationDate && this.initialExpirationDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),

View File

@@ -7,12 +7,14 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class SendComponent implements OnInit, OnDestroy {
disableSend = false;
@@ -49,7 +51,8 @@ export class SendComponent implements OnInit, OnDestroy {
protected searchService: SearchService,
protected policyService: PolicyService,
private logService: LogService,
protected sendApiService: SendApiService
protected sendApiService: SendApiService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -125,13 +128,13 @@ export class SendComponent implements OnInit, OnDestroy {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removePasswordConfirmation"),
this.i18nService.t("removePassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "removePassword" },
content: { key: "removePasswordConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -156,13 +159,13 @@ export class SendComponent implements OnInit, OnDestroy {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteSend" },
content: { key: "deleteSendConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}

View File

@@ -3,8 +3,7 @@ import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { EventType } from "@bitwarden/common/enums/eventType";
import { FieldType } from "@bitwarden/common/enums/fieldType";
import { EventType, FieldType } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";

View File

@@ -10,17 +10,14 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import {
isNotProviderUser,
isMember,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums/organization-user-status-type";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { EventType } from "@bitwarden/common/enums/eventType";
import { SecureNoteType } from "@bitwarden/common/enums/secureNoteType";
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
import { EventType, SecureNoteType, UriMatchType } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/misc/utils";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -37,6 +34,8 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class AddEditComponent implements OnInit, OnDestroy {
@Input() cloneMode = false;
@@ -101,7 +100,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
private logService: LogService,
protected passwordRepromptService: PasswordRepromptService,
private organizationService: OrganizationService,
protected sendApiService: SendApiService
protected sendApiService: SendApiService,
protected dialogService: DialogServiceAbstraction
) {
this.typeOptions = [
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
@@ -195,7 +195,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
const orgs = await this.organizationService.getAll();
orgs
.filter(isNotProviderUser)
.filter(isMember)
.sort(Utils.getSortFunction(this.i18nService, "name"))
.forEach((o) => {
if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) {
@@ -393,17 +393,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
),
this.i18nService.t("deleteItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning",
false,
this.componentName != "" ? this.componentName + " .modal-content" : null
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteItem" },
content: {
key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation",
},
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -432,13 +429,12 @@ export class AddEditComponent implements OnInit, OnDestroy {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("restoreItemConfirmation"),
this.i18nService.t("restoreItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "restoreItem" },
content: { key: "restoreItemConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -458,12 +454,12 @@ export class AddEditComponent implements OnInit, OnDestroy {
async generateUsername(): Promise<boolean> {
if (this.cipher.login?.username?.length) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("overwriteUsernameConfirmation"),
this.i18nService.t("overwriteUsername"),
this.i18nService.t("yes"),
this.i18nService.t("no")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "overwriteUsername" },
content: { key: "overwriteUsernameConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -475,12 +471,12 @@ export class AddEditComponent implements OnInit, OnDestroy {
async generatePassword(): Promise<boolean> {
if (this.cipher.login?.password?.length) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("overwritePasswordConfirmation"),
this.i18nService.t("overwritePassword"),
this.i18nService.t("yes"),
this.i18nService.t("no")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "overwritePassword" },
content: { key: "overwritePasswordConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -606,13 +602,15 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
protected deleteCipher() {
const asAdmin = this.organization?.canEditAnyCollection;
return this.cipher.isDeleted
? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
const asAdmin = this.organization?.canEditAnyCollection;
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
}
get defaultOwnerId(): string | null {

View File

@@ -14,6 +14,8 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class AttachmentsComponent implements OnInit {
@Input() cipherId: string;
@@ -40,7 +42,8 @@ export class AttachmentsComponent implements OnInit {
protected win: Window,
protected logService: LogService,
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
protected fileDownloadService: FileDownloadService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -100,15 +103,12 @@ export class AttachmentsComponent implements OnInit {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteAttachmentConfirmation"),
this.i18nService.t("deleteAttachment"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning",
false,
this.componentName != "" ? this.componentName + " .modal-content" : null
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteAttachment" },
content: { key: "deleteAttachmentConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return;
}
@@ -197,23 +197,24 @@ export class AttachmentsComponent implements OnInit {
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
if (!this.canAccessAttachments) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "premiumRequired" },
content: { key: "premiumRequiredDesc" },
acceptButtonText: { key: "learnMore" },
type: SimpleDialogType.SUCCESS,
});
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
} else if (!this.hasUpdatedKey) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("updateKey"),
this.i18nService.t("featureUnavailable"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "featureUnavailable" },
content: { key: "updateKey" },
acceptButtonText: { key: "learnMore" },
type: SimpleDialogType.WARNING,
});
if (confirmed) {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"

View File

@@ -7,6 +7,8 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstraction
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class FolderAddEditComponent implements OnInit {
@Input() folderId: string;
@@ -25,7 +27,8 @@ export class FolderAddEditComponent implements OnInit {
protected folderApiService: FolderApiServiceAbstraction,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
private logService: LogService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -61,15 +64,12 @@ export class FolderAddEditComponent implements OnInit {
}
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteFolderConfirmation"),
this.i18nService.t("deleteFolder"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning",
false,
this.componentName != "" ? this.componentName + " .modal-content" : null
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteFolder" },
content: { key: "deleteFolderConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}

View File

@@ -1,11 +1,16 @@
<div class="icon" aria-hidden="true">
<img
[src]="image"
appFallbackSrc="{{ fallbackImage }}"
*ngIf="imageEnabled && image"
alt=""
decoding="async"
loading="lazy"
/>
<i class="tw-text-muted bwi bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
<ng-container *ngIf="data$ | async as data">
<img
[src]="data.image"
[appFallbackSrc]="data.fallbackImage"
*ngIf="data.imageEnabled && data.image"
alt=""
decoding="async"
loading="lazy"
/>
<i
class="tw-text-muted bwi bwi-lg {{ data.icon }}"
*ngIf="!data.imageEnabled || !data.image"
></i>
</ng-container>
</div>

View File

@@ -1,7 +1,15 @@
import { Component, Input, OnChanges } from "@angular/core";
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
import {
BehaviorSubject,
combineLatest,
distinctUntilChanged,
filter,
map,
Observable,
} from "rxjs";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -25,92 +33,104 @@ const cardIcons: Record<string, string> = {
@Component({
selector: "app-vault-icon",
templateUrl: "icon.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IconComponent implements OnChanges {
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
private iconsUrl: string;
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
export class IconComponent implements OnInit {
@Input()
set cipher(value: CipherView) {
this.cipher$.next(value);
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
protected data$: Observable<{
imageEnabled: boolean;
image?: string;
fallbackImage: string;
icon?: string;
}>;
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = "bwi-globe";
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = "bwi-sticky-note";
break;
case CipherType.Card:
this.icon = "bwi-credit-card";
this.setCardIcon();
break;
case CipherType.Identity:
this.icon = "bwi-id-card";
break;
case CipherType.Fido2Key:
this.icon = "bwi-key"; // TODO: Verify if this icon should be classified as "Bitwarden Object"
break;
default:
break;
}
}
private cipher$ = new BehaviorSubject<CipherView>(undefined);
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
constructor(
private environmentService: EnvironmentService,
private settingsService: SettingsService
) {}
if (hostnameUri.indexOf("androidapp://") === 0) {
this.icon = "bwi-android";
this.image = null;
} else if (hostnameUri.indexOf("iosapp://") === 0) {
this.icon = "bwi-apple";
this.image = null;
} else if (
this.imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
async ngOnInit() {
const iconsUrl = this.environmentService.getIconsUrl();
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
this.fallbackImage = "images/bwi-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
this.data$ = combineLatest([
this.settingsService.disableFavicon$.pipe(distinctUntilChanged()),
this.cipher$.pipe(filter((c) => c !== undefined)),
]).pipe(
map(([disableFavicon, cipher]) => {
const imageEnabled = !disableFavicon;
let image = undefined;
let fallbackImage = "";
let icon = undefined;
switch (cipher.type) {
case CipherType.Login:
icon = "bwi-globe";
if (cipher.login.uri) {
let hostnameUri = cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf("androidapp://") === 0) {
icon = "bwi-android";
image = null;
} else if (hostnameUri.indexOf("iosapp://") === 0) {
icon = "bwi-apple";
image = null;
} else if (
imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (imageEnabled) {
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
if (imageEnabled && isWebsite) {
try {
image = iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
fallbackImage = "images/bwi-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
image = null;
}
break;
case CipherType.SecureNote:
icon = "bwi-sticky-note";
break;
case CipherType.Card:
icon = "bwi-credit-card";
if (imageEnabled && cipher.card.brand in cardIcons) {
icon = "credit-card-icon " + cardIcons[cipher.card.brand];
}
break;
case CipherType.Identity:
icon = "bwi-id-card";
break;
case CipherType.Fido2Key:
icon = "bwi-key"; // TODO: Verify if this icon should be classified as "Bitwarden Object"
break;
default:
break;
}
}
} else {
this.image = null;
}
}
private setCardIcon() {
const brand = this.cipher.card.brand;
if (this.imageEnabled && brand in cardIcons) {
this.icon = "credit-card-icon " + cardIcons[brand];
}
return {
imageEnabled,
image,
fallbackImage,
icon,
};
})
);
}
}

View File

@@ -6,6 +6,8 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@Directive()
export class PremiumComponent implements OnInit {
isPremium = false;
@@ -17,7 +19,8 @@ export class PremiumComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
private logService: LogService,
protected stateService: StateService
protected stateService: StateService,
protected dialogService: DialogServiceAbstraction
) {}
async ngOnInit() {
@@ -36,24 +39,24 @@ export class PremiumComponent implements OnInit {
}
async purchase() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumPurchaseAlert"),
this.i18nService.t("premiumPurchase"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "premiumPurchase" },
content: { key: "premiumPurchaseAlert" },
type: SimpleDialogType.INFO,
});
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
}
async manage() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumManageAlert"),
this.i18nService.t("premiumManage"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "premiumManage" },
content: { key: "premiumManageAlert" },
type: SimpleDialogType.INFO,
});
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=manage");
}

View File

@@ -2,6 +2,7 @@ import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@Directive()
@@ -31,7 +32,7 @@ export class VaultItemsComponent {
this._searchText = value;
}
constructor(protected searchService: SearchService) {}
constructor(protected searchService: SearchService, protected cipherService: CipherService) {}
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
this.deleted = deleted ?? false;
@@ -92,6 +93,7 @@ export class VaultItemsComponent {
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
protected async doSearch(indexedCiphers?: CipherView[]) {
indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted());
this.ciphers = await this.searchService.searchCiphers(
this.searchText,
[this.filter, this.deletedFilter],

View File

@@ -1,8 +1,7 @@
import { Directive, Input } from "@angular/core";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventType } from "@bitwarden/common/enums/eventType";
import { FieldType } from "@bitwarden/common/enums/fieldType";
import { EventType, FieldType } from "@bitwarden/common/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";

View File

@@ -22,8 +22,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EventType } from "@bitwarden/common/enums/eventType";
import { FieldType } from "@bitwarden/common/enums/fieldType";
import { EventType, FieldType } from "@bitwarden/common/enums";
import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -36,6 +35,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
const BroadcasterSubscriptionId = "ViewComponent";
@Directive()
@@ -85,7 +86,8 @@ export class ViewComponent implements OnDestroy, OnInit {
protected passwordRepromptService: PasswordRepromptService,
private logService: LogService,
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
protected fileDownloadService: FileDownloadService,
protected dialogService: DialogServiceAbstraction
) {}
ngOnInit() {
@@ -175,15 +177,14 @@ export class ViewComponent implements OnDestroy, OnInit {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
),
this.i18nService.t("deleteItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteItem" },
content: {
key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation",
},
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -208,13 +209,12 @@ export class ViewComponent implements OnDestroy, OnInit {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("restoreItemConfirmation"),
this.i18nService.t("restoreItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "restoreItem" },
content: { key: "restoreItemConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return false;
}
@@ -337,10 +337,7 @@ export class ViewComponent implements OnDestroy, OnInit {
);
if (typeI18nKey === "password") {
this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,
this.cipherId
);
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, this.cipherId);
} else if (typeI18nKey === "securityCode") {
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === "H_Field") {

View File

@@ -4,11 +4,11 @@ import { firstValueFrom, from, mergeMap, Observable } from "rxjs";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import {
isNotProviderUser,
isMember,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
@@ -44,9 +44,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
async buildOrganizations(): Promise<Organization[]> {
let organizations = await this.organizationService.getAll();
if (organizations != null) {
organizations = organizations
.filter(isNotProviderUser)
.sort((a, b) => a.name.localeCompare(b.name));
organizations = organizations.filter(isMember).sort((a, b) => a.name.localeCompare(b.name));
}
return organizations;