1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-07 19:07:45 +01:00

Merge branch 'main' into autofill/pm-5189-fix-issues-present-with-inline-menu-rendering-in-iframes

This commit is contained in:
Cesar Gonzalez 2024-06-20 08:23:36 -05:00 committed by GitHub
commit 6e56f23e44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1006 additions and 841 deletions

View File

@ -160,7 +160,15 @@
"configDir": ".storybook",
"browserTarget": "components:build",
"compodoc": true,
"compodocArgs": ["-e", "json", "-d", "."],
"compodocArgs": [
"-p",
"./tsconfig.json",
"-e",
"json",
"-d",
".",
"--disableRoutesGraph"
],
"outputDir": "storybook-static"
}
}

View File

@ -13,6 +13,7 @@ import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/ge
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-generator",
@ -34,6 +35,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
logService: LogService,
ngZone: NgZone,
private location: Location,
toastService: ToastService,
) {
super(
passwordGenerationService,
@ -45,6 +47,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
route,
ngZone,
window,
toastService,
);
this.cipherService = cipherService;
}

View File

@ -5,6 +5,7 @@ import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryCompon
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-password-generator-history",
@ -16,8 +17,9 @@ export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHist
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
private location: Location,
toastService: ToastService,
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
super(passwordGenerationService, platformUtilsService, i18nService, window, toastService);
}
close() {

View File

@ -15,7 +15,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
import { BrowserStateService } from "../../../platform/services/abstractions/browser-state.service";
@ -53,6 +53,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
private filePopoutUtilsService: FilePopoutUtilsService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
toastService: ToastService,
) {
super(
i18nService,
@ -69,6 +70,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
formBuilder,
billingAccountProfileStateService,
accountService,
toastService,
);
}

View File

@ -14,7 +14,7 @@ import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
@ -49,6 +49,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
@ -61,6 +62,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
logService,
sendApiService,
dialogService,
toastService,
);
super.onSuccessfulLoad = async () => {
this.selectAll();

View File

@ -15,7 +15,7 @@ import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { BrowserComponentState } from "../../../models/browserComponentState";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
@ -51,6 +51,7 @@ export class SendTypeComponent extends BaseSendComponent {
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
@ -63,6 +64,7 @@ export class SendTypeComponent extends BaseSendComponent {
logService,
sendApiService,
dialogService,
toastService,
);
super.onSuccessfulLoad = async () => {
this.selectType(this.type);

View File

@ -11,6 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { ToastService } from "@bitwarden/components";
import { GeneratorComponent } from "./generator.component";
@ -59,6 +60,10 @@ describe("GeneratorComponent", () => {
provide: AccountService,
useValue: mock<AccountService>(),
},
{
provide: ToastService,
useValue: mock<ToastService>(),
},
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();

View File

@ -8,6 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-generator",
@ -23,6 +24,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
route: ActivatedRoute,
ngZone: NgZone,
logService: LogService,
toastService: ToastService,
) {
super(
passwordGenerationService,
@ -34,6 +36,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
route,
ngZone,
window,
toastService,
);
}

View File

@ -4,6 +4,7 @@ import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryCompon
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-password-generator-history",
@ -14,7 +15,8 @@ export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHist
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
toastService: ToastService,
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
super(passwordGenerationService, platformUtilsService, i18nService, window, toastService);
}
}

View File

@ -14,7 +14,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "app-send-add-edit",
@ -36,6 +36,7 @@ export class AddEditComponent extends BaseAddEditComponent {
formBuilder: FormBuilder,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
toastService: ToastService,
) {
super(
i18nService,
@ -52,6 +53,7 @@ export class AddEditComponent extends BaseAddEditComponent {
formBuilder,
billingAccountProfileStateService,
accountService,
toastService,
);
}
@ -70,11 +72,11 @@ export class AddEditComponent extends BaseAddEditComponent {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink")),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("sendLink")),
});
}
async resetAndLoad() {

View File

@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { invokeMenu, RendererMenuItem } from "../../../utils";
import { SearchBarService } from "../../layout/search/search-bar.service";
@ -49,6 +49,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
@ -61,6 +62,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
logService,
sendApiService,
dialogService,
toastService,
);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.searchBarService.searchText$.subscribe((searchText) => {

View File

@ -1,32 +0,0 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { map, Observable, shareReplay, startWith, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@Component({
selector: "app-org-reporting",
templateUrl: "reporting.component.html",
})
export class ReportingComponent implements OnInit {
organization$: Observable<Organization>;
showLeftNav$: Observable<boolean>;
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
) {}
ngOnInit() {
this.organization$ = this.route.params.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
this.showLeftNav$ = this.organization$.pipe(
map((o) => o.canAccessEventLogs && o.canAccessReports),
startWith(true),
);
}
}

View File

@ -1,38 +0,0 @@
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@Component({
selector: "app-org-tools",
templateUrl: "tools.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ToolsComponent {
organization: Organization;
accessReports = false;
loading = true;
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private messagingService: MessagingService,
) {}
ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
// since all paid plans include useTotp
this.accessReports = this.organization.useTotp;
this.loading = false;
});
}
upgradeOrganization() {
this.messagingService.send("upgradeOrganization", { organizationId: this.organization.id });
}
}

View File

@ -9,8 +9,7 @@ import { EventType } from "@bitwarden/common/enums";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
import { ExportComponent } from "../../../../tools/vault-export/export.component";
@ -23,7 +22,7 @@ import { ExportComponent } from "../../../../tools/vault-export/export.component
export class OrganizationVaultExportComponent extends ExportComponent {
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
toastService: ToastService,
exportService: VaultExportServiceAbstraction,
eventCollectionService: EventCollectionService,
private route: ActivatedRoute,
@ -36,7 +35,7 @@ export class OrganizationVaultExportComponent extends ExportComponent {
) {
super(
i18nService,
platformUtilsService,
toastService,
exportService,
eventCollectionService,
policyService,

View File

@ -1,44 +1,23 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "passwordHint" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
<small class="form-text text-muted">{{ "enterEmailToGetHint" | i18n }}</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
<form [bitSubmit]="submit" [formGroup]="formGroup">
<bit-form-field>
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
<input
bitInput
appAutofocus
inputmode="email"
appInputVerbatim="false"
type="email"
formControlName="email"
/>
<bit-hint>{{ "enterEmailToGetHint" | i18n }}</bit-hint>
</bit-form-field>
<hr />
<div class="tw-flex tw-gap-2">
<button type="submit" bitButton bitFormButton buttonType="primary" [block]="true">
{{ "submit" | i18n }}
</button>
<a bitButton buttonType="secondary" routerLink="/login" [block]="true">
{{ "cancel" | i18n }}
</a>
</div>
</form>

View File

@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component";
@ -13,6 +14,14 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
formGroup = this.formBuilder.group({
email: ["", [Validators.email, Validators.required]],
});
get emailFormControl() {
return this.formGroup.controls.email;
}
constructor(
router: Router,
i18nService: I18nService,
@ -20,7 +29,24 @@ export class HintComponent extends BaseHintComponent {
platformUtilsService: PlatformUtilsService,
logService: LogService,
loginEmailService: LoginEmailServiceAbstraction,
private formBuilder: FormBuilder,
) {
super(router, i18nService, apiService, platformUtilsService, logService, loginEmailService);
}
ngOnInit(): void {
super.ngOnInit();
this.emailFormControl.setValue(this.email);
}
// Wrapper method to call super.submit() since properties (e.g., submit) cannot use super directly
// This is because properties are assigned per type and generally don't have access to the prototype
async superSubmit() {
await super.submit();
}
submit = async () => {
this.email = this.emailFormControl.value;
await this.superSubmit();
};
}

View File

@ -1,117 +1,110 @@
<div class="tabbed-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<h2 bitTypography="h2">{{ "encKeySettings" | i18n }}</h2>
<bit-callout type="warning">{{ "kdfSettingsChangeLogoutWarning" | i18n }}</bit-callout>
<form #form ngNativeValidate autocomplete="off">
<div class="row">
<div class="col-6">
<div class="form-group mb-0">
<label for="kdf">{{ "kdfAlgorithm" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/kdf-algorithms"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<select
id="kdf"
name="Kdf"
[(ngModel)]="kdfConfig.kdfType"
(ngModelChange)="onChangeKdf($event)"
class="form-control mb-3"
required
>
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfMemory">{{ "kdfMemory" | i18n }}</label>
<input
id="kdfMemory"
type="number"
[min]="ARGON2_MEMORY.min"
[max]="ARGON2_MEMORY.max"
name="Memory"
class="form-control mb-3"
[(ngModel)]="kdfConfig.memory"
required
/>
</ng-container>
</div>
</div>
<div class="col-6">
<div class="form-group mb-0">
<ng-container *ngIf="isPBKDF2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<p bitTypography="body1">
{{ "higherKDFIterations" | i18n }}
</p>
<p bitTypography="body1">
{{
"kdfToHighWarningIncreaseInIncrements"
| i18n: (isPBKDF2(kdfConfig) ? ("incrementsOf100,000" | i18n) : ("smallIncrements" | i18n))
}}
</p>
<form [formGroup]="formGroup" autocomplete="off">
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="tw-col-span-6">
<bit-form-field>
<bit-label
>{{ "kdfAlgorithm" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/what-encryption-is-used/#changing-kdf-iterations"
class="tw-ml-auto"
bitLink
href="https://bitwarden.com/help/kdf-algorithms"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
<bit-select formControlName="kdf">
<bit-option
*ngFor="let option of kdfOptions"
[value]="option.value"
[label]="option.name"
></bit-option>
</bit-select>
</bit-form-field>
<bit-form-field formGroupName="kdfConfig" *ngIf="isArgon2(kdfConfig)">
<bit-label>{{ "kdfMemory" | i18n }}</bit-label>
<input
bitInput
formControlName="memory"
type="number"
[min]="ARGON2_MEMORY.min"
[max]="ARGON2_MEMORY.max"
/>
</bit-form-field>
</div>
<div class="tw-col-span-6">
<div class="tw-mb-0">
<bit-form-field formGroupName="kdfConfig" *ngIf="isPBKDF2(kdfConfig)">
<bit-label>
{{ "kdfIterations" | i18n }}
<a
bitLink
class="tw-ml-auto"
href="https://bitwarden.com/help/what-encryption-is-used/#changing-kdf-iterations"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
<input
id="kdfIterations"
bitInput
type="number"
formControlName="iterations"
[min]="PBKDF2_ITERATIONS.min"
[max]="PBKDF2_ITERATIONS.max"
name="KdfIterations"
class="form-control"
[(ngModel)]="kdfConfig.iterations"
required
/>
</ng-container>
<bit-hint>{{ "kdfIterationRecommends" | i18n }}</bit-hint>
</bit-form-field>
<ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<input
id="iterations"
type="number"
[min]="ARGON2_ITERATIONS.min"
[max]="ARGON2_ITERATIONS.max"
name="Iterations"
class="form-control mb-3"
[(ngModel)]="kdfConfig.iterations"
required
/>
<label for="kdfParallelism">{{ "kdfParallelism" | i18n }}</label>
<input
id="kdfParallelism"
type="number"
[min]="ARGON2_PARALLELISM.min"
[max]="ARGON2_PARALLELISM.max"
name="Parallelism"
class="form-control"
[(ngModel)]="kdfConfig.parallelism"
required
/>
<bit-form-field formGroupName="kdfConfig">
<bit-label>
{{ "kdfIterations" | i18n }}
</bit-label>
<input
bitInput
type="number"
formControlName="iterations"
[min]="ARGON2_ITERATIONS.min"
[max]="ARGON2_ITERATIONS.max"
/>
</bit-form-field>
<bit-form-field formGroupName="kdfConfig">
<bit-label>
{{ "kdfParallelism" | i18n }}
</bit-label>
<input
bitInput
type="number"
formControlName="parallelism"
[min]="ARGON2_PARALLELISM.min"
[max]="ARGON2_PARALLELISM.max"
/>
</bit-form-field>
</ng-container>
</div>
</div>
<div class="col-12">
<ng-container *ngIf="isPBKDF2(kdfConfig)">
<p class="small form-text text-muted">
{{ "kdfIterationsDesc" | i18n: (PBKDF2_ITERATIONS.defaultValue | number) }}
</p>
<bit-callout type="warning">
{{ "kdfIterationsWarning" | i18n: (100000 | number) }}
</bit-callout>
</ng-container>
<ng-container *ngIf="isArgon2(kdfConfig)">
<p class="small form-text text-muted">{{ "argon2Desc" | i18n }}</p>
<bit-callout type="warning"> {{ "argon2Warning" | i18n }}</bit-callout>
</ng-container>
</div>
</div>
<button
(click)="openConfirmationModal()"
type="button"
buttonType="primary"
bitButton
[loading]="form.loading"
bitFormButton
>
{{ "changeKdf" | i18n }}
</button>

View File

@ -1,4 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormControl, ValidatorFn, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import {
@ -24,8 +26,34 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon
})
export class ChangeKdfComponent implements OnInit {
kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG;
kdfType = KdfType;
kdfOptions: any[] = [];
private destroy$ = new Subject<void>();
protected formGroup = this.formBuilder.group({
kdf: new FormControl(KdfType.PBKDF2_SHA256, [Validators.required]),
kdfConfig: this.formBuilder.group({
iterations: [
this.kdfConfig.iterations,
[
Validators.required,
Validators.min(PBKDF2_ITERATIONS.min),
Validators.max(PBKDF2_ITERATIONS.max),
],
],
memory: [
null as number,
[Validators.required, Validators.min(ARGON2_MEMORY.min), Validators.max(ARGON2_MEMORY.max)],
],
parallelism: [
null as number,
[
Validators.required,
Validators.min(ARGON2_PARALLELISM.min),
Validators.max(ARGON2_PARALLELISM.max),
],
],
}),
});
// Default values for template
protected PBKDF2_ITERATIONS = PBKDF2_ITERATIONS;
@ -36,6 +64,7 @@ export class ChangeKdfComponent implements OnInit {
constructor(
private dialogService: DialogService,
private kdfConfigService: KdfConfigService,
private formBuilder: FormBuilder,
) {
this.kdfOptions = [
{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 },
@ -45,6 +74,86 @@ export class ChangeKdfComponent implements OnInit {
async ngOnInit() {
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.formGroup.get("kdf").setValue(this.kdfConfig.kdfType, { emitEvent: false });
this.setFormControlValues(this.kdfConfig);
this.formGroup
.get("kdf")
.valueChanges.pipe(takeUntil(this.destroy$))
.subscribe((newValue) => {
this.updateKdfConfig(newValue);
});
}
private updateKdfConfig(newValue: KdfType) {
let config: KdfConfig;
const validators: { [key: string]: ValidatorFn[] } = {
iterations: [],
memory: [],
parallelism: [],
};
switch (newValue) {
case KdfType.PBKDF2_SHA256:
config = new PBKDF2KdfConfig();
validators.iterations = [
Validators.required,
Validators.min(PBKDF2_ITERATIONS.min),
Validators.max(PBKDF2_ITERATIONS.max),
];
break;
case KdfType.Argon2id:
config = new Argon2KdfConfig();
validators.iterations = [
Validators.required,
Validators.min(ARGON2_ITERATIONS.min),
Validators.max(ARGON2_ITERATIONS.max),
];
validators.memory = [
Validators.required,
Validators.min(ARGON2_MEMORY.min),
Validators.max(ARGON2_MEMORY.max),
];
validators.parallelism = [
Validators.required,
Validators.min(ARGON2_PARALLELISM.min),
Validators.max(ARGON2_PARALLELISM.max),
];
break;
default:
throw new Error("Unknown KDF type.");
}
this.kdfConfig = config;
this.setFormValidators(validators);
this.setFormControlValues(this.kdfConfig);
}
private setFormValidators(validators: { [key: string]: ValidatorFn[] }) {
this.setValidators("kdfConfig.iterations", validators.iterations);
this.setValidators("kdfConfig.memory", validators.memory);
this.setValidators("kdfConfig.parallelism", validators.parallelism);
}
private setValidators(controlName: string, validators: ValidatorFn[]) {
const control = this.formGroup.get(controlName);
if (control) {
control.setValidators(validators);
control.updateValueAndValidity();
}
}
private setFormControlValues(kdfConfig: KdfConfig) {
this.formGroup.get("kdfConfig").reset();
if (kdfConfig.kdfType === KdfType.PBKDF2_SHA256) {
this.formGroup.get("kdfConfig.iterations").setValue(kdfConfig.iterations);
} else if (kdfConfig.kdfType === KdfType.Argon2id) {
this.formGroup.get("kdfConfig.iterations").setValue(kdfConfig.iterations);
this.formGroup.get("kdfConfig.memory").setValue(kdfConfig.memory);
this.formGroup.get("kdfConfig.parallelism").setValue(kdfConfig.parallelism);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
isPBKDF2(t: KdfConfig): t is PBKDF2KdfConfig {
@ -55,17 +164,18 @@ export class ChangeKdfComponent implements OnInit {
return t instanceof Argon2KdfConfig;
}
async onChangeKdf(newValue: KdfType) {
if (newValue === KdfType.PBKDF2_SHA256) {
this.kdfConfig = new PBKDF2KdfConfig();
} else if (newValue === KdfType.Argon2id) {
this.kdfConfig = new Argon2KdfConfig();
} else {
throw new Error("Unknown KDF type.");
}
}
async openConfirmationModal() {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
return;
}
if (this.kdfConfig.kdfType === KdfType.PBKDF2_SHA256) {
this.kdfConfig.iterations = this.formGroup.get("kdfConfig.iterations").value;
} else if (this.kdfConfig.kdfType === KdfType.Argon2id) {
this.kdfConfig.iterations = this.formGroup.get("kdfConfig.iterations").value;
this.kdfConfig.memory = this.formGroup.get("kdfConfig.memory").value;
this.kdfConfig.parallelism = this.formGroup.get("kdfConfig.parallelism").value;
}
this.dialogService.open(ChangeKdfConfirmationComponent, {
data: {
kdfConfig: this.kdfConfig,

View File

@ -1,34 +1,16 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
<p class="text-center">
<strong>{{ email }}</strong>
</p>
<p>{{ "deleteRecoverConfirmDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-danger btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "deleteAccount" | i18n }}</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
<form [bitSubmit]="submit" [formGroup]="formGroup">
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
<p bitTypography="body1" class="tw-text-center">
<strong>{{ email }}</strong>
</p>
<p bitTypography="body1">{{ "deleteRecoverConfirmDesc" | i18n }}</p>
<hr />
<div class="tw-flex tw-gap-2">
<button type="submit" bitButton bitFormButton buttonType="danger" [block]="true">
{{ "deleteAccount" | i18n }}
</button>
<a bitButton buttonType="secondary" routerLink="/login" [block]="true">
{{ "cancel" | i18n }}
</a>
</div>
</form>

View File

@ -1,11 +1,11 @@
import { Component, OnInit } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VerifyDeleteRecoverRequest } from "@bitwarden/common/models/request/verify-delete-recover.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@Component({
@ -15,10 +15,10 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class VerifyRecoverDeleteComponent implements OnInit {
email: string;
formPromise: Promise<any>;
private userId: string;
private token: string;
protected formGroup = new FormGroup({});
constructor(
private router: Router,
@ -26,7 +26,6 @@ export class VerifyRecoverDeleteComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private logService: LogService,
) {}
ngOnInit() {
@ -37,28 +36,19 @@ export class VerifyRecoverDeleteComponent implements OnInit {
this.token = qParams.token;
this.email = qParams.email;
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
await this.router.navigate(["/"]);
}
});
}
async submit() {
try {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
this.i18nService.t("accountDeleted"),
this.i18nService.t("accountDeletedDesc"),
);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
submit = async () => {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
await this.apiService.postAccountRecoverDeleteToken(request);
this.platformUtilsService.showToast(
"success",
this.i18nService.t("accountDeleted"),
this.i18nService.t("accountDeletedDesc"),
);
await this.router.navigate(["/"]);
};
}

View File

@ -111,12 +111,6 @@ const routes: Routes = [
component: SetPasswordComponent,
data: { titleId: "setMasterPassword" } satisfies DataProperties,
},
{
path: "hint",
component: HintComponent,
canActivate: [UnauthGuard],
data: { titleId: "passwordHint" } satisfies DataProperties,
},
{
path: "lock",
component: LockComponent,
@ -136,12 +130,6 @@ const routes: Routes = [
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties,
},
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
{
path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" } satisfies DataProperties,
},
{
path: "verify-recover-delete-org",
component: VerifyRecoverDeleteOrgComponent,
@ -330,6 +318,39 @@ const routes: Routes = [
},
],
},
{
path: "verify-recover-delete",
canActivate: [unauthGuardFn()],
children: [
{
path: "",
component: VerifyRecoverDeleteComponent,
data: {
pageTitle: "deleteAccount",
titleId: "deleteAccount",
} satisfies DataProperties & AnonLayoutWrapperData,
},
],
},
{
path: "hint",
canActivate: [unauthGuardFn()],
children: [
{
path: "",
component: HintComponent,
data: {
pageTitle: "passwordHint",
titleId: "passwordHint",
} satisfies DataProperties & AnonLayoutWrapperData,
},
{
path: "",
component: EnvironmentSelectorComponent,
outlet: "environment-selector",
},
],
},
{
path: "remove-password",
component: RemovePasswordComponent,

View File

@ -8,7 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component";
@ -27,6 +27,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
route: ActivatedRoute,
ngZone: NgZone,
private dialogService: DialogService,
toastService: ToastService,
) {
super(
passwordGenerationService,
@ -38,6 +39,7 @@ export class GeneratorComponent extends BaseGeneratorComponent {
route,
ngZone,
window,
toastService,
);
if (platformUtilsService.isSelfHost()) {
// Allow only valid email forwarders for self host

View File

@ -4,6 +4,7 @@ import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryCompon
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-password-generator-history",
@ -14,7 +15,8 @@ export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHist
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
toastService: ToastService,
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
super(passwordGenerationService, platformUtilsService, i18nService, window, toastService);
}
}

View File

@ -8,7 +8,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
@ -18,7 +17,7 @@ import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SEND_KDF_ITERATIONS } from "@bitwarden/common/tools/send/send-kdf";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { NoItemsModule } from "@bitwarden/components";
import { NoItemsModule, ToastService } from "@bitwarden/components";
import { SharedModule } from "../../shared";
@ -67,7 +66,7 @@ export class AccessComponent implements OnInit {
private route: ActivatedRoute,
private cryptoService: CryptoService,
private sendApiService: SendApiService,
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
private i18nService: I18nService,
private configService: ConfigService,
protected formBuilder: FormBuilder,
@ -142,11 +141,11 @@ export class AccessComponent implements OnInit {
} else if (e.statusCode === 404) {
this.unavailable = true;
} else if (e.statusCode === 400) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
e.message,
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
} else {
this.error = true;
}

View File

@ -15,7 +15,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "app-send-add-edit",
@ -42,6 +42,7 @@ export class AddEditComponent extends BaseAddEditComponent {
protected dialogRef: DialogRef,
@Inject(DIALOG_DATA) params: { sendId: string },
accountService: AccountService,
toastService: ToastService,
) {
super(
i18nService,
@ -58,6 +59,7 @@ export class AddEditComponent extends BaseAddEditComponent {
formBuilder,
billingAccountProfileStateService,
accountService,
toastService,
);
this.sendId = params.sendId;

View File

@ -3,13 +3,13 @@ import { Component, Input } from "@angular/core";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { ToastService } from "@bitwarden/components";
import { SharedModule } from "../../shared";
@ -25,7 +25,7 @@ export class SendAccessFileComponent {
@Input() accessRequest: SendAccessRequest;
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
private cryptoService: CryptoService,
private fileDownloadService: FileDownloadService,
private sendApiService: SendApiService,
@ -42,13 +42,21 @@ export class SendAccessFileComponent {
);
if (Utils.isNullOrWhitespace(downloadData.url)) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingSendFile"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("missingSendFile"),
});
return;
}
const response = await fetch(new Request(downloadData.url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
return;
}
@ -61,7 +69,11 @@ export class SendAccessFileComponent {
downloadMethod: "save",
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
};
}

View File

@ -4,6 +4,7 @@ import { FormBuilder } from "@angular/forms";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { ToastService } from "@bitwarden/components";
import { SharedModule } from "../../shared";
@ -25,6 +26,7 @@ export class SendAccessTextComponent {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private formBuilder: FormBuilder,
private toastService: ToastService,
) {}
get send(): SendAccessView {
@ -46,11 +48,11 @@ export class SendAccessTextComponent {
protected copyText() {
this.platformUtilsService.copyToClipboard(this.send.text.text);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendTypeText")),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("sendTypeText")),
});
}
protected toggleText() {

View File

@ -12,7 +12,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService, NoItemsModule, SearchModule, TableDataSource } from "@bitwarden/components";
import {
DialogService,
NoItemsModule,
SearchModule,
TableDataSource,
ToastService,
} from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
@ -56,6 +62,7 @@ export class SendComponent extends BaseSendComponent {
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
@ -68,6 +75,7 @@ export class SendComponent extends BaseSendComponent {
logService,
sendApiService,
dialogService,
toastService,
);
}

View File

@ -1,39 +0,0 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@Component({
selector: "app-tools",
templateUrl: "tools.component.html",
})
export class ToolsComponent implements OnInit, OnDestroy {
private componentIsDestroyed$ = new Subject<boolean>();
canAccessPremium = false;
constructor(
private messagingService: MessagingService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {}
async ngOnInit() {
this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntil(this.componentIsDestroyed$))
.subscribe((canAccessPremium: boolean) => {
this.canAccessPremium = canAccessPremium;
});
}
ngOnDestroy() {
this.componentIsDestroyed$.next(true);
this.componentIsDestroyed$.complete();
}
premiumRequired() {
if (!this.canAccessPremium) {
this.messagingService.send("premiumRequired");
return;
}
}
}

View File

@ -7,8 +7,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui";
@ -19,7 +18,7 @@ import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-
export class ExportComponent extends BaseExportComponent {
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
toastService: ToastService,
exportService: VaultExportServiceAbstraction,
eventCollectionService: EventCollectionService,
policyService: PolicyService,
@ -31,7 +30,7 @@ export class ExportComponent extends BaseExportComponent {
) {
super(
i18nService,
platformUtilsService,
toastService,
exportService,
eventCollectionService,
policyService,
@ -45,6 +44,10 @@ export class ExportComponent extends BaseExportComponent {
protected saved() {
super.saved();
this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("exportSuccess"),
});
}
}

View File

@ -8405,5 +8405,29 @@
},
"memberAccessReportDesc": {
"message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations."
},
"higherKDFIterations": {
"message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker."
},
"incrementsOf100,000": {
"message": "increments of 100,000"
},
"smallIncrements": {
"message": "small increments"
},
"kdfIterationRecommends": {
"message": "We recommend 600,000 or more"
},
"kdfToHighWarningIncreaseInIncrements": {
"message": "For older devices, setting your KDF too high may lead to performance issues. Increase the value in $VALUE$ and test your devices.",
"placeholders": {
"value": {
"content": "$1",
"example":"increments of 100,000"
}
}
},
"providerReinstate":{
"message": " Contact Customer Support to reinstate your subscription."
}
}

View File

@ -1,28 +0,0 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
@Component({
selector: "provider-manage",
templateUrl: "manage.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ManageComponent implements OnInit {
provider: Provider;
accessEvents = false;
constructor(
private route: ActivatedRoute,
private providerService: ProviderService,
) {}
ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.params.subscribe(async (params) => {
this.provider = await this.providerService.get(params.providerId);
this.accessEvents = this.provider.useEvents;
});
}
}

View File

@ -43,6 +43,10 @@ export class SubscriptionStatusComponent {
}
get status(): string {
if (this.subscription.cancelAt && this.subscription.status === "active") {
this.subscription.status = "pending_cancellation";
}
return this.subscription.status;
}
@ -151,13 +155,15 @@ export class SubscriptionStatusComponent {
},
date: {
label: cancellationDateLabel,
value: this.subscription.currentPeriodEndDate.toDateString(),
value: this.subscription.cancelAt,
},
callout: {
severity: "warning",
header: pendingCancellationText,
body: this.i18nService.t("subscriptionPendingCanceled"),
showReinstatementButton: true,
body:
this.i18nService.t("subscriptionPendingCanceled") +
this.i18nService.t("providerReinstate"),
showReinstatementButton: false,
},
};
}

View File

@ -19,6 +19,7 @@ import {
UsernameGeneratorOptions,
} from "@bitwarden/common/tools/generator/username";
import { EmailForwarderOptions } from "@bitwarden/common/tools/models/domain/email-forwarder-options";
import { ToastService } from "@bitwarden/components";
@Directive()
export class GeneratorComponent implements OnInit, OnDestroy {
@ -67,6 +68,7 @@ export class GeneratorComponent implements OnInit, OnDestroy {
protected route: ActivatedRoute,
protected ngZone: NgZone,
private win: Window,
protected toastService: ToastService,
) {
this.typeOptions = [
{ name: i18nService.t("password"), value: "password" },
@ -317,11 +319,14 @@ export class GeneratorComponent implements OnInit, OnDestroy {
password ? this.password : this.username,
copyOptions,
);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(password ? "password" : "username")),
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t(
"valueCopied",
this.i18nService.t(password ? "password" : "username"),
),
});
}
select() {

View File

@ -6,6 +6,7 @@ import {
GeneratedPasswordHistory,
PasswordGenerationServiceAbstraction,
} from "@bitwarden/common/tools/generator/password";
import { ToastService } from "@bitwarden/components";
@Directive()
export class PasswordGeneratorHistoryComponent implements OnInit {
@ -16,6 +17,7 @@ export class PasswordGeneratorHistoryComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window,
protected toastService: ToastService,
) {}
async ngOnInit() {
@ -29,10 +31,10 @@ export class PasswordGeneratorHistoryComponent implements OnInit {
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password")),
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
});
}
}

View File

@ -22,7 +22,7 @@ import { SendTextView } from "@bitwarden/common/tools/send/models/view/send-text
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
// Value = hours
enum DatePreset {
@ -120,6 +120,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected formBuilder: FormBuilder,
protected billingAccountProfileStateService: BillingAccountProfileStateService,
protected accountService: AccountService,
protected toastService: ToastService,
) {
this.typeOptions = [
{ name: i18nService.t("sendTypeFile"), value: SendType.File, premium: true },
@ -269,11 +270,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.formGroup.markAllAsTouched();
if (this.disableSend) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sendDisabledWarning"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("sendDisabledWarning"),
});
return false;
}
@ -289,11 +290,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.send.type = this.type;
if (Utils.isNullOrWhitespace(this.send.name)) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("nameRequired"),
});
return false;
}
@ -302,22 +303,22 @@ export class AddEditComponent implements OnInit, OnDestroy {
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("selectFile"),
});
return;
}
file = files[0];
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("maxFileSize"),
});
return;
}
}
@ -340,11 +341,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
await this.handleCopyLinkToClipboard();
return;
}
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
});
});
try {
await this.formPromise;
@ -377,7 +378,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
try {
this.deletePromise = this.sendApiService.delete(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("deletedSend"),
});
await this.load();
this.onDeletedSend.emit(this.send);
return true;
@ -470,11 +475,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
private async handleCopyLinkToClipboard() {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
});
} else {
await this.dialogService.openSimpleDialog({
title: "",

View File

@ -20,7 +20,7 @@ import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
@Directive()
export class SendComponent implements OnInit, OnDestroy {
@ -76,6 +76,7 @@ export class SendComponent implements OnInit, OnDestroy {
private logService: LogService,
protected sendApiService: SendApiService,
protected dialogService: DialogService,
protected toastService: ToastService,
) {}
async ngOnInit() {
@ -186,7 +187,11 @@ export class SendComponent implements OnInit, OnDestroy {
this.onSuccessfulRemovePassword();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("removedPassword"),
});
await this.load();
}
} catch (e) {
@ -220,7 +225,11 @@ export class SendComponent implements OnInit, OnDestroy {
this.onSuccessfulDelete();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("deletedSend"),
});
await this.refresh();
}
} catch (e) {
@ -234,11 +243,11 @@ export class SendComponent implements OnInit, OnDestroy {
const env = await firstValueFrom(this.environmentService.environment$);
const link = env.getSendUrl() + s.accessId + "/" + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink")),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("sendLink")),
});
}
searchTextChanged() {

View File

@ -3,6 +3,7 @@ import { Observable } from "rxjs";
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
import { UserId } from "../../types/guid";
import { VaultTimeout } from "../../types/vault-timeout.type";
import { SetTokensResult } from "../models/domain/set-tokens-result";
import { DecodedAccessToken } from "../services/token.service";
export abstract class TokenService {
@ -23,7 +24,7 @@ export abstract class TokenService {
* @param refreshToken The optional refresh token to set. Note: this is undefined when using the CLI Login Via API Key flow
* @param clientIdClientSecret The API Key Client ID and Client Secret to set.
*
* @returns A promise that resolves when the tokens have been set.
* @returns A promise that resolves with the SetTokensResult containing the tokens that were set.
*/
setTokens: (
accessToken: string,
@ -31,7 +32,7 @@ export abstract class TokenService {
vaultTimeout: VaultTimeout,
refreshToken?: string,
clientIdClientSecret?: [string, string],
) => Promise<void>;
) => Promise<SetTokensResult>;
/**
* Clears the access token, refresh token, API Key Client ID, and API Key Client Secret out of memory, disk, and secure storage if supported.
@ -47,13 +48,13 @@ export abstract class TokenService {
* @param accessToken The access token to set.
* @param vaultTimeoutAction The action to take when the vault times out.
* @param vaultTimeout The timeout for the vault.
* @returns A promise that resolves when the access token has been set.
* @returns A promise that resolves with the access token that has been set.
*/
setAccessToken: (
accessToken: string,
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
) => Promise<void>;
) => Promise<string>;
// TODO: revisit having this public clear method approach once the state service is fully deprecated.
/**
@ -86,14 +87,14 @@ export abstract class TokenService {
* @param clientId The API Key Client ID to set.
* @param vaultTimeoutAction The action to take when the vault times out.
* @param vaultTimeout The timeout for the vault.
* @returns A promise that resolves when the API Key Client ID has been set.
* @returns A promise that resolves with the API Key Client ID that has been set.
*/
setClientId: (
clientId: string,
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId?: UserId,
) => Promise<void>;
) => Promise<string>;
/**
* Gets the API Key Client ID for the active user.
@ -106,14 +107,14 @@ export abstract class TokenService {
* @param clientSecret The API Key Client Secret to set.
* @param vaultTimeoutAction The action to take when the vault times out.
* @param vaultTimeout The timeout for the vault.
* @returns A promise that resolves when the API Key Client Secret has been set.
* @returns A promise that resolves with the client secret that has been set.
*/
setClientSecret: (
clientSecret: string,
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId?: UserId,
) => Promise<void>;
) => Promise<string>;
/**
* Gets the API Key Client Secret for the active user.

View File

@ -0,0 +1,10 @@
export class SetTokensResult {
constructor(accessToken: string, refreshToken?: string, clientIdSecretPair?: [string, string]) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.clientIdSecretPair = clientIdSecretPair;
}
accessToken: string;
refreshToken?: string;
clientIdSecretPair?: [string, string];
}

View File

@ -15,6 +15,7 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt
import { CsprngArray } from "../../types/csprng";
import { UserId } from "../../types/guid";
import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type";
import { SetTokensResult } from "../models/domain/set-tokens-result";
import { ACCOUNT_ACTIVE_ACCOUNT_ID } from "./account.service";
import {
@ -232,7 +233,7 @@ describe("TokenService", () => {
describe("Memory storage tests", () => {
it("set the access token in memory", async () => {
// Act
await tokenService.setAccessToken(
const result = await tokenService.setAccessToken(
accessTokenJwt,
memoryVaultTimeoutAction,
memoryVaultTimeout,
@ -241,13 +242,14 @@ describe("TokenService", () => {
expect(
singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY).nextMock,
).toHaveBeenCalledWith(accessTokenJwt);
expect(result).toEqual(accessTokenJwt);
});
});
describe("Disk storage tests (secure storage not supported on platform)", () => {
it("should set the access token in disk", async () => {
// Act
await tokenService.setAccessToken(
const result = await tokenService.setAccessToken(
accessTokenJwt,
diskVaultTimeoutAction,
diskVaultTimeout,
@ -256,6 +258,7 @@ describe("TokenService", () => {
expect(
singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock,
).toHaveBeenCalledWith(accessTokenJwt);
expect(result).toEqual(accessTokenJwt);
});
});
@ -295,7 +298,7 @@ describe("TokenService", () => {
secureStorageService.get.mockResolvedValueOnce(null).mockResolvedValue(accessTokenKeyB64);
// Act
await tokenService.setAccessToken(
const result = await tokenService.setAccessToken(
accessTokenJwt,
diskVaultTimeoutAction,
diskVaultTimeout,
@ -318,6 +321,9 @@ describe("TokenService", () => {
expect(
singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY).nextMock,
).toHaveBeenCalledWith(null);
// assert that the decrypted access token was returned
expect(result).toEqual(accessTokenJwt);
});
it("should fallback to disk storage for the access token if the access token cannot be set in secure storage", async () => {
@ -331,7 +337,7 @@ describe("TokenService", () => {
secureStorageService.get.mockResolvedValueOnce(null).mockResolvedValue(null);
// Act
await tokenService.setAccessToken(
const result = await tokenService.setAccessToken(
accessTokenJwt,
diskVaultTimeoutAction,
diskVaultTimeout,
@ -355,6 +361,9 @@ describe("TokenService", () => {
expect(
singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock,
).toHaveBeenCalledWith(accessTokenJwt);
// assert that the decrypted access token was returned
expect(result).toEqual(accessTokenJwt);
});
it("should fallback to disk storage for the access token if secure storage errors on trying to get an existing access token key", async () => {
@ -368,7 +377,7 @@ describe("TokenService", () => {
secureStorageService.get.mockRejectedValue(new Error(secureStorageError));
// Act
await tokenService.setAccessToken(
const result = await tokenService.setAccessToken(
accessTokenJwt,
diskVaultTimeoutAction,
diskVaultTimeout,
@ -385,6 +394,9 @@ describe("TokenService", () => {
expect(
singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock,
).toHaveBeenCalledWith(accessTokenJwt);
// assert that the decrypted access token was returned
expect(result).toEqual(accessTokenJwt);
});
});
});
@ -2376,18 +2388,21 @@ describe("TokenService", () => {
const clientId = "clientId";
const clientSecret = "clientSecret";
(tokenService as any)._setAccessToken = jest.fn();
// any hack allows for mocking private method.
(tokenService as any).setRefreshToken = jest.fn();
tokenService.setClientId = jest.fn();
tokenService.setClientSecret = jest.fn();
(tokenService as any)._setAccessToken = jest.fn().mockReturnValue(accessTokenJwt);
(tokenService as any).setRefreshToken = jest.fn().mockReturnValue(refreshToken);
tokenService.setClientId = jest.fn().mockReturnValue(clientId);
tokenService.setClientSecret = jest.fn().mockReturnValue(clientSecret);
// Act
// Note: passing a valid access token so that a valid user id can be determined from the access token
await tokenService.setTokens(accessTokenJwt, vaultTimeoutAction, vaultTimeout, refreshToken, [
clientId,
clientSecret,
]);
const result = await tokenService.setTokens(
accessTokenJwt,
vaultTimeoutAction,
vaultTimeout,
refreshToken,
[clientId, clientSecret],
);
// Assert
expect((tokenService as any)._setAccessToken).toHaveBeenCalledWith(
@ -2417,6 +2432,44 @@ describe("TokenService", () => {
vaultTimeout,
userIdFromAccessToken,
);
expect(result).toStrictEqual(
new SetTokensResult(accessTokenJwt, refreshToken, [clientId, clientSecret]),
);
});
it("does not try to set the refresh token when it is not passed in", async () => {
// Arrange
const vaultTimeoutAction = VaultTimeoutAction.Lock;
const vaultTimeout = 30;
(tokenService as any)._setAccessToken = jest.fn().mockReturnValue(accessTokenJwt);
(tokenService as any).setRefreshToken = jest.fn();
tokenService.setClientId = jest.fn();
tokenService.setClientSecret = jest.fn();
// Act
const result = await tokenService.setTokens(
accessTokenJwt,
vaultTimeoutAction,
vaultTimeout,
null,
);
// Assert
expect((tokenService as any)._setAccessToken).toHaveBeenCalledWith(
accessTokenJwt,
vaultTimeoutAction,
vaultTimeout,
userIdFromAccessToken,
);
// any hack allows for testing private methods
expect((tokenService as any).setRefreshToken).not.toHaveBeenCalled();
expect(tokenService.setClientId).not.toHaveBeenCalled();
expect(tokenService.setClientSecret).not.toHaveBeenCalled();
expect(result).toStrictEqual(new SetTokensResult(accessTokenJwt));
});
it("does not try to set client id and client secret when they are not passed in", async () => {
@ -2425,13 +2478,18 @@ describe("TokenService", () => {
const vaultTimeoutAction = VaultTimeoutAction.Lock;
const vaultTimeout = 30;
(tokenService as any)._setAccessToken = jest.fn();
(tokenService as any).setRefreshToken = jest.fn();
(tokenService as any)._setAccessToken = jest.fn().mockReturnValue(accessTokenJwt);
(tokenService as any).setRefreshToken = jest.fn().mockReturnValue(refreshToken);
tokenService.setClientId = jest.fn();
tokenService.setClientSecret = jest.fn();
// Act
await tokenService.setTokens(accessTokenJwt, vaultTimeoutAction, vaultTimeout, refreshToken);
const result = await tokenService.setTokens(
accessTokenJwt,
vaultTimeoutAction,
vaultTimeout,
refreshToken,
);
// Assert
expect((tokenService as any)._setAccessToken).toHaveBeenCalledWith(
@ -2451,6 +2509,8 @@ describe("TokenService", () => {
expect(tokenService.setClientId).not.toHaveBeenCalled();
expect(tokenService.setClientSecret).not.toHaveBeenCalled();
expect(result).toStrictEqual(new SetTokensResult(accessTokenJwt, refreshToken));
});
it("throws an error when the access token is invalid", async () => {
@ -2535,10 +2595,16 @@ describe("TokenService", () => {
(tokenService as any).setRefreshToken = jest.fn();
// Act
await tokenService.setTokens(accessTokenJwt, vaultTimeoutAction, vaultTimeout, refreshToken);
const result = await tokenService.setTokens(
accessTokenJwt,
vaultTimeoutAction,
vaultTimeout,
refreshToken,
);
// Assert
expect((tokenService as any).setRefreshToken).not.toHaveBeenCalled();
expect(result).toStrictEqual(new SetTokensResult(accessTokenJwt));
});
});

View File

@ -21,6 +21,7 @@ import {
import { UserId } from "../../types/guid";
import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type";
import { TokenService as TokenServiceAbstraction } from "../abstractions/token.service";
import { SetTokensResult } from "../models/domain/set-tokens-result";
import { ACCOUNT_ACTIVE_ACCOUNT_ID } from "./account.service";
import {
@ -160,7 +161,7 @@ export class TokenService implements TokenServiceAbstraction {
vaultTimeout: VaultTimeout,
refreshToken?: string,
clientIdClientSecret?: [string, string],
): Promise<void> {
): Promise<SetTokensResult> {
if (!accessToken) {
throw new Error("Access token is required.");
}
@ -181,16 +182,40 @@ export class TokenService implements TokenServiceAbstraction {
throw new Error("User id not found. Cannot set tokens.");
}
await this._setAccessToken(accessToken, vaultTimeoutAction, vaultTimeout, userId);
const newAccessToken = await this._setAccessToken(
accessToken,
vaultTimeoutAction,
vaultTimeout,
userId,
);
const newTokens = new SetTokensResult(newAccessToken);
if (refreshToken) {
await this.setRefreshToken(refreshToken, vaultTimeoutAction, vaultTimeout, userId);
newTokens.refreshToken = await this.setRefreshToken(
refreshToken,
vaultTimeoutAction,
vaultTimeout,
userId,
);
}
if (clientIdClientSecret != null) {
await this.setClientId(clientIdClientSecret[0], vaultTimeoutAction, vaultTimeout, userId);
await this.setClientSecret(clientIdClientSecret[1], vaultTimeoutAction, vaultTimeout, userId);
const clientId = await this.setClientId(
clientIdClientSecret[0],
vaultTimeoutAction,
vaultTimeout,
userId,
);
const clientSecret = await this.setClientSecret(
clientIdClientSecret[1],
vaultTimeoutAction,
vaultTimeout,
userId,
);
newTokens.clientIdSecretPair = [clientId, clientSecret];
}
return newTokens;
}
private async getAccessTokenKey(userId: UserId): Promise<AccessTokenKey | null> {
@ -289,7 +314,7 @@ export class TokenService implements TokenServiceAbstraction {
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId: UserId,
): Promise<void> {
): Promise<string> {
const storageLocation = await this.determineStorageLocation(
vaultTimeoutAction,
vaultTimeout,
@ -302,6 +327,8 @@ export class TokenService implements TokenServiceAbstraction {
// store the access token directly. Instead, we encrypt with accessTokenKey and store that
// in secure storage.
let decryptedAccessToken: string = null;
try {
const encryptedAccessToken: EncString = await this.encryptAccessToken(
accessToken,
@ -313,6 +340,10 @@ export class TokenService implements TokenServiceAbstraction {
.get(userId, ACCESS_TOKEN_DISK)
.update((_) => encryptedAccessToken.encryptedString);
// If we've successfully stored the encrypted access token to disk, we can return the decrypted access token
// so that the caller can use it immediately.
decryptedAccessToken = accessToken;
// TODO: PM-6408
// 2024-02-20: Remove access token from memory so that we migrate to encrypt the access token over time.
// Remove this call to remove the access token from memory after 3 months.
@ -324,25 +355,23 @@ export class TokenService implements TokenServiceAbstraction {
);
// Fall back to disk storage for unecrypted access token
await this.singleUserStateProvider
decryptedAccessToken = await this.singleUserStateProvider
.get(userId, ACCESS_TOKEN_DISK)
.update((_) => accessToken);
}
return;
return decryptedAccessToken;
}
case TokenStorageLocation.Disk:
// Access token stored on disk unencrypted as platform does not support secure storage
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, ACCESS_TOKEN_DISK)
.update((_) => accessToken);
return;
case TokenStorageLocation.Memory:
// Access token stored in memory due to vault timeout settings
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, ACCESS_TOKEN_MEMORY)
.update((_) => accessToken);
return;
}
}
@ -350,7 +379,7 @@ export class TokenService implements TokenServiceAbstraction {
accessToken: string,
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
): Promise<void> {
): Promise<string> {
if (!accessToken) {
throw new Error("Access token is required.");
}
@ -370,7 +399,7 @@ export class TokenService implements TokenServiceAbstraction {
throw new Error("Vault Timeout Action is required.");
}
await this._setAccessToken(accessToken, vaultTimeoutAction, vaultTimeout, userId);
return await this._setAccessToken(accessToken, vaultTimeoutAction, vaultTimeout, userId);
}
async clearAccessToken(userId?: UserId): Promise<void> {
@ -486,7 +515,7 @@ export class TokenService implements TokenServiceAbstraction {
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId: UserId,
): Promise<void> {
): Promise<string> {
// If we don't have a user id, we can't save the value
if (!userId) {
throw new Error("User id not found. Cannot save refresh token.");
@ -509,6 +538,8 @@ export class TokenService implements TokenServiceAbstraction {
switch (storageLocation) {
case TokenStorageLocation.SecureStorage: {
let decryptedRefreshToken: string = null;
try {
await this.saveStringToSecureStorage(
userId,
@ -530,6 +561,10 @@ export class TokenService implements TokenServiceAbstraction {
throw new Error("Refresh token failed to save to secure storage.");
}
// If we've successfully stored the encrypted refresh token, we can return the decrypted refresh token
// so that the caller can use it immediately.
decryptedRefreshToken = refreshToken;
// TODO: PM-6408
// 2024-02-20: Remove refresh token from memory and disk so that we migrate to secure storage over time.
// Remove these 2 calls to remove the refresh token from memory and disk after 3 months.
@ -544,24 +579,22 @@ export class TokenService implements TokenServiceAbstraction {
);
// Fall back to disk storage for refresh token
await this.singleUserStateProvider
decryptedRefreshToken = await this.singleUserStateProvider
.get(userId, REFRESH_TOKEN_DISK)
.update((_) => refreshToken);
}
return;
return decryptedRefreshToken;
}
case TokenStorageLocation.Disk:
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, REFRESH_TOKEN_DISK)
.update((_) => refreshToken);
return;
case TokenStorageLocation.Memory:
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, REFRESH_TOKEN_MEMORY)
.update((_) => refreshToken);
return;
}
}
@ -644,7 +677,7 @@ export class TokenService implements TokenServiceAbstraction {
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId?: UserId,
): Promise<void> {
): Promise<string> {
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
// If we don't have a user id, we can't save the value
@ -668,11 +701,11 @@ export class TokenService implements TokenServiceAbstraction {
);
if (storageLocation === TokenStorageLocation.Disk) {
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, API_KEY_CLIENT_ID_DISK)
.update((_) => clientId);
} else if (storageLocation === TokenStorageLocation.Memory) {
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, API_KEY_CLIENT_ID_MEMORY)
.update((_) => clientId);
}
@ -721,7 +754,7 @@ export class TokenService implements TokenServiceAbstraction {
vaultTimeoutAction: VaultTimeoutAction,
vaultTimeout: VaultTimeout,
userId?: UserId,
): Promise<void> {
): Promise<string> {
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
if (!userId) {
@ -744,11 +777,11 @@ export class TokenService implements TokenServiceAbstraction {
);
if (storageLocation === TokenStorageLocation.Disk) {
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, API_KEY_CLIENT_SECRET_DISK)
.update((_) => clientSecret);
} else if (storageLocation === TokenStorageLocation.Memory) {
await this.singleUserStateProvider
return await this.singleUserStateProvider
.get(userId, API_KEY_CLIENT_SECRET_MEMORY)
.update((_) => clientSecret);
}

View File

@ -9,6 +9,7 @@ export class ProviderSubscriptionResponse extends BaseResponse {
unpaidPeriodEndDate?: string;
gracePeriod?: number | null;
suspensionDate?: string;
cancelAt?: string;
constructor(response: any) {
super(response);
@ -19,6 +20,7 @@ export class ProviderSubscriptionResponse extends BaseResponse {
this.unpaidPeriodEndDate = this.getResponseProperty("unpaidPeriodEndDate");
this.gracePeriod = this.getResponseProperty("gracePeriod");
this.suspensionDate = this.getResponseProperty("suspensionDate");
this.cancelAt = this.getResponseProperty("cancelAt");
const plans = this.getResponseProperty("plans");
if (plans != null) {
this.plans = plans.map((i: any) => new ProviderPlanResponse(i));

View File

@ -249,7 +249,7 @@ export class ApiService implements ApiServiceAbstraction {
async refreshIdentityToken(): Promise<any> {
try {
await this.doAuthRefresh();
await this.refreshToken();
} catch (e) {
this.logService.error("Error refreshing access token: ", e);
throw e;
@ -1566,8 +1566,7 @@ export class ApiService implements ApiServiceAbstraction {
async getActiveBearerToken(): Promise<string> {
let accessToken = await this.tokenService.getAccessToken();
if (await this.tokenService.tokenNeedsRefresh()) {
await this.doAuthRefresh();
accessToken = await this.tokenService.getAccessToken();
accessToken = await this.refreshToken();
}
return accessToken;
}
@ -1707,16 +1706,16 @@ export class ApiService implements ApiServiceAbstraction {
);
}
protected async doAuthRefresh(): Promise<void> {
protected async refreshToken(): Promise<string> {
const refreshToken = await this.tokenService.getRefreshToken();
if (refreshToken != null && refreshToken !== "") {
return this.doRefreshToken();
return this.refreshAccessToken();
}
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
if (!Utils.isNullOrWhitespace(clientId) && !Utils.isNullOrWhitespace(clientSecret)) {
return this.doApiTokenRefresh();
return this.refreshApiToken();
}
this.refreshAccessTokenErrorCallback();
@ -1724,7 +1723,7 @@ export class ApiService implements ApiServiceAbstraction {
throw new Error("Cannot refresh access token, no refresh token or api keys are stored.");
}
protected async doRefreshToken(): Promise<void> {
protected async refreshAccessToken(): Promise<string> {
const refreshToken = await this.tokenService.getRefreshToken();
if (refreshToken == null || refreshToken === "") {
throw new Error();
@ -1770,19 +1769,20 @@ export class ApiService implements ApiServiceAbstraction {
this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId),
);
await this.tokenService.setTokens(
const refreshedTokens = await this.tokenService.setTokens(
tokenResponse.accessToken,
vaultTimeoutAction as VaultTimeoutAction,
vaultTimeout,
tokenResponse.refreshToken,
);
return refreshedTokens.accessToken;
} else {
const error = await this.handleError(response, true, true);
return Promise.reject(error);
}
}
protected async doApiTokenRefresh(): Promise<void> {
protected async refreshApiToken(): Promise<string> {
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
@ -1810,11 +1810,12 @@ export class ApiService implements ApiServiceAbstraction {
this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId),
);
await this.tokenService.setAccessToken(
const refreshedToken = await this.tokenService.setAccessToken(
response.accessToken,
vaultTimeoutAction as VaultTimeoutAction,
vaultTimeout,
);
return refreshedToken;
}
async send(

View File

@ -48,6 +48,7 @@ import {
IconButtonModule,
RadioButtonModule,
SelectModule,
ToastService,
} from "@bitwarden/components";
import { ImportOption, ImportResult, ImportType } from "../models";
@ -191,6 +192,7 @@ export class ImportComponent implements OnInit, OnDestroy {
@Inject(ImportCollectionServiceAbstraction)
@Optional()
protected importCollectionService: ImportCollectionServiceAbstraction,
protected toastService: ToastService,
) {}
protected get importBlockedByPolicy(): boolean {
@ -336,22 +338,22 @@ export class ImportComponent implements OnInit, OnDestroy {
);
if (importer === null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFormat"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("selectFormat"),
});
return;
}
const importContents = await this.setImportContents();
if (importContents == null || importContents === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("selectFile"),
});
return;
}
@ -502,11 +504,11 @@ export class ImportComponent implements OnInit, OnDestroy {
}
if (this.importBlockedByPolicy && this.organizationId == null) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("personalOwnershipPolicyInEffectImports"),
);
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("personalOwnershipPolicyInEffectImports"),
});
return false;
}
@ -517,14 +519,6 @@ export class ImportComponent implements OnInit, OnDestroy {
const fileEl = document.getElementById("import_input_file") as HTMLInputElement;
const files = fileEl.files;
let fileContents = this.formGroup.controls.fileContents.value;
if ((files == null || files.length === 0) && (fileContents == null || fileContents === "")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile"),
);
return;
}
if (files != null && files.length > 0) {
try {

View File

@ -15,7 +15,6 @@ import { EventType } from "@bitwarden/common/enums";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum";
import {
@ -28,6 +27,7 @@ import {
IconButtonModule,
RadioButtonModule,
SelectModule,
ToastService,
} from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
@ -123,7 +123,7 @@ export class ExportComponent implements OnInit, OnDestroy {
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected toastService: ToastService,
protected exportService: VaultExportServiceAbstraction,
protected eventCollectionService: EventCollectionService,
private policyService: PolicyService,
@ -222,11 +222,11 @@ export class ExportComponent implements OnInit, OnDestroy {
submit = async () => {
if (this.isFileEncryptedExport && this.filePassword != this.confirmFilePassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("filePasswordAndConfirmFilePasswordDoNotMatch"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("filePasswordAndConfirmFilePasswordDoNotMatch"),
});
return;
}
@ -236,11 +236,11 @@ export class ExportComponent implements OnInit, OnDestroy {
}
if (this.disabledByPolicy) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("personalVaultExportPolicyInEffect"),
);
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("personalVaultExportPolicyInEffect"),
});
return;
}

575
package-lock.json generated
View File

@ -82,7 +82,7 @@
"@angular/elements": "16.2.12",
"@babel/core": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@compodoc/compodoc": "1.1.23",
"@compodoc/compodoc": "1.1.25",
"@electron/notarize": "2.3.0",
"@electron/rebuild": "3.6.0",
"@ngtools/webpack": "16.2.11",
@ -2709,23 +2709,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-private-methods": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
"integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
"dev": true,
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@ -4485,51 +4468,51 @@
}
},
"node_modules/@compodoc/compodoc": {
"version": "1.1.23",
"resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.23.tgz",
"integrity": "sha512-5Zfx+CHKTxLD+TxCGt1U8krnEBCWPVxCLt3jCJEN55AzhTluo8xlMenaXlJsuVqL4Lmo/OTTzEXrm9zoQKh/3w==",
"version": "1.1.25",
"resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.25.tgz",
"integrity": "sha512-MsTEv6S0JGkdXc8pFp3yB/r8Lw49YenD0TCXyIVAmQhWNDtGWi4m2TGz02hdiKAlTJ1McQJFuyXWiItTQtje0A==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/schematics": "14.2.12",
"@babel/core": "^7.23.3",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/preset-env": "^7.23.3",
"@angular-devkit/schematics": "18.0.1",
"@babel/core": "^7.24.6",
"@babel/plugin-transform-private-methods": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@compodoc/live-server": "^1.2.3",
"@compodoc/ngd-transformer": "^2.1.3",
"bootstrap.native": "^5.0.10",
"bootstrap.native": "^5.0.12",
"chalk": "4.1.2",
"cheerio": "^1.0.0-rc.12",
"chokidar": "^3.5.3",
"chokidar": "^3.6.0",
"colors": "1.4.0",
"commander": "^11.1.0",
"cosmiconfig": "^8.3.6",
"commander": "^12.1.0",
"cosmiconfig": "^9.0.0",
"decache": "^4.6.2",
"es6-shim": "^0.35.8",
"fancy-log": "^2.0.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.1.1",
"glob": "^10.3.10",
"fs-extra": "^11.2.0",
"glob": "^10.4.1",
"handlebars": "^4.7.8",
"html-entities": "^2.4.0",
"i18next": "^23.7.6",
"html-entities": "^2.5.2",
"i18next": "^23.11.5",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"loglevel": "^1.8.1",
"loglevel": "^1.9.1",
"loglevel-plugin-prefix": "^0.8.4",
"lunr": "^2.3.9",
"marked": "7.0.3",
"minimist": "^1.2.8",
"opencollective-postinstall": "^2.0.3",
"os-name": "4.0.1",
"pdfjs-dist": "2.12.313",
"pdfmake": "^0.2.8",
"pdfmake": "^0.2.10",
"prismjs": "^1.29.0",
"semver": "^7.5.4",
"semver": "^7.6.2",
"svg-pan-zoom": "^3.6.1",
"tablesort": "^5.3.0",
"traverse": "^0.6.7",
"ts-morph": "^20.0.0",
"traverse": "^0.6.9",
"ts-morph": "^22.0.0",
"uuid": "^9.0.1",
"vis": "^4.21.0-EOL",
"zepto": "^1.2.0"
@ -4538,23 +4521,25 @@
"compodoc": "bin/index-cli.js"
},
"engines": {
"node": ">= 14.0.0"
"node": ">= 16.0.0"
}
},
"node_modules/@compodoc/compodoc/node_modules/@angular-devkit/core": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.12.tgz",
"integrity": "sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==",
"version": "18.0.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.0.1.tgz",
"integrity": "sha512-91eKZoObs+wRgwssw81Y/94Nvixj0WqJkNusBAg+gAfZTCEeJoGGZJkRK8wrONbM79C3Bx8lN/TfSIPRbjnfOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.1.0",
"rxjs": "6.6.7",
"ajv": "8.13.0",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.2.1",
"picomatch": "4.0.2",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^14.15.0 || >=16.10.0",
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
@ -4568,182 +4553,126 @@
}
},
"node_modules/@compodoc/compodoc/node_modules/@angular-devkit/schematics": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.12.tgz",
"integrity": "sha512-MN5yGR+SSSPPBBVMf4cifDJn9u0IYvxiHst+HWokH2AkBYy+vB1x8jYES2l1wkiISD7nvjTixfqX+Y95oMBoLg==",
"version": "18.0.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.0.1.tgz",
"integrity": "sha512-AKcEGa3fIgyXT6XTQZWEJZzgmcqlB89fcF7JFOuz4rgQfRmnE2xFw37lKE6ZclCOSiEoffAvgrL8acjdPI1ouw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/core": "14.2.12",
"jsonc-parser": "3.1.0",
"magic-string": "0.26.2",
"@angular-devkit/core": "18.0.1",
"jsonc-parser": "3.2.1",
"magic-string": "0.30.10",
"ora": "5.4.1",
"rxjs": "6.6.7"
"rxjs": "7.8.1"
},
"engines": {
"node": "^14.15.0 || >=16.10.0",
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-to-generator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz",
"integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==",
"dev": true,
"dependencies": {
"@babel/helper-module-imports": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-remap-async-to-generator": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@compodoc/compodoc/node_modules/@babel/preset-env": {
"version": "7.24.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz",
"integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.24.1",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-class-static-block": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3",
"@babel/plugin-syntax-import-assertions": "^7.24.1",
"@babel/plugin-syntax-import-attributes": "^7.24.1",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-numeric-separator": "^7.10.4",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.24.1",
"@babel/plugin-transform-async-generator-functions": "^7.24.3",
"@babel/plugin-transform-async-to-generator": "^7.24.1",
"@babel/plugin-transform-block-scoped-functions": "^7.24.1",
"@babel/plugin-transform-block-scoping": "^7.24.1",
"@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/plugin-transform-class-static-block": "^7.24.1",
"@babel/plugin-transform-classes": "^7.24.1",
"@babel/plugin-transform-computed-properties": "^7.24.1",
"@babel/plugin-transform-destructuring": "^7.24.1",
"@babel/plugin-transform-dotall-regex": "^7.24.1",
"@babel/plugin-transform-duplicate-keys": "^7.24.1",
"@babel/plugin-transform-dynamic-import": "^7.24.1",
"@babel/plugin-transform-exponentiation-operator": "^7.24.1",
"@babel/plugin-transform-export-namespace-from": "^7.24.1",
"@babel/plugin-transform-for-of": "^7.24.1",
"@babel/plugin-transform-function-name": "^7.24.1",
"@babel/plugin-transform-json-strings": "^7.24.1",
"@babel/plugin-transform-literals": "^7.24.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.1",
"@babel/plugin-transform-member-expression-literals": "^7.24.1",
"@babel/plugin-transform-modules-amd": "^7.24.1",
"@babel/plugin-transform-modules-commonjs": "^7.24.1",
"@babel/plugin-transform-modules-systemjs": "^7.24.1",
"@babel/plugin-transform-modules-umd": "^7.24.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
"@babel/plugin-transform-new-target": "^7.24.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
"@babel/plugin-transform-numeric-separator": "^7.24.1",
"@babel/plugin-transform-object-rest-spread": "^7.24.1",
"@babel/plugin-transform-object-super": "^7.24.1",
"@babel/plugin-transform-optional-catch-binding": "^7.24.1",
"@babel/plugin-transform-optional-chaining": "^7.24.1",
"@babel/plugin-transform-parameters": "^7.24.1",
"@babel/plugin-transform-private-methods": "^7.24.1",
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
"@babel/plugin-transform-property-literals": "^7.24.1",
"@babel/plugin-transform-regenerator": "^7.24.1",
"@babel/plugin-transform-reserved-words": "^7.24.1",
"@babel/plugin-transform-shorthand-properties": "^7.24.1",
"@babel/plugin-transform-spread": "^7.24.1",
"@babel/plugin-transform-sticky-regex": "^7.24.1",
"@babel/plugin-transform-template-literals": "^7.24.1",
"@babel/plugin-transform-typeof-symbol": "^7.24.1",
"@babel/plugin-transform-unicode-escapes": "^7.24.1",
"@babel/plugin-transform-unicode-property-regex": "^7.24.1",
"@babel/plugin-transform-unicode-regex": "^7.24.1",
"@babel/plugin-transform-unicode-sets-regex": "^7.24.1",
"@babel/preset-modules": "0.1.6-no-external-plugins",
"babel-plugin-polyfill-corejs2": "^0.4.10",
"babel-plugin-polyfill-corejs3": "^0.10.4",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"core-js-compat": "^3.31.0",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@compodoc/compodoc/node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@compodoc/compodoc/node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
"uri-js": "^4.4.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz",
"integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==",
"node_modules/@compodoc/compodoc/node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.1",
"core-js-compat": "^3.36.1"
"ajv": "^8.0.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-regenerator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz",
"integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==",
"node_modules/@compodoc/compodoc/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/@compodoc/compodoc/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.1"
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/@compodoc/compodoc/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@compodoc/compodoc/node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@compodoc/compodoc/node_modules/fast-glob": {
@ -4751,6 +4680,7 @@
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -4762,42 +4692,62 @@
"node": ">=8.6.0"
}
},
"node_modules/@compodoc/compodoc/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@compodoc/compodoc/node_modules/jsonc-parser": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz",
"integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==",
"dev": true
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
"dev": true,
"license": "MIT"
},
"node_modules/@compodoc/compodoc/node_modules/magic-string": {
"version": "0.26.2",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz",
"integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==",
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"sourcemap-codec": "^1.4.8"
},
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/@compodoc/compodoc/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@compodoc/compodoc/node_modules/rxjs": {
"version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"node_modules/@compodoc/compodoc/node_modules/semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"dependencies": {
"tslib": "^1.9.0"
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"npm": ">=2.0.0"
"node": ">=10"
}
},
"node_modules/@compodoc/compodoc/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/@compodoc/live-server": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@compodoc/live-server/-/live-server-1.2.3.tgz",
@ -5958,6 +5908,7 @@
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@ -5975,6 +5926,7 @@
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
@ -5987,6 +5939,7 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
@ -5998,13 +5951,15 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@ -6022,6 +5977,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@ -6037,6 +5993,7 @@
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@ -7213,6 +7170,7 @@
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
@ -11255,37 +11213,41 @@
}
},
"node_modules/@ts-morph/common": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.21.0.tgz",
"integrity": "sha512-ES110Mmne5Vi4ypUKrtVQfXFDtCsDXiUiGxF6ILVlE90dDD4fdpC1LSjydl/ml7xJWKSDZwUYD2zkOePMSrPBA==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz",
"integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-glob": "^3.2.12",
"minimatch": "^7.4.3",
"mkdirp": "^2.1.6",
"fast-glob": "^3.3.2",
"minimatch": "^9.0.3",
"mkdirp": "^3.0.1",
"path-browserify": "^1.0.1"
}
},
"node_modules/@ts-morph/common/node_modules/minimatch": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
"integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
"node_modules/@ts-morph/common/node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"brace-expansion": "^2.0.1"
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.4"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
"node": ">=8.6.0"
}
},
"node_modules/@ts-morph/common/node_modules/mkdirp": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
"integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
@ -15318,10 +15280,11 @@
}
},
"node_modules/bootstrap.native": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-5.0.11.tgz",
"integrity": "sha512-bk2i4sQcQk2KuCTs1yygTa+JGjZOpKzIZ/It6TZZOO/Q+PmVGuKuIbrznXF64BUFxXaPNy7gO9LnE7vjGdauSQ==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-5.0.12.tgz",
"integrity": "sha512-qTiFBK7//IgdF9u67w3W91U8C2Fc3TGQh61xa0pbtHmD1YRncncFNNs+6ewG2tW7fBGGMXg57gj5d9Qamr0S+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@thednp/event-listener": "^2.0.4",
"@thednp/shorty": "^2.0.0"
@ -16491,10 +16454,11 @@
}
},
"node_modules/code-block-writer": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==",
"dev": true
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.1.tgz",
"integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==",
"dev": true,
"license": "MIT"
},
"node_modules/code-point-at": {
"version": "1.1.0",
@ -18477,7 +18441,8 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/easy-stack": {
"version": "1.0.1",
@ -21731,22 +21696,23 @@
"dev": true
},
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz",
"integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@ -23697,9 +23663,9 @@
}
},
"node_modules/i18next": {
"version": "23.10.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.1.tgz",
"integrity": "sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==",
"version": "23.11.5",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz",
"integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==",
"dev": true,
"funding": [
{
@ -23715,6 +23681,7 @@
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
@ -25028,10 +24995,11 @@
}
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
"integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@ -29531,10 +29499,11 @@
}
},
"node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@ -32112,26 +32081,28 @@
}
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "14 || >=16.14"
}
@ -32165,20 +32136,6 @@
"through": "~2.3"
}
},
"node_modules/pdfjs-dist": {
"version": "2.12.313",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.12.313.tgz",
"integrity": "sha512-1x6iXO4Qnv6Eb+YFdN5JdUzt4pAkxSp3aLAYPX93eQCyg/m7QFzXVWJHJVtoW48CI8HCXju4dSkhQZwoheL5mA==",
"dev": true,
"peerDependencies": {
"worker-loader": "^3.0.8"
},
"peerDependenciesMeta": {
"worker-loader": {
"optional": true
}
}
},
"node_modules/pdfmake": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.10.tgz",
@ -36027,13 +35984,6 @@
"deprecated": "See https://github.com/lydell/source-map-url#deprecated",
"dev": true
},
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
"dev": true
},
"node_modules/space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
@ -36515,6 +36465,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@ -36590,6 +36541,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@ -37668,10 +37620,16 @@
}
},
"node_modules/traverse": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz",
"integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==",
"version": "0.6.9",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz",
"integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==",
"dev": true,
"license": "MIT",
"dependencies": {
"gopd": "^1.0.1",
"typedarray.prototype.slice": "^1.0.3",
"which-typed-array": "^1.1.15"
},
"engines": {
"node": ">= 0.4"
},
@ -37812,13 +37770,14 @@
}
},
"node_modules/ts-morph": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-20.0.0.tgz",
"integrity": "sha512-JVmEJy2Wow5n/84I3igthL9sudQ8qzjh/6i4tmYCm6IqYyKFlNbJZi7oBdjyqcWSWYRu3CtL0xbT6fS03ESZIg==",
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz",
"integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ts-morph/common": "~0.21.0",
"code-block-writer": "^12.0.0"
"@ts-morph/common": "~0.23.0",
"code-block-writer": "^13.0.1"
}
},
"node_modules/tsconfig-paths": {
@ -38261,6 +38220,27 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/typedarray.prototype.slice": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz",
"integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
"es-abstract": "^1.23.0",
"es-errors": "^1.3.0",
"typed-array-buffer": "^1.0.2",
"typed-array-byte-offset": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typescript": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
@ -40142,6 +40122,7 @@
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",

View File

@ -22,7 +22,7 @@
"test:watch": "jest --clearCache && jest --watch",
"test:watch:all": "jest --watchAll",
"test:types": "node ./scripts/test-types.js",
"docs:json": "compodoc -p ./tsconfig.json -e json -d .",
"docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph",
"storybook": "ng run components:storybook",
"build-storybook": "ng run components:build-storybook",
"build-storybook:ci": "ng run components:build-storybook --webpack-stats-json",
@ -43,7 +43,7 @@
"@angular/elements": "16.2.12",
"@babel/core": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@compodoc/compodoc": "1.1.23",
"@compodoc/compodoc": "1.1.25",
"@electron/notarize": "2.3.0",
"@electron/rebuild": "3.6.0",
"@ngtools/webpack": "16.2.11",