mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 09:03:28 +00:00
Merge branch 'feature/org-admin-refresh' into EC-86-groups-table
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||
|
||||
import { DynamicTreeNode } from "../vault/vault-filter/models/dynamic-tree-node.model";
|
||||
|
||||
/**
|
||||
* @deprecated August 30 2022: Use new VaultFilterService with observables
|
||||
*/
|
||||
export abstract class DeprecatedVaultFilterService {
|
||||
buildOrganizations: () => Promise<Organization[]>;
|
||||
buildNestedFolders: (organizationId?: string) => Observable<DynamicTreeNode<FolderView>>;
|
||||
buildCollections: (organizationId?: string) => Promise<DynamicTreeNode<CollectionView>>;
|
||||
buildCollapsedFilterNodes: () => Promise<Set<string>>;
|
||||
storeCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
|
||||
checkForSingleOrganizationPolicy: () => Promise<boolean>;
|
||||
checkForPersonalOwnershipPolicy: () => Promise<boolean>;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { AbstractControl, UntypedFormBuilder, ValidatorFn, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { InputsFieldMatch } from "@bitwarden/angular/validators/inputsFieldMatch.validator";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
@@ -24,6 +23,7 @@ import { RegisterRequest } from "@bitwarden/common/models/request/registerReques
|
||||
import { RegisterResponse } from "@bitwarden/common/models/response/authentication/registerResponse";
|
||||
|
||||
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
|
||||
import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||
|
||||
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/abstractions/account/account.service.abstraction";
|
||||
@@ -58,6 +56,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";
|
||||
@@ -106,6 +105,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";
|
||||
@@ -129,12 +129,12 @@ import {
|
||||
} from "./injection-tokens";
|
||||
import { ModalService } from "./modal.service";
|
||||
import { PasswordRepromptService } from "./passwordReprompt.service";
|
||||
import { ValidationService } from "./validation.service";
|
||||
import { ThemingService } from "./theming/theming.service";
|
||||
import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
providers: [
|
||||
ValidationService,
|
||||
AuthGuard,
|
||||
UnauthGuard,
|
||||
LockGuard,
|
||||
@@ -563,6 +563,11 @@ import { ValidationService } from "./validation.service";
|
||||
useClass: AnonymousHubService,
|
||||
deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService],
|
||||
},
|
||||
{
|
||||
provide: ValidationServiceAbstraction,
|
||||
useClass: ValidationService,
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: GroupServiceAbstraction,
|
||||
useClass: GroupService,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,10 +6,12 @@ import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||
|
||||
import { DeprecatedVaultFilterService } from "../../../abstractions/deprecated-vault-filter.service";
|
||||
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
|
||||
import { VaultFilter } from "../models/vault-filter.model";
|
||||
import { VaultFilterService } from "../services/vault-filter.service";
|
||||
|
||||
// TODO: Replace with refactored web vault filter component
|
||||
// and refactor desktop/browser vault filters
|
||||
@Directive()
|
||||
export class VaultFilterComponent implements OnInit {
|
||||
@Input() activeFilter: VaultFilter = new VaultFilter();
|
||||
@@ -31,7 +33,7 @@ export class VaultFilterComponent implements OnInit {
|
||||
collections: DynamicTreeNode<CollectionView>;
|
||||
folders$: Observable<DynamicTreeNode<FolderView>>;
|
||||
|
||||
constructor(protected vaultFilterService: VaultFilterService) {}
|
||||
constructor(protected vaultFilterService: DeprecatedVaultFilterService) {}
|
||||
|
||||
get displayCollections() {
|
||||
return this.collections?.fullList != null && this.collections.fullList.length > 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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 { ITreeNodeObject, TreeNode } from "@bitwarden/common/models/domain/treeNode";
|
||||
|
||||
export class DynamicTreeNode<T extends CollectionView | FolderView> {
|
||||
export class DynamicTreeNode<T extends ITreeNodeObject> {
|
||||
fullList: T[];
|
||||
nestedList: TreeNode<T>[];
|
||||
|
||||
|
||||
@@ -14,12 +14,13 @@ 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 = "/";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService {
|
||||
export class VaultFilterService implements DeprecatedVaultFilterServiceAbstraction {
|
||||
constructor(
|
||||
protected stateService: StateService,
|
||||
protected organizationService: OrganizationService,
|
||||
@@ -83,11 +84,15 @@ export class VaultFilterService {
|
||||
}
|
||||
|
||||
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>[]> {
|
||||
@@ -106,6 +111,6 @@ export class VaultFilterService {
|
||||
const folders = await this.getAllFoldersNested(
|
||||
await firstValueFrom(this.folderService.folderViews$)
|
||||
);
|
||||
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
|
||||
return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode<FolderView>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
|
||||
import { makeStaticByteArray, mockEnc } from "../../utils";
|
||||
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Attachment", () => {
|
||||
let data: AttachmentData;
|
||||
@@ -131,4 +131,25 @@ describe("Attachment", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = Attachment.fromJSON({
|
||||
key: "myKey",
|
||||
fileName: "myFileName",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
key: "myKey_fromJSON",
|
||||
fileName: "myFileName_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Attachment);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Attachment.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CardData } from "@bitwarden/common/models/data/cardData";
|
||||
import { Card } from "@bitwarden/common/models/domain/card";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Card", () => {
|
||||
let data: CardData;
|
||||
@@ -70,4 +71,33 @@ describe("Card", () => {
|
||||
expYear: "expYear",
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = Card.fromJSON({
|
||||
cardholderName: "mockCardHolder",
|
||||
brand: "mockBrand",
|
||||
number: "mockNumber",
|
||||
expMonth: "mockExpMonth",
|
||||
expYear: "mockExpYear",
|
||||
code: "mockCode",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
cardholderName: "mockCardHolder_fromJSON",
|
||||
brand: "mockBrand_fromJSON",
|
||||
number: "mockNumber_fromJSON",
|
||||
expMonth: "mockExpMonth_fromJSON",
|
||||
expYear: "mockExpYear_fromJSON",
|
||||
code: "mockCode_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Card);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Card.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType";
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
@@ -6,16 +8,20 @@ import { FieldType } from "@bitwarden/common/enums/fieldType";
|
||||
import { SecureNoteType } from "@bitwarden/common/enums/secureNoteType";
|
||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||
import { CipherData } from "@bitwarden/common/models/data/cipherData";
|
||||
import { Attachment } from "@bitwarden/common/models/domain/attachment";
|
||||
import { Card } from "@bitwarden/common/models/domain/card";
|
||||
import { Cipher } from "@bitwarden/common/models/domain/cipher";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Field } from "@bitwarden/common/models/domain/field";
|
||||
import { Identity } from "@bitwarden/common/models/domain/identity";
|
||||
import { Login } from "@bitwarden/common/models/domain/login";
|
||||
import { Password } from "@bitwarden/common/models/domain/password";
|
||||
import { SecureNote } from "@bitwarden/common/models/domain/secureNote";
|
||||
import { CardView } from "@bitwarden/common/models/view/cardView";
|
||||
import { IdentityView } from "@bitwarden/common/models/view/identityView";
|
||||
import { LoginView } from "@bitwarden/common/models/view/loginView";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Cipher DTO", () => {
|
||||
it("Convert from empty CipherData", () => {
|
||||
@@ -587,4 +593,63 @@ describe("Cipher DTO", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(Attachment, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(Field, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(Password, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const revisionDate = new Date("2022-08-04T01:06:40.441Z");
|
||||
const deletedDate = new Date("2022-09-04T01:06:40.441Z");
|
||||
const actual = Cipher.fromJSON({
|
||||
name: "myName",
|
||||
notes: "myNotes",
|
||||
revisionDate: revisionDate.toISOString(),
|
||||
attachments: ["attachment1", "attachment2"] as any,
|
||||
fields: ["field1", "field2"] as any,
|
||||
passwordHistory: ["ph1", "ph2"] as any,
|
||||
deletedDate: deletedDate.toISOString(),
|
||||
} as Jsonify<Cipher>);
|
||||
|
||||
expect(actual).toMatchObject({
|
||||
name: "myName_fromJSON",
|
||||
notes: "myNotes_fromJSON",
|
||||
revisionDate: revisionDate,
|
||||
attachments: ["attachment1_fromJSON", "attachment2_fromJSON"],
|
||||
fields: ["field1_fromJSON", "field2_fromJSON"],
|
||||
passwordHistory: ["ph1_fromJSON", "ph2_fromJSON"],
|
||||
deletedDate: deletedDate,
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Cipher);
|
||||
});
|
||||
|
||||
test.each([
|
||||
// Test description, CipherType, expected output
|
||||
["LoginView", CipherType.Login, { login: "myLogin_fromJSON" }],
|
||||
["CardView", CipherType.Card, { card: "myCard_fromJSON" }],
|
||||
["IdentityView", CipherType.Identity, { identity: "myIdentity_fromJSON" }],
|
||||
["Secure Note", CipherType.SecureNote, { secureNote: "mySecureNote_fromJSON" }],
|
||||
])("initializes %s", (description: string, cipherType: CipherType, expected: any) => {
|
||||
jest.spyOn(Login, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(Identity, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(Card, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(SecureNote, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = Cipher.fromJSON({
|
||||
login: "myLogin",
|
||||
card: "myCard",
|
||||
identity: "myIdentity",
|
||||
secureNote: "mySecureNote",
|
||||
type: cipherType,
|
||||
} as any);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Cipher.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
@@ -226,5 +227,9 @@ describe("EncString", () => {
|
||||
|
||||
expect(encString.toJSON()).toBe(encString.encryptedString);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(EncString.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FieldType } from "@bitwarden/common/enums/fieldType";
|
||||
import { FieldData } from "@bitwarden/common/models/data/fieldData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Field } from "@bitwarden/common/models/domain/field";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Field", () => {
|
||||
let data: FieldData;
|
||||
@@ -61,4 +62,25 @@ describe("Field", () => {
|
||||
showValue: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = Field.fromJSON({
|
||||
name: "myName",
|
||||
value: "myValue",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
name: "myName_fromJSON",
|
||||
value: "myValue_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Field);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Field.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FolderData } from "@bitwarden/common/models/data/folderData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Folder", () => {
|
||||
let data: FolderData;
|
||||
@@ -42,7 +42,6 @@ describe("Folder", () => {
|
||||
|
||||
describe("fromJSON", () => {
|
||||
jest.mock("@bitwarden/common/models/domain/encString");
|
||||
const mockFromJson = (stub: any) => (stub + "_fromJSON") as any;
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
it("initializes nested objects", () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IdentityData } from "@bitwarden/common/models/data/identityData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Identity } from "@bitwarden/common/models/domain/identity";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Identity", () => {
|
||||
let data: IdentityData;
|
||||
@@ -131,4 +132,57 @@ describe("Identity", () => {
|
||||
username: "mockUsername",
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = Identity.fromJSON({
|
||||
firstName: "mockFirstName",
|
||||
lastName: "mockLastName",
|
||||
address1: "mockAddress1",
|
||||
address2: "mockAddress2",
|
||||
address3: "mockAddress3",
|
||||
city: "mockCity",
|
||||
company: "mockCompany",
|
||||
country: "mockCountry",
|
||||
email: "mockEmail",
|
||||
licenseNumber: "mockLicenseNumber",
|
||||
middleName: "mockMiddleName",
|
||||
passportNumber: "mockPassportNumber",
|
||||
phone: "mockPhone",
|
||||
postalCode: "mockPostalCode",
|
||||
ssn: "mockSsn",
|
||||
state: "mockState",
|
||||
title: "mockTitle",
|
||||
username: "mockUsername",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
firstName: "mockFirstName_fromJSON",
|
||||
lastName: "mockLastName_fromJSON",
|
||||
address1: "mockAddress1_fromJSON",
|
||||
address2: "mockAddress2_fromJSON",
|
||||
address3: "mockAddress3_fromJSON",
|
||||
city: "mockCity_fromJSON",
|
||||
company: "mockCompany_fromJSON",
|
||||
country: "mockCountry_fromJSON",
|
||||
email: "mockEmail_fromJSON",
|
||||
licenseNumber: "mockLicenseNumber_fromJSON",
|
||||
middleName: "mockMiddleName_fromJSON",
|
||||
passportNumber: "mockPassportNumber_fromJSON",
|
||||
phone: "mockPhone_fromJSON",
|
||||
postalCode: "mockPostalCode_fromJSON",
|
||||
ssn: "mockSsn_fromJSON",
|
||||
state: "mockState_fromJSON",
|
||||
title: "mockTitle_fromJSON",
|
||||
username: "mockUsername_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Identity);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Identity.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||
import { LoginData } from "@bitwarden/common/models/data/loginData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Login } from "@bitwarden/common/models/domain/login";
|
||||
import { LoginUri } from "@bitwarden/common/models/domain/loginUri";
|
||||
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Login DTO", () => {
|
||||
it("Convert from empty LoginData", () => {
|
||||
@@ -98,4 +100,33 @@ describe("Login DTO", () => {
|
||||
|
||||
expect(loginData).toEqual(data);
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(LoginUri, "fromJSON").mockImplementation(mockFromJson);
|
||||
const passwordRevisionDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
|
||||
const actual = Login.fromJSON({
|
||||
uris: ["loginUri1", "loginUri2"] as any,
|
||||
username: "myUsername",
|
||||
password: "myPassword",
|
||||
passwordRevisionDate: passwordRevisionDate.toISOString(),
|
||||
totp: "myTotp",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
uris: ["loginUri1_fromJSON", "loginUri2_fromJSON"] as any,
|
||||
username: "myUsername_fromJSON",
|
||||
password: "myPassword_fromJSON",
|
||||
passwordRevisionDate: passwordRevisionDate,
|
||||
totp: "myTotp_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Login);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Login.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||
import { LoginUriData } from "@bitwarden/common/models/data/loginUriData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { LoginUri } from "@bitwarden/common/models/domain/loginUri";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("LoginUri", () => {
|
||||
let data: LoginUriData;
|
||||
@@ -54,4 +57,23 @@ describe("LoginUri", () => {
|
||||
match: 3,
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = LoginUri.fromJSON({
|
||||
uri: "myUri",
|
||||
} as Jsonify<LoginUri>);
|
||||
|
||||
expect(actual).toEqual({
|
||||
uri: "myUri_fromJSON",
|
||||
});
|
||||
expect(actual).toBeInstanceOf(LoginUri);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(LoginUri.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { PasswordHistoryData } from "@bitwarden/common/models/data/passwordHistoryData";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { Password } from "@bitwarden/common/models/domain/password";
|
||||
|
||||
import { mockEnc } from "../../utils";
|
||||
import { mockEnc, mockFromJson } from "../../utils";
|
||||
|
||||
describe("Password", () => {
|
||||
let data: PasswordHistoryData;
|
||||
@@ -48,4 +49,26 @@ describe("Password", () => {
|
||||
lastUsedDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson);
|
||||
const lastUsedDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
|
||||
const actual = Password.fromJSON({
|
||||
password: "myPassword",
|
||||
lastUsedDate: lastUsedDate.toISOString(),
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
password: "myPassword_fromJSON",
|
||||
lastUsedDate: lastUsedDate,
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Password);
|
||||
});
|
||||
|
||||
it("returns null if object is null", () => {
|
||||
expect(Password.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,4 +43,10 @@ describe("SecureNote", () => {
|
||||
type: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("returns null if object is null", () => {
|
||||
expect(SecureNote.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
import { AttachmentView } from "@bitwarden/common/models/view/attachmentView";
|
||||
|
||||
import { mockFromJson } from "../../utils";
|
||||
|
||||
jest.mock("@bitwarden/common/models/domain/symmetricCryptoKey");
|
||||
|
||||
describe("AttachmentView", () => {
|
||||
it("fromJSON initializes nested objects", () => {
|
||||
const mockFromJson = (stub: string) => stub + "_fromJSON";
|
||||
jest.spyOn(SymmetricCryptoKey, "fromJSON").mockImplementation(mockFromJson as any);
|
||||
jest.spyOn(SymmetricCryptoKey, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const actual = AttachmentView.fromJSON({
|
||||
key: "encKeyB64" as any,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { LoginView } from "@bitwarden/common/models/view/loginView";
|
||||
import { PasswordHistoryView } from "@bitwarden/common/models/view/passwordHistoryView";
|
||||
import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView";
|
||||
|
||||
import { mockFromJson } from "../../utils";
|
||||
|
||||
jest.mock("@bitwarden/common/models/view/loginView");
|
||||
jest.mock("@bitwarden/common/models/view/attachmentView");
|
||||
jest.mock("@bitwarden/common/models/view/fieldView");
|
||||
@@ -22,8 +24,6 @@ describe("CipherView", () => {
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
const mockFromJson = (stub: any) => (stub + "_fromJSON") as any;
|
||||
|
||||
it("initializes nested objects", () => {
|
||||
jest.spyOn(AttachmentView, "fromJSON").mockImplementation(mockFromJson);
|
||||
jest.spyOn(FieldView, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
|
||||
import { LoginView } from "@bitwarden/common/models/view/loginView";
|
||||
|
||||
import { mockFromJson } from "../../utils";
|
||||
|
||||
jest.mock("@bitwarden/common/models/view/loginUriView");
|
||||
|
||||
describe("LoginView", () => {
|
||||
@@ -9,8 +11,7 @@ describe("LoginView", () => {
|
||||
});
|
||||
|
||||
it("fromJSON initializes nested objects", () => {
|
||||
const mockFromJson = (stub: string) => stub + "_fromJSON";
|
||||
jest.spyOn(LoginUriView, "fromJSON").mockImplementation(mockFromJson as any);
|
||||
jest.spyOn(LoginUriView, "fromJSON").mockImplementation(mockFromJson);
|
||||
|
||||
const passwordRevisionDate = new Date();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
357
libs/common/spec/services/policy.service.spec.ts
Normal file
357
libs/common/spec/services/policy.service.spec.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PermissionsApi } from "@bitwarden/common/models/api/permissionsApi";
|
||||
import { OrganizationData } from "@bitwarden/common/models/data/organizationData";
|
||||
import { PolicyData } from "@bitwarden/common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "@bitwarden/common/models/domain/resetPasswordPolicyOptions";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
|
||||
import { StateService } from "@bitwarden/common/services/state.service";
|
||||
|
||||
describe("PolicyService", () => {
|
||||
let policyService: PolicyService;
|
||||
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let organizationService: SubstituteOf<OrganizationService>;
|
||||
let encryptService: SubstituteOf<EncryptService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
stateService = Substitute.for();
|
||||
organizationService = Substitute.for();
|
||||
organizationService
|
||||
.getAll("user")
|
||||
.resolves([
|
||||
new Organization(
|
||||
organizationData(
|
||||
"test-organization",
|
||||
true,
|
||||
true,
|
||||
OrganizationUserStatusType.Accepted,
|
||||
false
|
||||
)
|
||||
),
|
||||
]);
|
||||
organizationService.getAll(undefined).resolves([]);
|
||||
organizationService.getAll(null).resolves([]);
|
||||
activeAccount = new BehaviorSubject("123");
|
||||
activeAccountUnlocked = new BehaviorSubject(true);
|
||||
stateService.getEncryptedPolicies().resolves({
|
||||
"1": policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, {
|
||||
minutes: 14,
|
||||
}),
|
||||
});
|
||||
stateService.activeAccount$.returns(activeAccount);
|
||||
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
|
||||
stateService.getUserId().resolves("user");
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
policyService = new PolicyService(stateService, organizationService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
activeAccount.complete();
|
||||
activeAccountUnlocked.complete();
|
||||
});
|
||||
|
||||
it("upsert", async () => {
|
||||
await policyService.upsert(policyData("99", "test-organization", PolicyType.DisableSend, true));
|
||||
|
||||
expect(await firstValueFrom(policyService.policies$)).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.MaximumVaultTimeout,
|
||||
enabled: true,
|
||||
data: { minutes: 14 },
|
||||
},
|
||||
{
|
||||
id: "99",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("replace", async () => {
|
||||
await policyService.replace({
|
||||
"2": policyData("2", "test-organization", PolicyType.DisableSend, true),
|
||||
});
|
||||
|
||||
expect(await firstValueFrom(policyService.policies$)).toEqual([
|
||||
{
|
||||
id: "2",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("locking should clear", async () => {
|
||||
activeAccountUnlocked.next(false);
|
||||
// Sleep for 100ms to avoid timing issues
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
describe("clear", () => {
|
||||
it("null userId", async () => {
|
||||
await policyService.clear();
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
it("matching userId", async () => {
|
||||
await policyService.clear("user");
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
it("mismatching userId", async () => {
|
||||
await policyService.clear("12");
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("masterPasswordPolicyOptions", () => {
|
||||
it("returns default policy options", async () => {
|
||||
const data: any = {
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
requireUpper: true,
|
||||
};
|
||||
const model = [
|
||||
new Policy(policyData("1", "test-organization-3", PolicyType.MasterPassword, true, data)),
|
||||
];
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual({
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
requireLower: false,
|
||||
requireNumbers: false,
|
||||
requireSpecial: false,
|
||||
requireUpper: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null", async () => {
|
||||
const data: any = {};
|
||||
const model = [
|
||||
new Policy(
|
||||
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data)
|
||||
),
|
||||
new Policy(
|
||||
policyData("4", "test-organization-3", PolicyType.MaximumVaultTimeout, true, data)
|
||||
),
|
||||
];
|
||||
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("returns specified policy options", async () => {
|
||||
const data: any = {
|
||||
minLength: 14,
|
||||
};
|
||||
const model = [
|
||||
new Policy(
|
||||
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data)
|
||||
),
|
||||
new Policy(policyData("4", "test-organization-3", PolicyType.MasterPassword, true, data)),
|
||||
];
|
||||
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual({
|
||||
minComplexity: 0,
|
||||
minLength: 14,
|
||||
requireLower: false,
|
||||
requireNumbers: false,
|
||||
requireSpecial: false,
|
||||
requireUpper: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluateMasterPassword", () => {
|
||||
it("false", async () => {
|
||||
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
enforcedPolicyOptions.minLength = 14;
|
||||
const result = policyService.evaluateMasterPassword(10, "password", enforcedPolicyOptions);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("true", async () => {
|
||||
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
const result = policyService.evaluateMasterPassword(0, "password", enforcedPolicyOptions);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResetPasswordPolicyOptions", () => {
|
||||
it("default", async () => {
|
||||
const result = policyService.getResetPasswordPolicyOptions(null, null);
|
||||
|
||||
expect(result).toEqual([new ResetPasswordPolicyOptions(), false]);
|
||||
});
|
||||
|
||||
it("returns autoEnrollEnabled true", async () => {
|
||||
const data: any = {
|
||||
autoEnrollEnabled: true,
|
||||
};
|
||||
const policies = [
|
||||
new Policy(policyData("5", "test-organization-3", PolicyType.ResetPassword, true, data)),
|
||||
];
|
||||
const result = policyService.getResetPasswordPolicyOptions(policies, "test-organization-3");
|
||||
|
||||
expect(result).toEqual([{ autoEnrollEnabled: true }, true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapPoliciesFromToken", () => {
|
||||
it("null", async () => {
|
||||
const result = policyService.mapPoliciesFromToken(null);
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("null data", async () => {
|
||||
const model = new ListResponse(null, PolicyResponse);
|
||||
model.data = null;
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("empty array", async () => {
|
||||
const model = new ListResponse(null, PolicyResponse);
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("success", async () => {
|
||||
const policyResponse: any = {
|
||||
Data: [
|
||||
{
|
||||
Id: "1",
|
||||
OrganizationId: "organization-1",
|
||||
Type: PolicyType.DisablePersonalVaultExport,
|
||||
Enabled: true,
|
||||
Data: { requireUpper: true },
|
||||
},
|
||||
{
|
||||
Id: "2",
|
||||
OrganizationId: "organization-2",
|
||||
Type: PolicyType.DisableSend,
|
||||
Enabled: false,
|
||||
Data: { minComplexity: 5, minLength: 20 },
|
||||
},
|
||||
],
|
||||
};
|
||||
const model = new ListResponse(policyResponse, PolicyResponse);
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual([
|
||||
new Policy(
|
||||
policyData("1", "organization-1", PolicyType.DisablePersonalVaultExport, true, {
|
||||
requireUpper: true,
|
||||
})
|
||||
),
|
||||
new Policy(
|
||||
policyData("2", "organization-2", PolicyType.DisableSend, false, {
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policyAppliesToActiveUser", () => {
|
||||
it("MasterPassword does not apply", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.MasterPassword)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("MaximumVaultTimeout applies", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
|
||||
);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("DisablePersonalVaultExport does not apply", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
function policyData(
|
||||
id: string,
|
||||
organizationId: string,
|
||||
type: PolicyType,
|
||||
enabled: boolean,
|
||||
data?: any
|
||||
) {
|
||||
const policyData = new PolicyData({} as any);
|
||||
policyData.id = id;
|
||||
policyData.organizationId = organizationId;
|
||||
policyData.type = type;
|
||||
policyData.enabled = enabled;
|
||||
policyData.data = data;
|
||||
|
||||
return policyData;
|
||||
}
|
||||
|
||||
function organizationData(
|
||||
id: string,
|
||||
enabled: boolean,
|
||||
usePolicies: boolean,
|
||||
status: OrganizationUserStatusType,
|
||||
managePolicies: boolean
|
||||
) {
|
||||
const organizationData = new OrganizationData({} as any);
|
||||
organizationData.id = id;
|
||||
organizationData.enabled = enabled;
|
||||
organizationData.usePolicies = usePolicies;
|
||||
organizationData.status = status;
|
||||
organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any);
|
||||
return organizationData;
|
||||
}
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
@@ -35,3 +36,8 @@ export function makeStaticByteArray(length: number, start = 0) {
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to mock a return value of a static fromJSON method.
|
||||
*/
|
||||
export const mockFromJson = (stub: any) => (stub + "_fromJSON") as any;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { IEncrypted } from "../interfaces/IEncrypted";
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
export abstract class AbstractEncryptService {
|
||||
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
||||
import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
|
||||
|
||||
export abstract class AccountApiService {
|
||||
abstract deleteAccount(request: SecretVerificationRequest): Promise<void>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerConfigResponse } from "@bitwarden/common/models/response/server-config-response";
|
||||
import { ServerConfigResponse } from "../../models/response/server-config-response";
|
||||
|
||||
export abstract class ConfigApiServiceAbstraction {
|
||||
get: () => Promise<ServerConfigResponse>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
ServerConfigData,
|
||||
ThirdPartyServerConfigData,
|
||||
EnvironmentServerConfigData,
|
||||
} from "@bitwarden/common/models/data/server-config.data";
|
||||
} from "../../models/data/server-config.data";
|
||||
|
||||
const dayInMilliseconds = 24 * 3600 * 1000;
|
||||
const eighteenHoursInMilliseconds = 18 * 3600 * 1000;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
import { FolderResponse } from "@bitwarden/common/models/response/folderResponse";
|
||||
import { Folder } from "../../models/domain/folder";
|
||||
import { FolderResponse } from "../../models/response/folderResponse";
|
||||
|
||||
export class FolderApiServiceAbstraction {
|
||||
save: (folder: Folder) => Promise<any>;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { PolicyRequest } from "@bitwarden/common/models/request/policyRequest";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { PolicyRequest } from "../../models/request/policyRequest";
|
||||
import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export class PolicyApiServiceAbstraction {
|
||||
getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>;
|
||||
@@ -18,7 +17,6 @@ export class PolicyApiServiceAbstraction {
|
||||
organizationId: string,
|
||||
userId: string
|
||||
) => Promise<ListResponse<PolicyResponse>>;
|
||||
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
|
||||
getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise<MasterPasswordPolicyOptions>;
|
||||
putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
@@ -7,9 +9,17 @@ import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export abstract class PolicyService {
|
||||
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
|
||||
policies$: Observable<Policy[]>;
|
||||
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
|
||||
policyAppliesToActiveUser$: (
|
||||
policyType: PolicyType,
|
||||
policyFilter?: (policy: Policy) => boolean
|
||||
) => Observable<boolean>;
|
||||
|
||||
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
|
||||
evaluateMasterPassword: (
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
@@ -29,6 +39,6 @@ export abstract class PolicyService {
|
||||
|
||||
export abstract class InternalPolicyService extends PolicyService {
|
||||
upsert: (policy: PolicyData) => Promise<any>;
|
||||
replace: (policies: { [id: string]: PolicyData }) => Promise<any>;
|
||||
replace: (policies: { [id: string]: PolicyData }) => Promise<void>;
|
||||
clear: (userId?: string) => Promise<any>;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,13 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
|
||||
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this, use PolicyService
|
||||
*/
|
||||
getDecryptedPolicies: (options?: StorageOptions) => Promise<Policy[]>;
|
||||
/**
|
||||
* @deprecated Do not call this, use PolicyService
|
||||
*/
|
||||
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
||||
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
||||
@@ -214,7 +220,13 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
|
||||
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use PolicyService
|
||||
*/
|
||||
getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use PolicyService
|
||||
*/
|
||||
setEncryptedPolicies: (
|
||||
value: { [id: string]: PolicyData },
|
||||
options?: StorageOptions
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VerifyOTPRequest } from "@bitwarden/common/models/request/account/verifyOTPRequest";
|
||||
import { VerifyOTPRequest } from "../../models/request/account/verifyOTPRequest";
|
||||
|
||||
export abstract class UserVerificationApiServiceAbstraction {
|
||||
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
|
||||
|
||||
3
libs/common/src/abstractions/validation.service.ts
Normal file
3
libs/common/src/abstractions/validation.service.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export abstract class ValidationService {
|
||||
showError: (data: any) => string[];
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
import { ITreeNodeObject, TreeNode } from "../models/domain/treeNode";
|
||||
|
||||
export class ServiceUtils {
|
||||
/**
|
||||
* Recursively adds a node to nodeTree
|
||||
* @param {TreeNode<ITreeNodeObject>[]} nodeTree - An array of TreeNodes that the node will be added to
|
||||
* @param {number} partIndex - Index of the `parts` array that is being processed
|
||||
* @param {string[]} parts - Array of strings that represent the path to the `obj` node
|
||||
* @param {ITreeNodeObject} obj - The node to be added to the tree
|
||||
* @param {ITreeNodeObject} parent - The parent node of the `obj` node
|
||||
* @param {string} delimiter - The delimiter used to split the path string
|
||||
*/
|
||||
static nestedTraverse(
|
||||
nodeTree: TreeNode<ITreeNodeObject>[],
|
||||
partIndex: number,
|
||||
@@ -22,7 +31,7 @@ export class ServiceUtils {
|
||||
}
|
||||
if (end && nodeTree[i].node.id !== obj.id) {
|
||||
// Another node with the same name.
|
||||
nodeTree.push(new TreeNode(obj, partName, parent));
|
||||
nodeTree.push(new TreeNode(obj, parent, partName));
|
||||
return;
|
||||
}
|
||||
ServiceUtils.nestedTraverse(
|
||||
@@ -38,7 +47,7 @@ export class ServiceUtils {
|
||||
|
||||
if (nodeTree.filter((n) => n.node.name === partName).length === 0) {
|
||||
if (end) {
|
||||
nodeTree.push(new TreeNode(obj, partName, parent));
|
||||
nodeTree.push(new TreeNode(obj, parent, partName));
|
||||
return;
|
||||
}
|
||||
const newPartName = parts[partIndex] + delimiter + parts[partIndex + 1];
|
||||
@@ -53,7 +62,37 @@ export class ServiceUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a tree for a node with a matching `id`
|
||||
* @param {TreeNode<ITreeNodeObject>} nodeTree - A single TreeNode branch that will be searched
|
||||
* @param {string} id - The id of the node to be found
|
||||
* @returns {TreeNode<ITreeNodeObject>} The node with a matching `id`
|
||||
*/
|
||||
static getTreeNodeObject(
|
||||
nodeTree: TreeNode<ITreeNodeObject>,
|
||||
id: string
|
||||
): TreeNode<ITreeNodeObject> {
|
||||
if (nodeTree.node.id === id) {
|
||||
return nodeTree;
|
||||
}
|
||||
for (let i = 0; i < nodeTree.children.length; i++) {
|
||||
if (nodeTree.children[i].children != null) {
|
||||
const node = ServiceUtils.getTreeNodeObject(nodeTree.children[i], id);
|
||||
if (node !== null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches an array of tree nodes for a node with a matching `id`
|
||||
* @param {TreeNode<ITreeNodeObject>} nodeTree - An array of TreeNode branches that will be searched
|
||||
* @param {string} id - The id of the node to be found
|
||||
* @returns {TreeNode<ITreeNodeObject>} The node with a matching `id`
|
||||
*/
|
||||
static getTreeNodeObjectFromList(
|
||||
nodeTree: TreeNode<ITreeNodeObject>[],
|
||||
id: string
|
||||
): TreeNode<ITreeNodeObject> {
|
||||
@@ -61,7 +100,7 @@ export class ServiceUtils {
|
||||
if (nodeTree[i].node.id === id) {
|
||||
return nodeTree[i];
|
||||
} else if (nodeTree[i].children != null) {
|
||||
const node = ServiceUtils.getTreeNodeObject(nodeTree[i].children, id);
|
||||
const node = ServiceUtils.getTreeNodeObjectFromList(nodeTree[i].children, id);
|
||||
if (node !== null) {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import * as tldjs from "tldjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
||||
@@ -340,6 +339,12 @@ export class Utils {
|
||||
return str == null || typeof str !== "string" || str == "";
|
||||
}
|
||||
|
||||
static isPromise(obj: any): obj is Promise<unknown> {
|
||||
return (
|
||||
obj != undefined && typeof obj["then"] === "function" && typeof obj["catch"] === "function"
|
||||
);
|
||||
}
|
||||
|
||||
static nameOf<T>(name: string & keyof T) {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ScimProviderType } from "@bitwarden/common/enums/scimProviderType";
|
||||
|
||||
import { ScimProviderType } from "../../enums/scimProviderType";
|
||||
import { BaseResponse } from "../response/baseResponse";
|
||||
|
||||
export class ScimConfigApi extends BaseResponse {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
import { makeStaticByteArray } from "../../../spec/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { AccountKeys, EncryptionPair } from "./account";
|
||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Except, Jsonify } from "type-fest";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
|
||||
|
||||
import { AuthenticationStatus } from "../../enums/authenticationStatus";
|
||||
import { KdfType } from "../../enums/kdfType";
|
||||
import { UriMatchType } from "../../enums/uriMatchType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { DeepJsonify } from "../../types/deep-jsonify";
|
||||
import { CipherData } from "../data/cipherData";
|
||||
import { CollectionData } from "../data/collectionData";
|
||||
import { EncryptedOrganizationKeyData } from "../data/encryptedOrganizationKeyData";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { AttachmentData } from "../data/attachmentData";
|
||||
import { AttachmentView } from "../view/attachmentView";
|
||||
@@ -90,4 +92,18 @@ export class Attachment extends Domain {
|
||||
);
|
||||
return a;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<Attachment>>): Attachment {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = EncString.fromJSON(obj.key);
|
||||
const fileName = EncString.fromJSON(obj.fileName);
|
||||
|
||||
return Object.assign(new Attachment(), obj, {
|
||||
key,
|
||||
fileName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { CardData } from "../data/cardData";
|
||||
import { CardView } from "../view/cardView";
|
||||
|
||||
@@ -62,4 +64,25 @@ export class Card extends Domain {
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<Card>>): Card {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cardholderName = EncString.fromJSON(obj.cardholderName);
|
||||
const brand = EncString.fromJSON(obj.brand);
|
||||
const number = EncString.fromJSON(obj.number);
|
||||
const expMonth = EncString.fromJSON(obj.expMonth);
|
||||
const expYear = EncString.fromJSON(obj.expYear);
|
||||
const code = EncString.fromJSON(obj.code);
|
||||
return Object.assign(new Card(), obj, {
|
||||
cardholderName,
|
||||
brand,
|
||||
number,
|
||||
expMonth,
|
||||
expYear,
|
||||
code,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { CipherData } from "../data/cipherData";
|
||||
@@ -234,4 +236,48 @@ export class Cipher extends Domain {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<Cipher>) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domain = new Cipher();
|
||||
const name = EncString.fromJSON(obj.name);
|
||||
const notes = EncString.fromJSON(obj.notes);
|
||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||
const attachments = obj.attachments?.map((a: any) => Attachment.fromJSON(a));
|
||||
const fields = obj.fields?.map((f: any) => Field.fromJSON(f));
|
||||
const passwordHistory = obj.passwordHistory?.map((ph: any) => Password.fromJSON(ph));
|
||||
|
||||
Object.assign(domain, obj, {
|
||||
name,
|
||||
notes,
|
||||
revisionDate,
|
||||
deletedDate,
|
||||
attachments,
|
||||
fields,
|
||||
passwordHistory,
|
||||
});
|
||||
|
||||
switch (obj.type) {
|
||||
case CipherType.Card:
|
||||
domain.card = Card.fromJSON(obj.card);
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
domain.identity = Identity.fromJSON(obj.identity);
|
||||
break;
|
||||
case CipherType.Login:
|
||||
domain.login = Login.fromJSON(obj.login);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
domain.secureNote = SecureNote.fromJSON(obj.secureNote);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EncryptionType } from "@bitwarden/common/enums/encryptionType";
|
||||
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import { IEncrypted } from "../../interfaces/IEncrypted";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
const ENC_TYPE_LENGTH = 1;
|
||||
const IV_LENGTH = 16;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
|
||||
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import { IEncrypted } from "../../interfaces/IEncrypted";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
||||
@@ -45,6 +44,10 @@ export class EncString implements IEncrypted {
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<EncString>): EncString {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EncString(obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { EncryptionPair } from "./account";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { LinkedIdType } from "../../enums/linkedIdType";
|
||||
import { FieldData } from "../data/fieldData";
|
||||
@@ -59,4 +61,18 @@ export class Field extends Domain {
|
||||
);
|
||||
return f;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<Field>>): Field {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = EncString.fromJSON(obj.name);
|
||||
const value = EncString.fromJSON(obj.value);
|
||||
|
||||
return Object.assign(new Field(), obj, {
|
||||
name,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { IdentityData } from "../data/identityData";
|
||||
import { IdentityView } from "../view/identityView";
|
||||
|
||||
@@ -110,4 +112,50 @@ export class Identity extends Domain {
|
||||
});
|
||||
return i;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<Identity>): Identity {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = EncString.fromJSON(obj.title);
|
||||
const firstName = EncString.fromJSON(obj.firstName);
|
||||
const middleName = EncString.fromJSON(obj.middleName);
|
||||
const lastName = EncString.fromJSON(obj.lastName);
|
||||
const address1 = EncString.fromJSON(obj.address1);
|
||||
const address2 = EncString.fromJSON(obj.address2);
|
||||
const address3 = EncString.fromJSON(obj.address3);
|
||||
const city = EncString.fromJSON(obj.city);
|
||||
const state = EncString.fromJSON(obj.state);
|
||||
const postalCode = EncString.fromJSON(obj.postalCode);
|
||||
const country = EncString.fromJSON(obj.country);
|
||||
const company = EncString.fromJSON(obj.company);
|
||||
const email = EncString.fromJSON(obj.email);
|
||||
const phone = EncString.fromJSON(obj.phone);
|
||||
const ssn = EncString.fromJSON(obj.ssn);
|
||||
const username = EncString.fromJSON(obj.username);
|
||||
const passportNumber = EncString.fromJSON(obj.passportNumber);
|
||||
const licenseNumber = EncString.fromJSON(obj.licenseNumber);
|
||||
|
||||
return Object.assign(new Identity(), obj, {
|
||||
title,
|
||||
firstName,
|
||||
middleName,
|
||||
lastName,
|
||||
address1,
|
||||
address2,
|
||||
address3,
|
||||
city,
|
||||
state,
|
||||
postalCode,
|
||||
country,
|
||||
company,
|
||||
email,
|
||||
phone,
|
||||
ssn,
|
||||
username,
|
||||
passportNumber,
|
||||
licenseNumber,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { AuthenticationType } from "../../enums/authenticationType";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
||||
import { TokenRequestTwoFactor } from "../request/identityToken/tokenRequestTwoFactor";
|
||||
|
||||
export class PasswordLogInCredentials {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { LoginData } from "../data/loginData";
|
||||
import { LoginView } from "../view/loginView";
|
||||
|
||||
@@ -85,4 +87,25 @@ export class Login extends Domain {
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<Login>>): Login {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = EncString.fromJSON(obj.username);
|
||||
const password = EncString.fromJSON(obj.password);
|
||||
const totp = EncString.fromJSON(obj.totp);
|
||||
const passwordRevisionDate =
|
||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||
const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri));
|
||||
|
||||
return Object.assign(new Login(), obj, {
|
||||
username,
|
||||
password,
|
||||
totp,
|
||||
passwordRevisionDate: passwordRevisionDate,
|
||||
uris: uris,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { UriMatchType } from "../../enums/uriMatchType";
|
||||
import { LoginUriData } from "../data/loginUriData";
|
||||
import { LoginUriView } from "../view/loginUriView";
|
||||
@@ -51,4 +53,15 @@ export class LoginUri extends Domain {
|
||||
);
|
||||
return u;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<LoginUri>): LoginUri {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const uri = EncString.fromJSON(obj.uri);
|
||||
return Object.assign(new LoginUri(), obj, {
|
||||
uri,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,10 @@ export class Organization {
|
||||
);
|
||||
}
|
||||
|
||||
get canUseAdminCollections() {
|
||||
return this.canEditAnyCollection;
|
||||
}
|
||||
|
||||
get canDeleteAnyCollection() {
|
||||
return (
|
||||
this.isAdmin ||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { PasswordHistoryData } from "../data/passwordHistoryData";
|
||||
import { PasswordHistoryView } from "../view/passwordHistoryView";
|
||||
|
||||
@@ -40,4 +42,18 @@ export class Password extends Domain {
|
||||
});
|
||||
return ph;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<Password>>): Password {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const password = EncString.fromJSON(obj.password);
|
||||
const lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate);
|
||||
|
||||
return Object.assign(new Password(), obj, {
|
||||
password,
|
||||
lastUsedDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { SecureNoteData } from "../data/secureNoteData";
|
||||
import { SecureNoteView } from "../view/secureNoteView";
|
||||
@@ -26,4 +28,12 @@ export class SecureNote extends Domain {
|
||||
n.type = this.type;
|
||||
return n;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<SecureNote>): SecureNote {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SecureNote(), obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
export class SymmetricCryptoKey {
|
||||
key: ArrayBuffer;
|
||||
|
||||
@@ -3,10 +3,15 @@ export class TreeNode<T extends ITreeNodeObject> {
|
||||
node: T;
|
||||
children: TreeNode<T>[] = [];
|
||||
|
||||
constructor(node: T, name: string, parent: T) {
|
||||
constructor(node: T, parent: T, name?: string, id?: string) {
|
||||
this.parent = parent;
|
||||
this.node = node;
|
||||
this.node.name = name;
|
||||
if (name) {
|
||||
this.node.name = name;
|
||||
}
|
||||
if (id) {
|
||||
this.node.id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ScimProviderType } from "@bitwarden/common/enums/scimProviderType";
|
||||
import { ScimProviderType } from "../../enums/scimProviderType";
|
||||
|
||||
export class ScimConfigRequest {
|
||||
constructor(private enabled: boolean, private scimProvider: ScimProviderType = null) {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceType } from "@bitwarden/common/enums/deviceType";
|
||||
import { DeviceType } from "../../enums/deviceType";
|
||||
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "../../abstractions/account/account-api.service.abstraction";
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
|
||||
|
||||
export class AccountApiService implements AccountApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
|
||||
import { AccountApiService } from "../../abstractions/account/account-api.service.abstraction";
|
||||
import { AccountService as AccountServiceAbstraction } from "../../abstractions/account/account.service.abstraction";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { Verification } from "../../types/verification";
|
||||
|
||||
export class AccountService implements AccountServiceAbstraction {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class AnonymousHubService implements AnonymousHubServiceAbstraction {
|
||||
this.url = this.environmentService.getNotificationsUrl();
|
||||
|
||||
this.anonHubConnection = new HubConnectionBuilder()
|
||||
.withUrl(this.url + "/anonymousHub?Token=" + token, {
|
||||
.withUrl(this.url + "/anonymous-hub?Token=" + token, {
|
||||
skipNegotiation: true,
|
||||
transport: HttpTransportType.WebSockets,
|
||||
})
|
||||
|
||||
@@ -91,6 +91,10 @@ export class CollectionService implements CollectionServiceAbstraction {
|
||||
return decryptedCollections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated August 30 2022: Moved to new Vault Filter Service
|
||||
* Remove when Desktop and Browser are updated
|
||||
*/
|
||||
async getAllNested(collections: CollectionView[] = null): Promise<TreeNode<CollectionView>[]> {
|
||||
if (collections == null) {
|
||||
collections = await this.getAllDecrypted();
|
||||
@@ -106,9 +110,13 @@ export class CollectionService implements CollectionServiceAbstraction {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated August 30 2022: Moved to new Vault Filter Service
|
||||
* Remove when Desktop and Browser are updated
|
||||
*/
|
||||
async getNested(id: string): Promise<TreeNode<CollectionView>> {
|
||||
const collections = await this.getAllNested();
|
||||
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionView>;
|
||||
return ServiceUtils.getTreeNodeObjectFromList(collections, id) as TreeNode<CollectionView>;
|
||||
}
|
||||
|
||||
async upsert(collection: CollectionData | CollectionData[]): Promise<any> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ConfigApiServiceAbstraction as ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/config/config-api.service.abstraction";
|
||||
import { ServerConfigResponse } from "@bitwarden/common/models/response/server-config-response";
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { ConfigApiServiceAbstraction as ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
|
||||
import { ServerConfigResponse } from "../../models/response/server-config-response";
|
||||
|
||||
export class ConfigApiService implements ConfigApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { BehaviorSubject, concatMap, map, switchMap, timer, EMPTY } from "rxjs";
|
||||
|
||||
import { ServerConfigData } from "@bitwarden/common/models/data/server-config.data";
|
||||
|
||||
import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
|
||||
import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction";
|
||||
import { ServerConfig } from "../../abstractions/config/server-config";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { ServerConfigData } from "../../models/data/server-config.data";
|
||||
|
||||
export class ConfigService implements ConfigServiceAbstraction {
|
||||
private _serverConfig = new BehaviorSubject<ServerConfig | null>(null);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { EncryptedObject } from "@bitwarden/common/models/domain/encryptedObject";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { EncryptionType } from "../enums/encryptionType";
|
||||
import { IEncrypted } from "../interfaces/IEncrypted";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { EncryptedObject } from "../models/domain/encryptedObject";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
export class EncryptService implements AbstractEncryptService {
|
||||
constructor(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction";
|
||||
import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { FolderData } from "@bitwarden/common/models/data/folderData";
|
||||
import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
import { FolderRequest } from "@bitwarden/common/models/request/folderRequest";
|
||||
import { FolderResponse } from "@bitwarden/common/models/response/folderResponse";
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { FolderApiServiceAbstraction } from "../../abstractions/folder/folder-api.service.abstraction";
|
||||
import { InternalFolderService } from "../../abstractions/folder/folder.service.abstraction";
|
||||
import { FolderData } from "../../models/data/folderData";
|
||||
import { Folder } from "../../models/domain/folder";
|
||||
import { FolderRequest } from "../../models/request/folderRequest";
|
||||
import { FolderResponse } from "../../models/response/folderResponse";
|
||||
|
||||
export class FolderApiService implements FolderApiServiceAbstraction {
|
||||
constructor(private folderService: InternalFolderService, private apiService: ApiService) {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AbstractStorageService,
|
||||
MemoryStorageServiceInterface,
|
||||
} from "@bitwarden/common/abstractions/storage.service";
|
||||
} from "../abstractions/storage.service";
|
||||
|
||||
export class MemoryStorageService
|
||||
extends AbstractStorageService
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||
|
||||
/**
|
||||
* If you want to use this, don't.
|
||||
* If you think you should use that after the warning, don't.
|
||||
*/
|
||||
export class NoopEventService implements EventService {
|
||||
constructor() {
|
||||
if (chrome.runtime.getManifest().manifest_version !== 3) {
|
||||
throw new Error("You are not allowed to use this when not in manifest_version 3");
|
||||
}
|
||||
}
|
||||
|
||||
collect(eventType: EventType, cipherId?: string, uploadImmediately?: boolean) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
uploadEvents(userId?: string) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
clearEvents(userId?: string) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
import * as zxcvbn from "zxcvbn";
|
||||
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
@@ -258,7 +259,11 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
const policies: Policy[] =
|
||||
this.policyService == null
|
||||
? null
|
||||
: await this.policyService.getAll(PolicyType.PasswordGenerator);
|
||||
: await firstValueFrom(
|
||||
this.policyService.policies$.pipe(
|
||||
map((p) => p.filter((policy) => policy.type === PolicyType.PasswordGenerator))
|
||||
)
|
||||
);
|
||||
let enforcedOptions: PasswordGeneratorPolicyOptions = null;
|
||||
|
||||
if (policies == null || policies.length === 0) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PolicyData } from "@bitwarden/common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { PolicyRequest } from "@bitwarden/common/models/request/policyRequest";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { PolicyRequest } from "../../models/request/policyRequest";
|
||||
import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
constructor(
|
||||
@@ -79,30 +80,13 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
return new ListResponse(r, PolicyResponse);
|
||||
}
|
||||
|
||||
async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> {
|
||||
const org = await this.organizationService.get(organizationId);
|
||||
if (org?.isProviderUser) {
|
||||
const orgPolicies = await this.getPolicies(organizationId);
|
||||
const policy = orgPolicies.data.find((p) => p.organizationId === organizationId);
|
||||
|
||||
if (policy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Policy(new PolicyData(policy));
|
||||
}
|
||||
|
||||
const policies = await this.policyService.getAll(policyType);
|
||||
return policies.find((p) => p.organizationId === organizationId);
|
||||
}
|
||||
|
||||
async getMasterPasswordPoliciesForInvitedUsers(
|
||||
orgId: string
|
||||
): Promise<MasterPasswordPolicyOptions> {
|
||||
const userId = await this.stateService.getUserId();
|
||||
const response = await this.getPoliciesByInvitedUser(orgId, userId);
|
||||
const policies = await this.policyService.mapPoliciesFromToken(response);
|
||||
return this.policyService.getMasterPasswordPolicyOptions(policies);
|
||||
return await firstValueFrom(this.policyService.masterPasswordPolicyOptions$(policies));
|
||||
}
|
||||
|
||||
async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { of, concatMap, BehaviorSubject, Observable, map } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
@@ -13,13 +16,37 @@ import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
policyCache: Policy[];
|
||||
private _policies: BehaviorSubject<Policy[]> = new BehaviorSubject([]);
|
||||
|
||||
policies$ = this._policies.asObservable();
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
.pipe(
|
||||
concatMap(async (unlocked) => {
|
||||
if (Utils.global.bitwardenContainerService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unlocked) {
|
||||
this._policies.next([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.stateService.getEncryptedPolicies();
|
||||
|
||||
await this.updateObservables(data);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
|
||||
let response: Policy[] = [];
|
||||
const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
|
||||
@@ -28,8 +55,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
} else {
|
||||
const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
|
||||
for (const id in diskPolicies) {
|
||||
// eslint-disable-next-line
|
||||
if (diskPolicies.hasOwnProperty(id)) {
|
||||
if (Object.prototype.hasOwnProperty.call(diskPolicies, id)) {
|
||||
response.push(new Policy(diskPolicies[id]));
|
||||
}
|
||||
}
|
||||
@@ -42,60 +68,72 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions = null;
|
||||
masterPasswordPolicyOptions$(policies?: Policy[]): Observable<MasterPasswordPolicyOptions> {
|
||||
const observable = policies ? of(policies) : this.policies$;
|
||||
return observable.pipe(
|
||||
map((obsPolicies) => {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions = null;
|
||||
const filteredPolicies = obsPolicies.filter((p) => p.type === PolicyType.MasterPassword);
|
||||
|
||||
if (policies == null) {
|
||||
policies = await this.getAll(PolicyType.MasterPassword);
|
||||
} else {
|
||||
policies = policies.filter((p) => p.type === PolicyType.MasterPassword);
|
||||
}
|
||||
if (filteredPolicies == null || filteredPolicies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
if (policies == null || policies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
filteredPolicies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || currentPolicy.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
policies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || currentPolicy.data == null) {
|
||||
return;
|
||||
}
|
||||
if (enforcedOptions == null) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (enforcedOptions == null) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
) {
|
||||
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
) {
|
||||
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
|
||||
}
|
||||
if (
|
||||
currentPolicy.data.minLength != null &&
|
||||
currentPolicy.data.minLength > enforcedOptions.minLength
|
||||
) {
|
||||
enforcedOptions.minLength = currentPolicy.data.minLength;
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minLength != null &&
|
||||
currentPolicy.data.minLength > enforcedOptions.minLength
|
||||
) {
|
||||
enforcedOptions.minLength = currentPolicy.data.minLength;
|
||||
}
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
});
|
||||
return enforcedOptions;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return enforcedOptions;
|
||||
policyAppliesToActiveUser$(
|
||||
policyType: PolicyType,
|
||||
policyFilter: (policy: Policy) => boolean = (p) => true
|
||||
) {
|
||||
return this.policies$.pipe(
|
||||
concatMap(async (policies) => {
|
||||
const userId = await this.stateService.getUserId();
|
||||
return await this.checkPoliciesThatApplyToUser(policies, policyType, policyFilter, userId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
evaluateMasterPassword(
|
||||
@@ -174,25 +212,8 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
userId?: string
|
||||
) {
|
||||
const policies = await this.getAll(policyType, userId);
|
||||
const organizations = await this.organizationService.getAll(userId);
|
||||
let filteredPolicies;
|
||||
|
||||
if (policyFilter != null) {
|
||||
filteredPolicies = policies.filter((p) => p.enabled && policyFilter(p));
|
||||
} else {
|
||||
filteredPolicies = policies.filter((p) => p.enabled);
|
||||
}
|
||||
|
||||
const policySet = new Set(filteredPolicies.map((p) => p.organizationId));
|
||||
|
||||
return organizations.some(
|
||||
(o) =>
|
||||
o.enabled &&
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
!this.isExcemptFromPolicies(o, policyType) &&
|
||||
policySet.has(o.id)
|
||||
);
|
||||
return this.checkPoliciesThatApplyToUser(policies, policyType, policyFilter, userId);
|
||||
}
|
||||
|
||||
async upsert(policy: PolicyData): Promise<any> {
|
||||
@@ -203,17 +224,19 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
|
||||
policies[policy.id] = policy;
|
||||
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
await this.updateObservables(policies);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async replace(policies: { [id: string]: PolicyData }): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
async replace(policies: { [id: string]: PolicyData }): Promise<void> {
|
||||
await this.updateObservables(policies);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null, { userId: userId });
|
||||
async clear(userId?: string): Promise<void> {
|
||||
if (userId == null || userId == (await this.stateService.getUserId())) {
|
||||
this._policies.next([]);
|
||||
}
|
||||
await this.stateService.setEncryptedPolicies(null, { userId: userId });
|
||||
}
|
||||
|
||||
@@ -224,4 +247,32 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
|
||||
return organization.isExemptFromPolicies;
|
||||
}
|
||||
|
||||
private async updateObservables(policiesMap: { [id: string]: PolicyData }) {
|
||||
const policies = Object.values(policiesMap || {}).map((f) => new Policy(f));
|
||||
|
||||
this._policies.next(policies);
|
||||
}
|
||||
|
||||
private async checkPoliciesThatApplyToUser(
|
||||
policies: Policy[],
|
||||
policyType: PolicyType,
|
||||
policyFilter: (policy: Policy) => boolean = (p) => true,
|
||||
userId?: string
|
||||
) {
|
||||
const organizations = await this.organizationService.getAll(userId);
|
||||
const filteredPolicies = policies.filter(
|
||||
(p) => p.type === policyType && p.enabled && policyFilter(p)
|
||||
);
|
||||
const policySet = new Set(filteredPolicies.map((p) => p.organizationId));
|
||||
|
||||
return organizations.some(
|
||||
(o) =>
|
||||
o.enabled &&
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
policySet.has(o.id) &&
|
||||
!this.isExcemptFromPolicies(o, policyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||
import { ValidationService as ValidationServiceAbstraction } from "../abstractions/validation.service";
|
||||
import { ErrorResponse } from "../models/response/errorResponse";
|
||||
|
||||
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 {
|
||||
export class ValidationService implements ValidationServiceAbstraction {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
14
libs/components/src/async-actions/async-actions.module.ts
Normal file
14
libs/components/src/async-actions/async-actions.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared";
|
||||
|
||||
import { BitActionDirective } from "./bit-action.directive";
|
||||
import { BitSubmitDirective } from "./bit-submit.directive";
|
||||
import { BitFormButtonDirective } from "./form-button.directive";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective],
|
||||
exports: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective],
|
||||
})
|
||||
export class AsyncActionsModule {}
|
||||
58
libs/components/src/async-actions/bit-action.directive.ts
Normal file
58
libs/components/src/async-actions/bit-action.directive.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Directive, HostListener, Input, OnDestroy, Optional } from "@angular/core";
|
||||
import { BehaviorSubject, finalize, Subject, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
||||
|
||||
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
||||
import { FunctionReturningAwaitable, functionToObservable } from "../utils/function-to-observable";
|
||||
|
||||
/**
|
||||
* Allow a single button to perform async actions on click and reflect the progress in the UI by automatically
|
||||
* activating the loading effect while the action is processed.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[bitAction]",
|
||||
})
|
||||
export class BitActionDirective implements OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
@Input("bitAction") protected handler: FunctionReturningAwaitable;
|
||||
|
||||
readonly loading$ = this._loading$.asObservable();
|
||||
|
||||
constructor(
|
||||
private buttonComponent: ButtonLikeAbstraction,
|
||||
@Optional() private validationService?: ValidationService
|
||||
) {}
|
||||
|
||||
get loading() {
|
||||
return this._loading$.value;
|
||||
}
|
||||
|
||||
set loading(value: boolean) {
|
||||
this._loading$.next(value);
|
||||
this.buttonComponent.loading = value;
|
||||
}
|
||||
|
||||
@HostListener("click")
|
||||
protected async onClick() {
|
||||
if (!this.handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
functionToObservable(this.handler)
|
||||
.pipe(
|
||||
tap({ error: (err: unknown) => this.validationService?.showError(err) }),
|
||||
finalize(() => (this.loading = false)),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
82
libs/components/src/async-actions/bit-submit.directive.ts
Normal file
82
libs/components/src/async-actions/bit-submit.directive.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Directive, Input, OnDestroy, OnInit, Optional } from "@angular/core";
|
||||
import { FormGroupDirective } from "@angular/forms";
|
||||
import { BehaviorSubject, catchError, filter, of, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
||||
|
||||
import { FunctionReturningAwaitable, functionToObservable } from "../utils/function-to-observable";
|
||||
|
||||
/**
|
||||
* Allow a form to perform async actions on submit, disabling the form while the action is processing.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[formGroup][bitSubmit]",
|
||||
})
|
||||
export class BitSubmitDirective implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
private _disabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
@Input("bitSubmit") protected handler: FunctionReturningAwaitable;
|
||||
|
||||
readonly loading$ = this._loading$.asObservable();
|
||||
readonly disabled$ = this._disabled$.asObservable();
|
||||
|
||||
constructor(
|
||||
private formGroupDirective: FormGroupDirective,
|
||||
@Optional() validationService?: ValidationService
|
||||
) {
|
||||
formGroupDirective.ngSubmit
|
||||
.pipe(
|
||||
filter(() => !this.disabled),
|
||||
switchMap(() => {
|
||||
// Calling functionToObservable exectues the sync part of the handler
|
||||
// allowing the function to check form validity before it gets disabled.
|
||||
const awaitable = functionToObservable(this.handler);
|
||||
|
||||
// Disable form
|
||||
this.loading = true;
|
||||
|
||||
return awaitable.pipe(
|
||||
catchError((err: unknown) => {
|
||||
validationService?.showError(err);
|
||||
return of(undefined);
|
||||
})
|
||||
);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe({
|
||||
next: () => (this.loading = false),
|
||||
complete: () => (this.loading = false),
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formGroupDirective.statusChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((c) => this._disabled$.next(c === "DISABLED"));
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this._disabled$.value;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled$.next(value);
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this._loading$.value;
|
||||
}
|
||||
|
||||
set loading(value: boolean) {
|
||||
this.disabled = value;
|
||||
this._loading$.next(value);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
58
libs/components/src/async-actions/form-button.directive.ts
Normal file
58
libs/components/src/async-actions/form-button.directive.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Directive, Input, OnDestroy, Optional } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
||||
|
||||
import { BitSubmitDirective } from "./bit-submit.directive";
|
||||
|
||||
import { BitActionDirective } from ".";
|
||||
|
||||
/**
|
||||
* This directive has two purposes:
|
||||
*
|
||||
* When attached to a submit button:
|
||||
* - Activates the button loading effect while the form is processing an async submit action.
|
||||
* - Disables the button while a `bitAction` directive on another button is being processed.
|
||||
*
|
||||
* When attached to a standalone button with `bitAction` directive:
|
||||
* - Disables the form while the `bitAction` directive is processing an async submit action.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "button[bitFormButton]",
|
||||
})
|
||||
export class BitFormButtonDirective implements OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@Input() type: string;
|
||||
|
||||
constructor(
|
||||
buttonComponent: ButtonLikeAbstraction,
|
||||
@Optional() submitDirective?: BitSubmitDirective,
|
||||
@Optional() actionDirective?: BitActionDirective
|
||||
) {
|
||||
if (submitDirective && buttonComponent) {
|
||||
submitDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => {
|
||||
if (this.type === "submit") {
|
||||
buttonComponent.loading = loading;
|
||||
} else {
|
||||
buttonComponent.disabled = loading;
|
||||
}
|
||||
});
|
||||
|
||||
submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => {
|
||||
buttonComponent.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
if (submitDirective && actionDirective) {
|
||||
actionDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => {
|
||||
submitDirective.disabled = disabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
114
libs/components/src/async-actions/in-forms.stories.mdx
Normal file
114
libs/components/src/async-actions/in-forms.stories.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Component Library/Async Actions/In Forms/Documentation" />
|
||||
|
||||
# Async Actions In Forms
|
||||
|
||||
These directives should be used when building forms with buttons that trigger long running tasks in the background,
|
||||
eg. Submit or Delete buttons. For buttons that are not associated with a form see [Standalone Async Actions](?path=/story/component-library-async-actions-standalone-documentation--page).
|
||||
|
||||
There are two separately supported use-cases: Submit buttons and standalone form buttons (eg. Delete buttons).
|
||||
|
||||
## Usage: Submit buttons
|
||||
|
||||
Adding async actions to submit buttons requires the following 3 steps
|
||||
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful for aborting an action.
|
||||
|
||||
**NOTE:**
|
||||
|
||||
- Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
- `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually
|
||||
invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
class Component {
|
||||
formGroup = this.formBuilder.group({...});
|
||||
|
||||
// submit can also return Observable instead of Promise
|
||||
submit = async () => {
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.cryptoService.encrypt(/* ... */);
|
||||
|
||||
// `formGroup.invalid` will always return `true` here
|
||||
|
||||
await this.apiService.post(/* ... */);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add directive to the `form` element
|
||||
|
||||
Add the `bitSubmit` directive and supply the handler defined in step 1.
|
||||
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`.
|
||||
This is different from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`.
|
||||
|
||||
```html
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
|
||||
```
|
||||
|
||||
### 3. Add directive to the `type="submit"` button
|
||||
|
||||
Add both `bitButton` and `bitFormButton` directives to the button.
|
||||
|
||||
```html
|
||||
<button type="submit" bitButton bitFormButton>{{ "submit" | i18n }}</button>
|
||||
```
|
||||
|
||||
## Usage: Standalone form buttons
|
||||
|
||||
Adding async actions to standalone form buttons requires the following 3 steps.
|
||||
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful for aborting an action.
|
||||
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
class Component {
|
||||
formGroup = this.formBuilder.group({...});
|
||||
|
||||
submit = async () => {
|
||||
// not relevant for this example
|
||||
}
|
||||
|
||||
// action can also return Observable instead of Promise
|
||||
handler = async () => {
|
||||
if (/* perform guard check */) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.post(/* ... */);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add directive to the `form` element
|
||||
|
||||
The `bitSubmit` directive is required beacuse of its coordinating role.
|
||||
|
||||
```html
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
|
||||
```
|
||||
|
||||
### 3. Add directives to the `button` element
|
||||
|
||||
Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sure to supply a handler.
|
||||
|
||||
```html
|
||||
<button type="button" bitFormButton bitButton [bitAction]="handler">Do action</button>
|
||||
<button type="button" bitFormButton bitIconButton="bwi-star" [bitAction]="handler"></button>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user