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

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Shane Melton
2022-10-11 13:28:19 -07:00
140 changed files with 3451 additions and 683 deletions

View File

@@ -1,5 +1,5 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Observable } from "rxjs";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { Observable, Subject, takeUntil, concatMap } from "rxjs";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@@ -33,7 +33,7 @@ import { LoginView } from "@bitwarden/common/models/view/loginView";
import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView";
@Directive()
export class AddEditComponent implements OnInit {
export class AddEditComponent implements OnInit, OnDestroy {
@Input() cloneMode = false;
@Input() folderId: string = null;
@Input() cipherId: string;
@@ -75,7 +75,9 @@ export class AddEditComponent implements OnInit {
reprompt = false;
canUseReprompt = true;
protected destroy$ = new Subject<void>();
protected writeableCollections: CollectionView[];
private personalOwnershipPolicyAppliesToActiveUser: boolean;
private previousCipherId: string;
constructor(
@@ -152,14 +154,28 @@ export class AddEditComponent implements OnInit {
}
async ngOnInit() {
await this.init();
this.policyService
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
.pipe(
concatMap(async (policyAppliesToActiveUser) => {
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser;
await this.init();
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
async init() {
if (this.ownershipOptions.length) {
this.ownershipOptions = [];
}
if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) {
if (this.personalOwnershipPolicyAppliesToActiveUser) {
this.allowPersonal = false;
} else {
const myEmail = await this.stateService.getEmail();

View File

@@ -1,4 +1,5 @@
import { Directive, OnInit } from "@angular/core";
import { Directive, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -15,7 +16,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCry
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
@Directive()
export class ChangePasswordComponent implements OnInit {
export class ChangePasswordComponent implements OnInit, OnDestroy {
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
@@ -28,6 +29,8 @@ export class ChangePasswordComponent implements OnInit {
protected kdf: KdfType;
protected kdfIterations: number;
protected destroy$ = new Subject<void>();
constructor(
protected i18nService: I18nService,
protected cryptoService: CryptoService,
@@ -40,7 +43,18 @@ export class ChangePasswordComponent implements OnInit {
async ngOnInit() {
this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions ??= await this.policyService.getMasterPasswordPolicyOptions();
this.policyService
.masterPasswordPolicyOptions$()
.pipe(takeUntil(this.destroy$))
.subscribe(
(enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions)
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
async submit() {

View File

@@ -55,6 +55,13 @@ export class ExportComponent implements OnInit, OnDestroy {
) {}
async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disabledByPolicy = policyAppliesToActiveUser;
});
await this.checkExportDisabled();
merge(
@@ -71,9 +78,6 @@ export class ExportComponent implements OnInit, OnDestroy {
}
async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(
PolicyType.DisablePersonalVaultExport
);
if (this.disabledByPolicy) {
this.exportForm.disable();
}
@@ -117,6 +121,7 @@ export class ExportComponent implements OnInit, OnDestroy {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
return;
}
this.doExport();

View File

@@ -1,7 +1,7 @@
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Subscription } from "rxjs";
import { take } from "rxjs/operators";
import { Subject } from "rxjs";
import { concatMap, take, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@@ -41,7 +41,7 @@ export class LockComponent implements OnInit, OnDestroy {
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
private activeAccountSubscription: Subscription;
private destroy$ = new Subject<void>();
constructor(
protected router: Router,
@@ -60,14 +60,19 @@ export class LockComponent implements OnInit, OnDestroy {
) {}
async ngOnInit() {
// eslint-disable-next-line rxjs/no-async-subscribe
this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => {
await this.load();
});
this.stateService.activeAccount$
.pipe(
concatMap(async () => {
await this.load();
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy() {
this.activeAccountSubscription.unsubscribe();
this.destroy$.next();
this.destroy$.complete();
}
async submit() {

View File

@@ -65,7 +65,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
}
async ngOnInit() {
let email = this.formGroup.get("email")?.value;
let email = this.formGroup.value.email;
if (email == null || email === "") {
email = await this.stateService.getRememberedEmail();
this.formGroup.get("email")?.setValue(email);
@@ -81,9 +81,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
}
async submit(showToast = true) {
const email = this.formGroup.get("email")?.value;
const masterPassword = this.formGroup.get("masterPassword")?.value;
const rememberEmail = this.formGroup.get("rememberEmail")?.value;
const data = this.formGroup.value;
await this.setupCaptcha();
@@ -103,15 +101,15 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
try {
const credentials = new PasswordLogInCredentials(
email,
masterPassword,
data.email,
data.masterPassword,
this.captchaToken,
null
);
this.formPromise = this.authService.logIn(credentials);
const response = await this.formPromise;
if (rememberEmail || this.alwaysRememberEmail) {
await this.stateService.setRememberedEmail(email);
if (data.rememberEmail || this.alwaysRememberEmail) {
await this.stateService.setRememberedEmail(data.email);
} else {
await this.stateService.setRememberedEmail(null);
}
@@ -216,7 +214,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
}
protected focusInput() {
const email = this.formGroup.get("email")?.value;
const email = this.formGroup.value.email;
document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus();
}
}

View File

@@ -96,11 +96,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
async submit(showToast = true) {
let email = this.formGroup.get("email")?.value;
let email = this.formGroup.value.email;
email = email.trim().toLowerCase();
let name = this.formGroup.get("name")?.value;
let name = this.formGroup.value.name;
name = name === "" ? null : name; // Why do we do this?
const masterPassword = this.formGroup.get("masterPassword")?.value;
const masterPassword = this.formGroup.value.masterPassword;
try {
if (!this.accountCreated) {
const registerResponse = await this.registerAccount(
@@ -125,7 +125,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
if (loginResponse.captchaRequired) {
return;
}
this.createdAccount.emit(this.formGroup.get("email")?.value);
this.createdAccount.emit(this.formGroup.value.email);
} else {
this.platformUtilsService.showToast(
"success",
@@ -232,7 +232,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
masterPassword: string,
name: string
): Promise<RegisterRequest> {
const hint = this.formGroup.get("hint")?.value;
const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);

View File

@@ -1,5 +1,6 @@
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -18,7 +19,7 @@ import { SendTextView } from "@bitwarden/common/models/view/sendTextView";
import { SendView } from "@bitwarden/common/models/view/sendView";
@Directive()
export class AddEditComponent implements OnInit {
export class AddEditComponent implements OnInit, OnDestroy {
@Input() sendId: string;
@Input() type: SendType;
@@ -45,6 +46,7 @@ export class AddEditComponent implements OnInit {
showOptions = false;
private sendLinkBaseUrl: string;
private destroy$ = new Subject<void>();
constructor(
protected i18nService: I18nService,
@@ -80,9 +82,28 @@ export class AddEditComponent implements OnInit {
}
async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser;
});
this.policyService
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableHideEmail = policyAppliesToActiveUser;
});
await this.load();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get editMode(): boolean {
return this.sendId != null;
}
@@ -97,12 +118,6 @@ export class AddEditComponent implements OnInit {
}
async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(
PolicyType.SendOptions,
(p) => p.data.disableHideEmail
);
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {

View File

@@ -1,4 +1,5 @@
import { Directive, NgZone, OnInit } from "@angular/core";
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -12,7 +13,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
import { SendView } from "@bitwarden/common/models/view/sendView";
@Directive()
export class SendComponent implements OnInit {
export class SendComponent implements OnInit, OnDestroy {
disableSend = false;
sendType = SendType;
loaded = false;
@@ -36,6 +37,7 @@ export class SendComponent implements OnInit {
onSuccessfulLoad: () => Promise<any>;
private searchTimeout: any;
private destroy$ = new Subject<void>();
constructor(
protected sendService: SendService,
@@ -49,7 +51,17 @@ export class SendComponent implements OnInit {
) {}
async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
async load(filter: (send: SendView) => boolean = null) {

View File

@@ -1,11 +1,12 @@
import { Directive, Input, OnInit } from "@angular/core";
import { Directive, Input, OnDestroy, OnInit } from "@angular/core";
import {
AbstractControl,
ControlValueAccessor,
UntypedFormBuilder,
FormBuilder,
ValidationErrors,
Validator,
} from "@angular/forms";
import { combineLatestWith, Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
@@ -13,7 +14,9 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Policy } from "@bitwarden/common/models/domain/policy";
@Directive()
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
export class VaultTimeoutInputComponent
implements ControlValueAccessor, Validator, OnInit, OnDestroy
{
get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
}
@@ -42,29 +45,28 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void;
private destroy$ = new Subject<void>();
constructor(
private formBuilder: UntypedFormBuilder,
private formBuilder: FormBuilder,
private policyService: PolicyService,
private i18nService: I18nService
) {}
async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
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[0];
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
this.applyVaultTimeoutPolicy();
}
});
// eslint-disable-next-line rxjs/no-async-subscribe
this.form.valueChanges.subscribe(async (value) => {
@@ -87,6 +89,11 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
ngOnChanges() {
this.vaultTimeouts.push({
name: this.i18nService.t("custom"),
@@ -152,6 +159,19 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
}
private customTimeInMinutes() {
return this.form.get("custom.hours")?.value * 60 + this.form.get("custom.minutes")?.value;
return this.form.value.custom.hours * 60 + this.form.value.custom.minutes;
}
private applyVaultTimeoutPolicy() {
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
}

View File

@@ -60,7 +60,6 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
}
async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations();

View File

@@ -1,10 +1,9 @@
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { ErrorResponse } from "@bitwarden/common/models/response/errorResponse";
import { ValidationService } from "../services/validation.service";
/**
* Provides error handling, in particular for any error returned by the server in an api call.
* Attach it to a <form> element and provide the name of the class property that will hold the api call promise.

View File

@@ -55,6 +55,7 @@ import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/comm
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/abstractions/validation.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
@@ -102,6 +103,7 @@ import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service";
import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service";
import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service";
import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service";
import { ValidationService } from "@bitwarden/common/services/validation.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
@@ -127,12 +129,10 @@ import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./passwordReprompt.service";
import { ThemingService } from "./theming/theming.service";
import { AbstractThemingService } from "./theming/theming.service.abstraction";
import { ValidationService } from "./validation.service";
@NgModule({
declarations: [],
providers: [
ValidationService,
AuthGuard,
UnauthGuard,
LockGuard,
@@ -561,6 +561,11 @@ import { ValidationService } from "./validation.service";
useClass: AnonymousHubService,
deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService],
},
{
provide: ValidationServiceAbstraction,
useClass: ValidationService,
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
},
],
})
export class JslibServicesModule {}

View File

@@ -49,6 +49,11 @@ export class ModalService {
return this.modalList[this.modalCount - 1];
}
/**
* @deprecated Use `dialogService.open` (in web) or `modalService.open` (in desktop/browser) instead.
* If replacing an existing call to this method, also remove any `@ViewChild` and `<ng-template>` associated with the
* existing usage.
*/
async openViewRef<T>(
componentType: Type<T>,
viewContainerRef: ViewContainerRef,

View File

@@ -1,38 +0,0 @@
import { Injectable } from "@angular/core";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ErrorResponse } from "@bitwarden/common/models/response/errorResponse";
@Injectable()
export class ValidationService {
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
) {}
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t("unexpectedError");
let errors: string[] = [];
if (data != null && typeof data === "string") {
errors.push(data);
} else if (data == null || typeof data !== "object") {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@angular/core";
import { firstValueFrom, mergeMap, Observable, from } from "rxjs";
import { firstValueFrom, from, mergeMap, Observable } from "rxjs";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/abstractions/deprecated-vault-filter.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
@@ -15,6 +14,7 @@ import { TreeNode } from "@bitwarden/common/models/domain/treeNode";
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
import { FolderView } from "@bitwarden/common/models/view/folderView";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../../abstractions/deprecated-vault-filter.service";
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
const NestingDelimiter = "/";
@@ -84,11 +84,15 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
}
async checkForSingleOrganizationPolicy(): Promise<boolean> {
return await this.policyService.policyAppliesToUser(PolicyType.SingleOrg);
return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg)
);
}
async checkForPersonalOwnershipPolicy(): Promise<boolean> {
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
);
}
protected async getAllFoldersNested(folders: FolderView[]): Promise<TreeNode<FolderView>[]> {