Merge branch 'main' into auth/pm-7392/token-service-add-secure-storage-fallback
This commit is contained in:
commit
eaec61dd4f
|
@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended";
|
|||
import {
|
||||
AssertCredentialResult,
|
||||
CreateCredentialResult,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
export function createCredentialCreationOptionsMock(
|
||||
customFields: Partial<CredentialCreationOptions> = {},
|
||||
|
|
|
@ -79,6 +79,9 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
|
@ -106,6 +109,8 @@ import { DefaultConfigService } from "@bitwarden/common/platform/services/config
|
|||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service";
|
||||
import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
|
@ -158,9 +163,6 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
|
@ -171,8 +173,6 @@ import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwar
|
|||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/services/collection.service";
|
||||
import { Fido2AuthenticatorService } from "@bitwarden/common/vault/services/fido2/fido2-authenticator.service";
|
||||
import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
AssertCredentialResult,
|
||||
CreateCredentialParams,
|
||||
CreateCredentialResult,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
type SharedFido2ScriptInjectionDetails = {
|
||||
runAt: browser.contentScripts.RegisteredContentScriptOptions["runAt"];
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import {
|
||||
AssertCredentialParams,
|
||||
CreateCredentialParams,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service";
|
||||
|
||||
import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks";
|
||||
import {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { firstValueFrom, startWith } from "rxjs";
|
||||
import { pairwise } from "rxjs/operators";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import {
|
||||
AssertCredentialParams,
|
||||
AssertCredentialResult,
|
||||
CreateCredentialParams,
|
||||
CreateCredentialResult,
|
||||
Fido2ClientService,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
|
|
|
@ -16,14 +16,14 @@ import {
|
|||
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserRequestedFallbackAbortReason } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { UserRequestedFallbackAbortReason } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import {
|
||||
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||
Fido2UserInterfaceSession,
|
||||
NewCredentialParams,
|
||||
PickCredentialParams,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { closeFido2Popout, openFido2Popout } from "../popup/utils/vault-popout-window";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { CreateCredentialResult } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { CreateCredentialResult } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks";
|
||||
import { triggerPortOnDisconnectEvent } from "../../../autofill/spec/testing-utils";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AssertCredentialParams,
|
||||
CreateCredentialParams,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
import { sendExtensionMessage } from "../../../autofill/utils";
|
||||
import { Fido2PortName } from "../enums/fido2-port-name.enum";
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
CreateCredentialResult,
|
||||
AssertCredentialParams,
|
||||
AssertCredentialResult,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
export enum MessageType {
|
||||
CredentialCreationRequest,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
import { Message, MessageType } from "./message";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
import { WebauthnUtils } from "../webauthn-utils";
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
CreateCredentialResult,
|
||||
AssertCredentialResult,
|
||||
} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { Fido2Utils } from "@bitwarden/common/vault/services/fido2/fido2-utils";
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
|
||||
|
||||
import {
|
||||
InsecureAssertCredentialParams,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula
|
|||
import { AbstractControl, FormBuilder, Validators } from "@angular/forms";
|
||||
import {
|
||||
combineLatest,
|
||||
from,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
|
@ -23,7 +22,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||
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 { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { CollectionResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { BitValidators, DialogService } from "@bitwarden/components";
|
||||
|
@ -56,7 +54,10 @@ export interface CollectionDialogParams {
|
|||
initialTab?: CollectionDialogTabType;
|
||||
parentCollectionId?: string;
|
||||
showOrgSelector?: boolean;
|
||||
collectionIds?: string[];
|
||||
/**
|
||||
* Flag to limit the nested collections to only those the user has explicit CanManage access too.
|
||||
*/
|
||||
limitNestedCollections?: boolean;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
|
@ -85,7 +86,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
protected tabIndex: CollectionDialogTabType;
|
||||
protected loading = true;
|
||||
protected organization?: Organization;
|
||||
protected collection?: CollectionView;
|
||||
protected collection?: CollectionAdminView;
|
||||
protected nestOptions: CollectionView[] = [];
|
||||
protected accessItems: AccessItemView[] = [];
|
||||
protected deletedParentName: string | undefined;
|
||||
|
@ -107,7 +108,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
private organizationService: OrganizationService,
|
||||
private groupService: GroupService,
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private collectionService: CollectionService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
|
@ -124,7 +124,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
this.showOrgSelector = true;
|
||||
this.formGroup.controls.selectedOrg.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((id) => this.loadOrg(id, this.params.collectionIds));
|
||||
.subscribe((id) => this.loadOrg(id));
|
||||
this.organizations$ = this.organizationService.organizations$.pipe(
|
||||
first(),
|
||||
map((orgs) =>
|
||||
|
@ -138,11 +138,11 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
} else {
|
||||
// Opened from the org vault
|
||||
this.formGroup.patchValue({ selectedOrg: this.params.organizationId });
|
||||
await this.loadOrg(this.params.organizationId, this.params.collectionIds);
|
||||
await this.loadOrg(this.params.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async loadOrg(orgId: string, collectionIds: string[]) {
|
||||
async loadOrg(orgId: string) {
|
||||
const organization$ = this.organizationService
|
||||
.get$(orgId)
|
||||
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
|
@ -158,28 +158,14 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
combineLatest({
|
||||
organization: organization$,
|
||||
collections: this.collectionAdminService.getAll(orgId),
|
||||
collectionDetails: this.params.collectionId
|
||||
? from(this.collectionAdminService.get(orgId, this.params.collectionId))
|
||||
: of(null),
|
||||
groups: groups$,
|
||||
// Collection(s) needed to map readonlypermission for (potential) access selector disabled state
|
||||
users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }),
|
||||
collection: this.params.collectionId
|
||||
? this.collectionService.get(this.params.collectionId)
|
||||
: of(null),
|
||||
flexibleCollectionsV1: this.flexibleCollectionsV1Enabled$,
|
||||
})
|
||||
.pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$))
|
||||
.subscribe(
|
||||
({
|
||||
organization,
|
||||
collections,
|
||||
collectionDetails,
|
||||
groups,
|
||||
users,
|
||||
collection,
|
||||
flexibleCollectionsV1,
|
||||
}) => {
|
||||
({ organization, collections: allCollections, groups, users, flexibleCollectionsV1 }) => {
|
||||
this.organization = organization;
|
||||
this.accessItems = [].concat(
|
||||
groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)),
|
||||
|
@ -189,37 +175,48 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||
// Force change detection to update the access selector's items
|
||||
this.changeDetectorRef.detectChanges();
|
||||
|
||||
if (collectionIds) {
|
||||
collections = collections.filter((c) => collectionIds.includes(c.id));
|
||||
}
|
||||
this.nestOptions = this.params.limitNestedCollections
|
||||
? allCollections.filter((c) => c.manage)
|
||||
: allCollections;
|
||||
|
||||
if (this.params.collectionId) {
|
||||
this.collection = collections.find((c) => c.id === this.collectionId);
|
||||
this.nestOptions = collections.filter((c) => c.id !== this.collectionId);
|
||||
this.collection = allCollections.find((c) => c.id === this.collectionId);
|
||||
// Ensure we don't allow nesting the current collection within itself
|
||||
this.nestOptions = this.nestOptions.filter((c) => c.id !== this.collectionId);
|
||||
|
||||
if (!this.collection) {
|
||||
throw new Error("Could not find collection to edit.");
|
||||
}
|
||||
|
||||
const { name, parent } = parseName(this.collection);
|
||||
if (parent !== undefined && !this.nestOptions.find((c) => c.name === parent)) {
|
||||
this.deletedParentName = parent;
|
||||
// Parse the name to find its parent name
|
||||
const { name, parent: parentName } = parseName(this.collection);
|
||||
|
||||
// Determine if the user can see/select the parent collection
|
||||
if (parentName !== undefined) {
|
||||
if (
|
||||
this.organization.canViewAllCollections &&
|
||||
!allCollections.find((c) => c.name === parentName)
|
||||
) {
|
||||
// The user can view all collections, but the parent was not found -> assume it has been deleted
|
||||
this.deletedParentName = parentName;
|
||||
} else if (!this.nestOptions.find((c) => c.name === parentName)) {
|
||||
// We cannot find the current parent collection in our list of options, so add a placeholder
|
||||
this.nestOptions.unshift({ name: parentName } as CollectionView);
|
||||
}
|
||||
}
|
||||
|
||||
const accessSelections = mapToAccessSelections(collectionDetails);
|
||||
const accessSelections = mapToAccessSelections(this.collection);
|
||||
this.formGroup.patchValue({
|
||||
name,
|
||||
externalId: this.collection.externalId,
|
||||
parent,
|
||||
parent: parentName,
|
||||
access: accessSelections,
|
||||
});
|
||||
this.collection.manage = collection?.manage ?? false; // Get manage flag from sync data collection
|
||||
this.showDeleteButton =
|
||||
!this.dialogReadonly &&
|
||||
this.collection.canDelete(organization, flexibleCollectionsV1);
|
||||
} else {
|
||||
this.nestOptions = collections;
|
||||
const parent = collections.find((c) => c.id === this.params.parentCollectionId);
|
||||
const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId);
|
||||
const currentOrgUserId = users.data.find(
|
||||
(u) => u.userId === this.organization?.userId,
|
||||
)?.id;
|
||||
|
|
|
@ -650,7 +650,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
.sort(Utils.getSortFunction(this.i18nService, "name"))[0].id,
|
||||
parentCollectionId: this.filter.collectionId,
|
||||
showOrgSelector: true,
|
||||
collectionIds: this.allCollections.map((c) => c.id),
|
||||
limitNestedCollections: true,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
|
@ -666,7 +666,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
|
||||
async editCollection(c: CollectionView, tab: CollectionDialogTabType): Promise<void> {
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: { collectionId: c?.id, organizationId: c.organizationId, initialTab: tab },
|
||||
data: {
|
||||
collectionId: c?.id,
|
||||
organizationId: c.organizationId,
|
||||
initialTab: tab,
|
||||
limitNestedCollections: true,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|||
import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
import { CollectionDialogTabType } from "../components/collection-dialog";
|
||||
|
||||
const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="10 -10 120 140" fill="none">
|
||||
<rect class="tw-stroke-secondary-600" width="134" height="86" x="3" y="31.485" stroke-width="6" rx="11"/>
|
||||
|
@ -16,24 +17,36 @@ const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="120" height=
|
|||
template: `<bit-no-items [icon]="icon" class="tw-mt-2 tw-block">
|
||||
<span slot="title" class="tw-mt-4 tw-block">{{ "collectionAccessRestricted" | i18n }}</span>
|
||||
<button
|
||||
*ngIf="canEditCollection"
|
||||
slot="button"
|
||||
bitButton
|
||||
(click)="viewCollectionClicked.emit()"
|
||||
(click)="viewCollectionClicked.emit({ readonly: false, tab: collectionDialogTabType.Info })"
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ buttonText | i18n }}
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ "editCollection" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!canEditCollection && canViewCollectionInfo"
|
||||
slot="button"
|
||||
bitButton
|
||||
(click)="viewCollectionClicked.emit({ readonly: true, tab: collectionDialogTabType.Access })"
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-users"></i> {{ "viewAccess" | i18n }}
|
||||
</button>
|
||||
</bit-no-items>`,
|
||||
})
|
||||
export class CollectionAccessRestrictedComponent {
|
||||
protected icon = icon;
|
||||
protected collectionDialogTabType = CollectionDialogTabType;
|
||||
|
||||
@Input() canEditCollection = false;
|
||||
@Input() canViewCollectionInfo = false;
|
||||
|
||||
@Output() viewCollectionClicked = new EventEmitter<void>();
|
||||
|
||||
get buttonText() {
|
||||
return this.canEditCollection ? "editCollection" : "viewCollection";
|
||||
}
|
||||
@Output() viewCollectionClicked = new EventEmitter<{
|
||||
readonly: boolean;
|
||||
tab: CollectionDialogTabType;
|
||||
}>();
|
||||
}
|
||||
|
|
|
@ -116,14 +116,18 @@
|
|||
</bit-no-items>
|
||||
<collection-access-restricted
|
||||
*ngIf="showCollectionAccessRestricted"
|
||||
[canEditCollection]="organization.isProviderUser"
|
||||
(viewCollectionClicked)="
|
||||
editCollection(
|
||||
selectedCollection.node,
|
||||
CollectionDialogTabType.Info,
|
||||
!organization.isProviderUser
|
||||
[canEditCollection]="
|
||||
selectedCollection?.node?.canEdit(organization, flexibleCollectionsV1Enabled)
|
||||
"
|
||||
[canViewCollectionInfo]="
|
||||
selectedCollection?.node?.canViewCollectionInfo(
|
||||
organization,
|
||||
flexibleCollectionsV1Enabled
|
||||
)
|
||||
"
|
||||
(viewCollectionClicked)="
|
||||
editCollection(selectedCollection.node, $event.tab, $event.readonly)
|
||||
"
|
||||
>
|
||||
</collection-access-restricted>
|
||||
</ng-container>
|
||||
|
|
|
@ -1175,6 +1175,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
data: {
|
||||
organizationId: this.organization?.id,
|
||||
parentCollectionId: this.selectedCollection?.node.id,
|
||||
limitNestedCollections: !this.organization.canEditAnyCollection(
|
||||
this.flexibleCollectionsV1Enabled,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1198,6 +1201,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
organizationId: this.organization?.id,
|
||||
initialTab: tab,
|
||||
readonly: readonly,
|
||||
limitNestedCollections: !this.organization.canEditAnyCollection(
|
||||
this.flexibleCollectionsV1Enabled,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -7749,9 +7749,6 @@
|
|||
"success": {
|
||||
"message": "Success"
|
||||
},
|
||||
"viewCollection": {
|
||||
"message": "View collection"
|
||||
},
|
||||
"restrictedGroupAccess": {
|
||||
"message": "You cannot add yourself to groups."
|
||||
},
|
||||
|
@ -8198,5 +8195,26 @@
|
|||
},
|
||||
"viewAccess": {
|
||||
"message": "View access"
|
||||
},
|
||||
"updateName": {
|
||||
"message": "Update name"
|
||||
},
|
||||
"updatedOrganizationName": {
|
||||
"message": "Updated organization name"
|
||||
},
|
||||
"providerPlan": {
|
||||
"message": "Managed Service Provider"
|
||||
},
|
||||
"orgSeats": {
|
||||
"message": "Organization Seats"
|
||||
},
|
||||
"providerDiscount": {
|
||||
"message": "$AMOUNT$% Discount",
|
||||
"placeholders": {
|
||||
"amount": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
|||
import { ProviderSubscriptionComponent } from "../../billing/providers";
|
||||
import {
|
||||
CreateClientOrganizationComponent,
|
||||
ManageClientOrganizationSubscriptionComponent,
|
||||
ManageClientOrganizationsComponent,
|
||||
ManageClientOrganizationNameComponent,
|
||||
ManageClientOrganizationSubscriptionComponent,
|
||||
} from "../../billing/providers/clients";
|
||||
|
||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
|
@ -62,6 +63,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||
UserAddEditComponent,
|
||||
CreateClientOrganizationComponent,
|
||||
ManageClientOrganizationsComponent,
|
||||
ManageClientOrganizationNameComponent,
|
||||
ManageClientOrganizationSubscriptionComponent,
|
||||
ProviderSubscriptionComponent,
|
||||
],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./create-client-organization.component";
|
||||
export * from "./manage-client-organizations.component";
|
||||
export * from "./manage-client-organization-name.component";
|
||||
export * from "./manage-client-organization-subscription.component";
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle class="tw-font-semibold">
|
||||
{{ "updateName" | i18n }}
|
||||
<small class="tw-text-muted">{{ dialogParams.organization.name }}</small>
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<bit-form-field>
|
||||
<bit-label>
|
||||
{{ "organizationName" | i18n }}
|
||||
</bit-label>
|
||||
<input type="text" bitInput formControlName="name" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton bitFormButton buttonType="primary" type="submit">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
|
@ -0,0 +1,77 @@
|
|||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
type ManageClientOrganizationNameParams = {
|
||||
providerId: string;
|
||||
organization: {
|
||||
id: string;
|
||||
name: string;
|
||||
seats: number;
|
||||
};
|
||||
};
|
||||
|
||||
export enum ManageClientOrganizationNameResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
}
|
||||
|
||||
export const openManageClientOrganizationNameDialog = (
|
||||
dialogService: DialogService,
|
||||
dialogConfig: DialogConfig<ManageClientOrganizationNameParams>,
|
||||
) =>
|
||||
dialogService.open<ManageClientOrganizationNameResultType, ManageClientOrganizationNameParams>(
|
||||
ManageClientOrganizationNameComponent,
|
||||
dialogConfig,
|
||||
);
|
||||
|
||||
@Component({
|
||||
selector: "app-manage-client-organization-name",
|
||||
templateUrl: "manage-client-organization-name.component.html",
|
||||
})
|
||||
export class ManageClientOrganizationNameComponent {
|
||||
protected ResultType = ManageClientOrganizationNameResultType;
|
||||
protected formGroup = this.formBuilder.group({
|
||||
name: [this.dialogParams.organization.name, Validators.required],
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected dialogParams: ManageClientOrganizationNameParams,
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private dialogRef: DialogRef<ManageClientOrganizationNameResultType>,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new UpdateClientOrganizationRequest();
|
||||
request.assignedSeats = this.dialogParams.organization.seats;
|
||||
request.name = this.formGroup.value.name;
|
||||
|
||||
await this.billingApiService.updateClientOrganization(
|
||||
this.dialogParams.providerId,
|
||||
this.dialogParams.organization.id,
|
||||
request,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("updatedOrganizationName"),
|
||||
});
|
||||
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
};
|
||||
}
|
|
@ -71,6 +71,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
|
|||
|
||||
const request = new UpdateClientOrganizationRequest();
|
||||
request.assignedSeats = assignedSeats;
|
||||
request.name = this.clientName;
|
||||
|
||||
await this.billingApiService.updateClientOrganization(
|
||||
this.providerId,
|
||||
|
|
|
@ -78,8 +78,12 @@
|
|||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<bit-menu #rowMenu>
|
||||
<button type="button" bitMenuItem (click)="manageName(client)">
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i>
|
||||
{{ "updateName" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="manageSubscription(client)">
|
||||
<i aria-hidden="true" class="bwi bwi-question-circle"></i>
|
||||
<i aria-hidden="true" class="bwi bwi-family"></i>
|
||||
{{ "manageSubscription" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(client)">
|
||||
|
|
|
@ -24,6 +24,10 @@ import {
|
|||
CreateClientOrganizationResultType,
|
||||
openCreateClientOrganizationDialog,
|
||||
} from "./create-client-organization.component";
|
||||
import {
|
||||
ManageClientOrganizationNameResultType,
|
||||
openManageClientOrganizationNameDialog,
|
||||
} from "./manage-client-organization-name.component";
|
||||
import { ManageClientOrganizationSubscriptionComponent } from "./manage-client-organization-subscription.component";
|
||||
|
||||
@Component({
|
||||
|
@ -106,6 +110,25 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||
this.loading = false;
|
||||
}
|
||||
|
||||
async manageName(organization: ProviderOrganizationOrganizationDetailsResponse) {
|
||||
const dialogRef = openManageClientOrganizationNameDialog(this.dialogService, {
|
||||
data: {
|
||||
providerId: this.providerId,
|
||||
organization: {
|
||||
id: organization.id,
|
||||
name: organization.organizationName,
|
||||
seats: organization.seats,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(dialogRef.closed);
|
||||
|
||||
if (result === ManageClientOrganizationNameResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
async manageSubscription(organization: ProviderOrganizationOrganizationDetailsResponse) {
|
||||
if (organization == null) {
|
||||
return;
|
||||
|
@ -135,4 +158,6 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||
|
||||
await this.load();
|
||||
};
|
||||
protected readonly openManageClientOrganizationNameDialog =
|
||||
openManageClientOrganizationNameDialog;
|
||||
}
|
||||
|
|
|
@ -1 +1,83 @@
|
|||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="subscription && firstLoaded">
|
||||
<bit-callout type="warning" title="{{ 'canceled' | i18n }}" *ngIf="false">
|
||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
||||
>
|
||||
|
||||
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||
<dd>{{ "providerPlan" | i18n }}</dd>
|
||||
<ng-container *ngIf="subscription">
|
||||
<dt>{{ "status" | i18n }}</dt>
|
||||
<dd>
|
||||
<span class="tw-capitalize">{{ subscription.status }}</span>
|
||||
</dd>
|
||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">{{ "nextCharge" | i18n }}</dt>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ subscription.currentPeriodEndDate | date: "mediumDate" }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="tw-flex-col">
|
||||
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 pb-2"
|
||||
>{{ "details" | i18n }}  <span
|
||||
bitBadge
|
||||
variant="success"
|
||||
*ngIf="subscription.discountPercentage"
|
||||
>{{ "providerDiscount" | i18n: subscription.discountPercentage }}</span
|
||||
>
|
||||
</strong>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<ng-container *ngIf="subscription">
|
||||
<tr bitRow *ngFor="let i of subscription.plans">
|
||||
<td bitCell class="tw-pl-0 tw-py-3">
|
||||
{{ getFormattedPlanName(i.planName) }} {{ "orgSeats" | i18n }} ({{
|
||||
i.cadence.toLowerCase()
|
||||
}}) {{ "×" }}{{ getFormattedSeatCount(i.seatMinimum, i.purchasedSeats) }}
|
||||
@
|
||||
{{
|
||||
getFormattedCost(
|
||||
i.cost,
|
||||
i.seatMinimum,
|
||||
i.purchasedSeats,
|
||||
subscription.discountPercentage
|
||||
) | currency: "$"
|
||||
}}
|
||||
</td>
|
||||
<td bitCell class="tw-text-right tw-py-3">
|
||||
{{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{
|
||||
"month" | i18n
|
||||
}}
|
||||
<div>
|
||||
<bit-hint class="tw-text-sm tw-line-through">
|
||||
{{ i.cost | currency: "$" }} /{{ "month" | i18n }}
|
||||
</bit-hint>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr bitRow>
|
||||
<td bitCell class="tw-pl-0 tw-py-3"></td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span class="tw-font-bold">Total:</span> {{ totalCost | currency: "$" }} /{{
|
||||
"month" | i18n
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
|
|
@ -1,7 +1,86 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Subject, concatMap, takeUntil } from "rxjs";
|
||||
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import {
|
||||
Plans,
|
||||
ProviderSubscriptionResponse,
|
||||
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||
|
||||
@Component({
|
||||
selector: "app-provider-subscription",
|
||||
templateUrl: "./provider-subscription.component.html",
|
||||
})
|
||||
export class ProviderSubscriptionComponent {}
|
||||
export class ProviderSubscriptionComponent {
|
||||
subscription: ProviderSubscriptionResponse;
|
||||
providerId: string;
|
||||
firstLoaded = false;
|
||||
loading: boolean;
|
||||
private destroy$ = new Subject<void>();
|
||||
totalCost: number;
|
||||
currentDate = new Date();
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.params
|
||||
.pipe(
|
||||
concatMap(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
get isExpired() {
|
||||
return this.subscription.status !== "active";
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.subscription = await this.billingApiService.getProviderSubscription(this.providerId);
|
||||
this.totalCost =
|
||||
((100 - this.subscription.discountPercentage) / 100) * this.sumCost(this.subscription.plans);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
getFormattedCost(
|
||||
cost: number,
|
||||
seatMinimum: number,
|
||||
purchasedSeats: number,
|
||||
discountPercentage: number,
|
||||
): number {
|
||||
const costPerSeat = cost / (seatMinimum + purchasedSeats);
|
||||
const discountedCost = costPerSeat - (costPerSeat * discountPercentage) / 100;
|
||||
return discountedCost;
|
||||
}
|
||||
|
||||
getFormattedPlanName(planName: string): string {
|
||||
const spaceIndex = planName.indexOf(" ");
|
||||
return planName.substring(0, spaceIndex);
|
||||
}
|
||||
|
||||
getFormattedSeatCount(seatMinimum: number, purchasedSeats: number): string {
|
||||
const totalSeats = seatMinimum + purchasedSeats;
|
||||
return totalSeats > 1 ? totalSeats.toString() : "";
|
||||
}
|
||||
|
||||
sumCost(plans: Plans[]): number {
|
||||
return plans.reduce((acc, plan) => acc + plan.cost, 0);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export class UpdateClientOrganizationRequest {
|
||||
assignedSeats: number;
|
||||
name: string;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,14 @@ import { TextEncoder } from "util";
|
|||
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { CipherService } from "../../abstractions/cipher.service";
|
||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
||||
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../../vault/enums/cipher-type";
|
||||
import { Cipher } from "../../../vault/models/domain/cipher";
|
||||
import { CipherView } from "../../../vault/models/view/cipher.view";
|
||||
import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view";
|
||||
import { LoginView } from "../../../vault/models/view/login.view";
|
||||
import {
|
||||
Fido2AuthenticatorErrorCode,
|
||||
Fido2AuthenticatorGetAssertionParams,
|
||||
|
@ -14,13 +20,7 @@ import {
|
|||
Fido2UserInterfaceSession,
|
||||
NewCredentialParams,
|
||||
} from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { SyncService } from "../../abstractions/sync/sync.service.abstraction";
|
||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../enums/cipher-type";
|
||||
import { Cipher } from "../../models/domain/cipher";
|
||||
import { CipherView } from "../../models/view/cipher.view";
|
||||
import { Fido2CredentialView } from "../../models/view/fido2-credential.view";
|
||||
import { LoginView } from "../../models/view/login.view";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { CBOR } from "./cbor";
|
||||
import { AAGUID, Fido2AuthenticatorService } from "./fido2-authenticator.service";
|
|
@ -1,6 +1,9 @@
|
|||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { CipherService } from "../../abstractions/cipher.service";
|
||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
||||
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../../vault/enums/cipher-type";
|
||||
import { CipherView } from "../../../vault/models/view/cipher.view";
|
||||
import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view";
|
||||
import {
|
||||
Fido2AlgorithmIdentifier,
|
||||
Fido2AuthenticatorError,
|
||||
|
@ -13,11 +16,8 @@ import {
|
|||
PublicKeyCredentialDescriptor,
|
||||
} from "../../abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { SyncService } from "../../abstractions/sync/sync.service.abstraction";
|
||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../enums/cipher-type";
|
||||
import { CipherView } from "../../models/view/cipher.view";
|
||||
import { Fido2CredentialView } from "../../models/view/fido2-credential.view";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { CBOR } from "./cbor";
|
||||
import { p1363ToDer } from "./ecdsa-utils";
|
|
@ -4,8 +4,8 @@ import { of } from "rxjs";
|
|||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
|
||||
import { ConfigService } from "../../../platform/abstractions/config/config.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { ConfigService } from "../../abstractions/config/config.service";
|
||||
import {
|
||||
Fido2AuthenticatorError,
|
||||
Fido2AuthenticatorErrorCode,
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
CreateCredentialParams,
|
||||
FallbackRequestedError,
|
||||
} from "../../abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { VaultSettingsService } from "../../abstractions/vault-settings/vault-settings.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { Fido2AuthenticatorService } from "./fido2-authenticator.service";
|
||||
import { Fido2ClientService } from "./fido2-client.service";
|
|
@ -4,9 +4,8 @@ import { parse } from "tldts";
|
|||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
|
||||
import { ConfigService } from "../../../platform/abstractions/config/config.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { ConfigService } from "../../abstractions/config/config.service";
|
||||
import {
|
||||
Fido2AuthenticatorError,
|
||||
Fido2AuthenticatorErrorCode,
|
||||
|
@ -26,7 +25,8 @@ import {
|
|||
UserRequestedFallbackAbortReason,
|
||||
UserVerification,
|
||||
} from "../../abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { VaultSettingsService } from "../../abstractions/vault-settings/vault-settings.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { isValidRpId } from "./domain-utils";
|
||||
import { Fido2Utils } from "./fido2-utils";
|
Loading…
Reference in New Issue