1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 19:53:43 +00:00

[SG-998] and [SG-999] Vault and Autofill team refactor (#4542)

* Move DeprecatedVaultFilterService to vault folder

* [libs] move VaultItemsComponent

* [libs] move AddEditComponent

* [libs] move AddEditCustomFields

* [libs] move attachmentsComponent

* [libs] folderAddEditComponent

* [libs] IconComponent

* [libs] PasswordRepormptComponent

* [libs] PremiumComponent

* [libs] ViewCustomFieldsComponent

* [libs] ViewComponent

* [libs] PasswordRepromptService

* [libs] Move FolderService and FolderApiService abstractions

* [libs] FolderService imports

* [libs] PasswordHistoryComponent

* [libs] move Sync and SyncNotifier abstractions

* [libs] SyncService imports

* [libs] fix file casing for passwordReprompt abstraction

* [libs] SyncNotifier import fix

* [libs] CipherServiceAbstraction

* [libs] PasswordRepromptService abstraction

* [libs] Fix file casing for angular passwordReprompt service

* [libs] fix file casing for SyncNotifierService

* [libs] CipherRepromptType

* [libs] rename CipherRepromptType

* [libs] CipherType

* [libs] Rename CipherType

* [libs] CipherData

* [libs] FolderData

* [libs] PasswordHistoryData

* [libs] AttachmentData

* [libs] CardData

* [libs] FieldData

* [libs] IdentityData

* [libs] LocalData

* [libs] LoginData

* [libs] SecureNoteData

* [libs] LoginUriData

* [libs] Domain classes

* [libs] SecureNote

* [libs] Request models

* [libs] Response models

* [libs] View part 1

* [libs] Views part 2

* [libs] Move folder services

* [libs] Views fixes

* [libs] Move sync services

* [libs] cipher service

* [libs] Types

* [libs] Sync file casing

* [libs] Fix folder service import

* [libs] Move spec files

* [libs] casing fixes on spec files

* [browser] Autofill background, clipboard, commands

* [browser] Fix ContextMenusBackground casing

* [browser] Rename fix

* [browser] Autofill content

* [browser] autofill.js

* [libs] enpass importer spec fix

* [browser] autofill models

* [browser] autofill manifest path updates

* [browser] Autofill notification files

* [browser] autofill services

* [browser] Fix file casing

* [browser] Vault popup loose components

* [browser] Vault components

* [browser] Manifest fixes

* [browser] Vault services

* [cli] vault commands and models

* [browser] File capitilization fixes

* [desktop] Vault components and services

* [web] vault loose components

* [web] Vault components

* [browser] Fix misc-utils import

* [libs] Fix psono spec imports

* [fix] Add comments to address lint rules
This commit is contained in:
Robyn MacCallum
2023-01-31 16:08:37 -05:00
committed by GitHub
parent bf1df6ebf6
commit 7ebedbecfb
472 changed files with 1371 additions and 1328 deletions

View File

@@ -13,7 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Component({
selector: "app-set-password",

View File

@@ -11,8 +11,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstraction
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ProductType } from "@bitwarden/common/enums/productType";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { OrganizationPlansComponent } from "../../settings/organization-plans.component";

View File

@@ -10,7 +10,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Component({
selector: "app-update-temp-password",

View File

@@ -9,11 +9,9 @@ import Swal from "sweetalert2";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
@@ -23,8 +21,10 @@ import { InternalPolicyService } from "@bitwarden/common/abstractions/policy/pol
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PolicyListService, RouterService } from "./core";
import {

View File

@@ -1,53 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header">
<h1 class="modal-title" id="confirmUserTitle">
{{ "passwordConfirmation" | i18n }}
</h1>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ "passwordConfirmationDesc" | i18n }}
<div class="form-group">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="text-monospace form-control"
[(ngModel)]="masterPassword"
required
appAutofocus
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit">
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,8 +0,0 @@
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "@bitwarden/angular/components/password-reprompt.component";
@Component({
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@@ -1,19 +0,0 @@
import { Component } from "@angular/core";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
@Component({
selector: "app-premium-badge",
template: `
<button type="button" *appNotPremium bitBadge badgeType="success" (click)="premiumRequired()">
{{ "premium" | i18n }}
</button>
`,
})
export class PremiumBadgeComponent {
constructor(private messagingService: MessagingService) {}
premiumRequired() {
this.messagingService.send("premiumRequired");
}
}

View File

@@ -1,61 +0,0 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
import { BadgeModule, I18nMockService } from "@bitwarden/components";
import { PremiumBadgeComponent } from "./premium-badge.component";
class MockMessagingService implements MessagingService {
send(subscriber: string, arg?: any) {
alert("Clicked on badge");
}
}
class MockedStateService implements Partial<StateService> {
async getCanAccessPremium(options?: StorageOptions) {
return false;
}
}
export default {
title: "Web/Premium Badge",
component: PremiumBadgeComponent,
decorators: [
moduleMetadata({
imports: [JslibModule, BadgeModule],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
premium: "Premium",
});
},
},
{
provide: MessagingService,
useFactory: () => {
return new MockMessagingService();
},
},
{
provide: StateService,
useFactory: () => {
return new MockedStateService();
},
},
],
}),
],
} as Meta;
const Template: Story<PremiumBadgeComponent> = (args: PremiumBadgeComponent) => ({
props: args,
});
export const Primary = Template.bind({});
Primary.args = {};

View File

@@ -15,7 +15,6 @@ import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
@@ -23,6 +22,11 @@ import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.s
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { LoginService } from "@bitwarden/common/services/login.service";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
// TODO refine elsint rule for **/app/core/*
// eslint-disable-next-line no-restricted-imports
import { PasswordRepromptService } from "../../vault/app/core/password-reprompt.service";
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
import { EventService } from "./event.service";
@@ -30,7 +34,6 @@ import { HtmlStorageService } from "./html-storage.service";
import { I18nService } from "./i18n.service";
import { InitService } from "./init.service";
import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./password-reprompt.service";
import { PolicyListService } from "./policy-list.service";
import { RouterService } from "./router.service";
import { Account, GlobalState, StateService } from "./state";

View File

@@ -1,10 +0,0 @@
import { Injectable } from "@angular/core";
import { PasswordRepromptService as BasePasswordRepromptService } from "@bitwarden/angular/services/passwordReprompt.service";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
@Injectable()
export class PasswordRepromptService extends BasePasswordRepromptService {
component = PasswordRepromptComponent;
}

View File

@@ -13,12 +13,12 @@ import {
AbstractStorageService,
} from "@bitwarden/common/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { CollectionData } from "@bitwarden/common/models/data/collection.data";
import { FolderData } from "@bitwarden/common/models/data/folder.data";
import { SendData } from "@bitwarden/common/models/data/send.data";
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
import { StateService as BaseStateService } from "@bitwarden/common/services/state.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { FolderData } from "@bitwarden/common/vault/models/data/folder.data";
import { Account } from "./account";
import { GlobalState } from "./global-state";

View File

@@ -10,10 +10,10 @@ import {
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { Provider } from "@bitwarden/common/models/domain/provider";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Component({
selector: "app-navbar",

View File

@@ -9,9 +9,9 @@ import { mock, MockProxy } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { OrganizationPermissionsGuard } from "./org-permissions.guard";

View File

@@ -7,8 +7,8 @@ import {
OrganizationService,
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Injectable({
providedIn: "root",

View File

@@ -22,7 +22,6 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
@@ -34,6 +33,7 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
import { OrganizationKeysRequest } from "@bitwarden/common/models/request/organization-keys.request";
import { CollectionDetailsResponse } from "@bitwarden/common/models/response/collection.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import {
DialogService,
SimpleDialogCloseType,

View File

@@ -1,16 +1,16 @@
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { Verification } from "@bitwarden/common/types/verification";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
class CountBasedLocalizationKey {
singular: string;

View File

@@ -8,13 +8,13 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType";
import { PlanType } from "@bitwarden/common/enums/planType";
import { ProductType } from "@bitwarden/common/enums/productType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/models/request/organization/organization-sponsorship-redeem.request";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DeleteOrganizationComponent } from "../../organizations/settings";
import { OrganizationPlansComponent } from "../../settings/organization-plans.component";

View File

@@ -3,12 +3,12 @@ import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
// eslint-disable-next-line no-restricted-imports
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/pages/exposed-passwords-report.component";

View File

@@ -1,15 +1,15 @@
import { NgModule } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { ImportApiServiceAbstraction } from "@bitwarden/common/abstractions/import/import-api.service.abstraction";
import { ImportService as ImportServiceAbstraction } from "@bitwarden/common/abstractions/import/import.service.abstraction";
import { ImportApiService } from "@bitwarden/common/services/import/import-api.service";
import { ImportService } from "@bitwarden/common/services/import/import.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { LooseComponentsModule, SharedModule } from "../../../shared";

View File

@@ -8,7 +8,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ImportComponent } from "../../../tools/import-export/import.component";

View File

@@ -2,12 +2,12 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
// eslint-disable-next-line no-restricted-imports
import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../reports/pages/inactive-two-factor-report.component";

View File

@@ -2,13 +2,13 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
// eslint-disable-next-line no-restricted-imports
import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../reports/pages/reused-passwords-report.component";

View File

@@ -2,11 +2,11 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
// eslint-disable-next-line no-restricted-imports
import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../reports/pages/unsecured-websites-report.component";

View File

@@ -2,13 +2,13 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
// eslint-disable-next-line no-restricted-imports
import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../reports/pages/weak-passwords-report.component";

View File

@@ -9,11 +9,11 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { Utils } from "@bitwarden/common/misc/utils";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { Verification } from "@bitwarden/common/types/verification";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Component({
selector: "app-enroll-master-password-reset",

View File

@@ -2,30 +2,30 @@ import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { CipherCreateRequest } from "@bitwarden/common/models/request/cipher-create.request";
import { CipherRequest } from "@bitwarden/common/models/request/cipher.request";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherCreateRequest } from "@bitwarden/common/vault/models/request/cipher-create.request";
import { CipherRequest } from "@bitwarden/common/vault/models/request/cipher.request";
import { AddEditComponent as BaseAddEditComponent } from "../../vault/add-edit.component";
import { AddEditComponent as BaseAddEditComponent } from "../../../vault/app/vault/add-edit.component";
@Component({
selector: "app-org-vault-add-edit",
templateUrl: "../../vault/add-edit.component.html",
templateUrl: "../../../vault/app/vault/add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent {
originalCipher: Cipher = null;

View File

@@ -1,23 +1,23 @@
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { AttachmentView } from "@bitwarden/common/models/view/attachment.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { AttachmentsComponent as BaseAttachmentsComponent } from "../../vault/attachments.component";
import { AttachmentsComponent as BaseAttachmentsComponent } from "../../../vault/app/vault/attachments.component";
@Component({
selector: "app-org-vault-attachments",
templateUrl: "../../vault/attachments.component.html",
templateUrl: "../../../vault/app/vault/attachments.component.html",
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = false;

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
import { SharedModule } from "../../../shared";
import { PipesModule } from "../../../vault/pipes/pipes.module";
import { CollectionNameBadgeComponent } from "./collection-name.badge.component";

View File

@@ -1,21 +1,21 @@
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherCollectionsRequest } from "@bitwarden/common/models/request/cipher-collections.request";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherCollectionsRequest } from "@bitwarden/common/vault/models/request/cipher-collections.request";
import { CollectionsComponent as BaseCollectionsComponent } from "../../vault/collections.component";
import { CollectionsComponent as BaseCollectionsComponent } from "../../../vault/app/vault/collections.component";
@Component({
selector: "app-org-vault-collections",
templateUrl: "../../vault/collections.component.html",
templateUrl: "../../../vault/app/vault/collections.component.html",
})
export class CollectionsComponent extends BaseCollectionsComponent {
organization: Organization;

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
import { SharedModule } from "../../../shared";
import { PipesModule } from "../../../vault/pipes/pipes.module";
import { GroupNameBadgeComponent } from "./group-name-badge.component";

View File

@@ -5,16 +5,16 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../vault/vault-filter/components/vault-filter.component";
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/app/vault/vault-filter/components/vault-filter.component";
import {
VaultFilterList,
VaultFilterType,
} from "../../../vault/vault-filter/shared/models/vault-filter-section.type";
import { CollectionFilter } from "../../../vault/vault-filter/shared/models/vault-filter.type";
} from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter-section.type";
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
@Component({
selector: "app-organization-vault-filter",
templateUrl: "../../../vault/vault-filter/components/vault-filter.component.html",
templateUrl: "../../../../vault/app/vault/vault-filter/components/vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent implements OnInit, OnDestroy {
@Input() set organization(value: Organization) {

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilterSharedModule } from "../../../vault/vault-filter/shared/vault-filter-shared.module";
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilterSharedModule } from "../../../../vault/app/vault/vault-filter/shared/vault-filter-shared.module";
import { VaultFilterComponent } from "./vault-filter.component";
import { VaultFilterService } from "./vault-filter.service";

View File

@@ -1,9 +1,7 @@
import { Injectable, OnDestroy } from "@angular/core";
import { filter, map, Observable, ReplaySubject, Subject, switchMap, takeUntil } from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import {
canAccessVaultTab,
@@ -13,9 +11,11 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { VaultFilterService as BaseVaultFilterService } from "../../../vault/vault-filter/services/vault-filter.service";
import { CollectionFilter } from "../../../vault/vault-filter/shared/models/vault-filter.type";
import { VaultFilterService as BaseVaultFilterService } from "../../../../vault/app/vault/vault-filter/services/vault-filter.service";
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
import { CollectionAdminView } from "../../core";
import { CollectionAdminService } from "../../core/services/collection-admin.service";

View File

@@ -3,12 +3,10 @@ import { lastValueFrom } from "rxjs";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
@@ -16,20 +14,22 @@ import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import {
BulkDeleteDialogResult,
openBulkDeleteDialog,
} from "../../vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
import { VaultFilterService } from "../../vault/vault-filter/services/abstractions/vault-filter.service";
import { CollectionFilter } from "../../vault/vault-filter/shared/models/vault-filter.type";
} from "../../../vault/app/vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { CollectionFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
import {
VaultItemRow,
VaultItemsComponent as BaseVaultItemsComponent,
} from "../../vault/vault-items.component";
} from "../../../vault/app/vault/vault-items.component";
import { CollectionAdminView } from "../core";
import { GroupService } from "../core/services/group/group.service";
import {
@@ -42,7 +42,7 @@ const MaxCheckedCount = 500;
@Component({
selector: "app-org-vault-items",
templateUrl: "../../vault/vault-items.component.html",
templateUrl: "../../../vault/app/vault/vault-items.component.html",
})
export class VaultItemsComponent extends BaseVaultItemsComponent implements OnDestroy {
@Input() set initOrganization(value: Organization) {

View File

@@ -13,17 +13,17 @@ import { first, switchMap, takeUntil } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ProductType } from "@bitwarden/common/enums/productType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
DialogService,
SimpleDialogCloseType,
@@ -31,9 +31,9 @@ import {
SimpleDialogType,
} from "@bitwarden/components";
import { VaultFilterService } from "../../vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../vault/vault-filter/shared/models/vault-filter.model";
import { CollectionFilter } from "../../vault/vault-filter/shared/models/vault-filter.type";
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.model";
import { CollectionFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
import { CollectionAdminService } from "../core";
import { EntityEventsComponent } from "../manage/entity-events.component";
import {

View File

@@ -2,10 +2,10 @@ import { NgModule } from "@angular/core";
import { BreadcrumbsModule } from "@bitwarden/components";
import { OrganizationBadgeModule } from "../../../vault/app/vault/organization-badge/organization-badge.module";
import { PipesModule } from "../../../vault/app/vault/pipes/pipes.module";
import { LooseComponentsModule } from "../../shared/loose-components.module";
import { SharedModule } from "../../shared/shared.module";
import { OrganizationBadgeModule } from "../../vault/organization-badge/organization-badge.module";
import { PipesModule } from "../../vault/pipes/pipes.module";
import { CollectionBadgeModule } from "./collection-badge/collection-badge.module";
import { GroupBadgeModule } from "./group-badge/group-badge.module";

View File

@@ -6,6 +6,7 @@ import { LockGuard } from "@bitwarden/angular/guards/lock.guard";
import { UnauthGuard } from "@bitwarden/angular/guards/unauth.guard";
import { flagEnabled, Flags } from "../utils/flags";
import { VaultModule } from "../vault/app/vault/vault.module";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
@@ -45,7 +46,6 @@ import { SponsoredFamiliesComponent } from "./settings/sponsored-families.compon
import { SubscriptionRoutingModule } from "./settings/subscription-routing.module";
import { GeneratorComponent } from "./tools/generator.component";
import { ToolsComponent } from "./tools/tools.component";
import { VaultModule } from "./vault/vault.module";
const routes: Routes = [
{

View File

@@ -1,13 +1,14 @@
import { NgModule } from "@angular/core";
import { OrganizationBadgeModule } from "../vault/app/vault/organization-badge/organization-badge.module";
import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module";
import { LoginModule } from "./accounts/login/login.module";
import { TrialInitiationModule } from "./accounts/trial-initiation/trial-initiation.module";
import { OrganizationCreateModule } from "./organizations/create/organization-create.module";
import { OrganizationManageModule } from "./organizations/manage/organization-manage.module";
import { OrganizationUserModule } from "./organizations/users/organization-user.module";
import { LooseComponentsModule, SharedModule } from "./shared";
import { OrganizationBadgeModule } from "./vault/organization-badge/organization-badge.module";
import { VaultFilterModule } from "./vault/vault-filter/vault-filter.module";
@NgModule({
imports: [

View File

@@ -2,13 +2,13 @@ import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { AddEditComponent } from "../../../vault/app/vault/add-edit.component";
import { AddEditComponent as OrgAddEditComponent } from "../../organizations/vault/add-edit.component";
import { AddEditComponent } from "../../vault/add-edit.component";
@Directive()
export class CipherReportComponent {

View File

@@ -2,11 +2,11 @@ import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherReportComponent } from "./cipher-report.component";

View File

@@ -1,13 +1,13 @@
import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherReportComponent } from "./cipher-report.component";

View File

@@ -1,12 +1,12 @@
import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherReportComponent } from "./cipher-report.component";

View File

@@ -1,11 +1,11 @@
import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherReportComponent } from "./cipher-report.component";

View File

@@ -1,12 +1,12 @@
import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BadgeTypes } from "@bitwarden/components";
import { CipherReportComponent } from "./cipher-report.component";

View File

@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import { PremiumBadgeComponent } from "../../../components/premium-badge.component";
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
import { ReportVariant } from "../models/report-variant";

View File

@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import { PremiumBadgeComponent } from "../../../components/premium-badge.component";
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
import { reports } from "../../reports";
import { ReportVariant } from "../models/report-variant";

View File

@@ -4,9 +4,7 @@ import { firstValueFrom } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
@@ -19,17 +17,19 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SendService } from "@bitwarden/common/abstractions/send.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { EmergencyAccessStatusType } from "@bitwarden/common/enums/emergencyAccessStatusType";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { CipherWithIdRequest } from "@bitwarden/common/models/request/cipher-with-id.request";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/models/request/emergency-access-update.request";
import { FolderWithIdRequest } from "@bitwarden/common/models/request/folder-with-id.request";
import { PasswordRequest } from "@bitwarden/common/models/request/password.request";
import { SendWithIdRequest } from "@bitwarden/common/models/request/send-with-id.request";
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
@Component({
selector: "app-change-password",

View File

@@ -1,19 +1,19 @@
import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/components/attachments.component";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AttachmentView } from "@bitwarden/common/models/view/attachment.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
@Component({
selector: "emergency-access-attachments",
templateUrl: "../vault/attachments.component.html",
templateUrl: "../../vault/app/vault/attachments.component.html",
})
export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = true;

View File

@@ -3,13 +3,13 @@ import { ActivatedRoute, Router } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { EmergencyAccessViewResponse } from "@bitwarden/common/models/response/emergency-access.response";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { EmergencyAccessAttachmentsComponent } from "./emergency-access-attachments.component";
import { EmergencyAddEditComponent } from "./emergency-add-edit.component";

View File

@@ -1,27 +1,27 @@
import { Component } from "@angular/core";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { AddEditComponent as BaseAddEditComponent } from "../vault/add-edit.component";
import { AddEditComponent as BaseAddEditComponent } from "../../vault/app/vault/add-edit.component";
@Component({
selector: "app-org-vault-add-edit",
templateUrl: "../vault/add-edit.component.html",
templateUrl: "../../vault/app/vault/add-edit.component.html",
})
export class EmergencyAddEditComponent extends BaseAddEditComponent {
originalCipher: Cipher = null;

View File

@@ -20,7 +20,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstraction
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
import { PlanType } from "@bitwarden/common/enums/planType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
@@ -32,6 +31,7 @@ import { OrganizationKeysRequest } from "@bitwarden/common/models/request/organi
import { OrganizationUpgradeRequest } from "@bitwarden/common/models/request/organization-upgrade.request";
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/models/request/provider/provider-organization-create.request";
import { PlanResponse } from "@bitwarden/common/models/response/plan.response";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PaymentComponent } from "./payment.component";
import { TaxInfoComponent } from "./tax-info.component";

View File

@@ -7,8 +7,8 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PaymentComponent } from "./payment.component";
import { TaxInfoComponent } from "./tax-info.component";

View File

@@ -5,9 +5,9 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { Verification } from "@bitwarden/common/types/verification";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Component({
selector: "app-purge-vault",

View File

@@ -8,9 +8,9 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
interface RequestSponsorshipForm {
selectedSponsorshipOrgId: FormControl<string>;

View File

@@ -2,18 +2,18 @@ import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { CipherWithIdRequest } from "@bitwarden/common/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/models/request/folder-with-id.request";
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
import { CipherWithIdRequest } from "@bitwarden/common/vault//models/request/cipher-with-id.request";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
@Component({
selector: "app-update-key",

View File

@@ -1,5 +1,13 @@
import { NgModule } from "@angular/core";
import { PasswordRepromptComponent } from "../../../src/vault/app/components/password-reprompt.component";
import { PremiumBadgeComponent } from "../../vault/app/components/premium-badge.component";
import { AddEditCustomFieldsComponent } from "../../vault/app/vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../../vault/app/vault/add-edit.component";
import { AttachmentsComponent } from "../../vault/app/vault/attachments.component";
import { CollectionsComponent } from "../../vault/app/vault/collections.component";
import { FolderAddEditComponent } from "../../vault/app/vault/folder-add-edit.component";
import { ShareComponent } from "../../vault/app/vault/share.component";
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
@@ -18,8 +26,6 @@ import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.compon
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { PremiumBadgeComponent } from "../components/premium-badge.component";
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
import { UserVerificationPromptComponent } from "../components/user-verification-prompt.component";
import { UserVerificationComponent } from "../components/user-verification.component";
@@ -105,12 +111,6 @@ import { VerifyEmailComponent } from "../settings/verify-email.component";
import { GeneratorComponent } from "../tools/generator.component";
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
import { ToolsComponent } from "../tools/tools.component";
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../vault/add-edit.component";
import { AttachmentsComponent } from "../vault/attachments.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { ShareComponent } from "../vault/share.component";
import { SharedModule } from "./shared.module";

View File

@@ -1,15 +1,15 @@
import { NgModule } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { ImportApiServiceAbstraction } from "@bitwarden/common/abstractions/import/import-api.service.abstraction";
import { ImportService as ImportServiceAbstraction } from "@bitwarden/common/abstractions/import/import.service.abstraction";
import { ImportApiService } from "@bitwarden/common/services/import/import-api.service";
import { ImportService } from "@bitwarden/common/services/import/import.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { LooseComponentsModule, SharedModule } from "../../shared";

View File

@@ -10,10 +10,10 @@ import { ImportService } from "@bitwarden/common/abstractions/import/import.serv
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ImportOption, ImportType } from "@bitwarden/common/enums/importOptions";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { ImportError } from "@bitwarden/common/importers/import-error";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { FilePasswordPromptComponent } from "./file-password-prompt.component";

View File

@@ -1,176 +0,0 @@
<ng-container>
<h3 class="mt-4">{{ "customFields" | i18n }}</h3>
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div
role="group"
class="row"
cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
attr.aria-label="{{ f.name }}"
>
<div class="col-5 form-group">
<div class="d-flex">
<label for="fieldName{{ i }}">{{ "name" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/custom-fields/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<input
id="fieldName{{ i }}"
type="text"
name="Field.Name{{ i }}"
[(ngModel)]="f.name"
class="form-control"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-7 form-group">
<label for="fieldValue{{ i }}">{{ "value" | i18n }}</label>
<div class="d-flex align-items-center">
<!-- Text -->
<div class="input-group" *ngIf="f.type === fieldType.Text">
<input
id="fieldValue{{ i }}"
class="form-control"
type="text"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
attr.aria-describedby="fieldName{{ i }}"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(f.value, 'value', 'Field')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<!-- Hidden -->
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
<input
id="fieldValue{{ i }}"
type="{{ f.showValue ? 'text' : 'password' }}"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
class="form-control text-monospace"
appInputVerbatim
autocomplete="new-password"
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)"
[readonly]="!cipher.edit && editMode"
attr.aria-describedby="fieldName{{ i }}"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleFieldValue(f)"
[disabled]="!cipher.viewPassword && !f.newField"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !f.showValue, 'bwi-eye-slash': f.showValue }"
>
</i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(f.value, 'value', f.type === fieldType.Hidden ? 'H_Field' : 'Field')"
[disabled]="!cipher.viewPassword && !f.newField"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<!-- Linked -->
<div class="input-group" *ngIf="f.type === fieldType.Linked">
<select
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
class="form-control"
[(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
[disabled]="cipher.isDeleted || viewOnly"
attr.aria-describedby="fieldName{{ i }}"
>
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="flex-fill">
<!-- Boolean -->
<input
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
type="checkbox"
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean"
appTrueFalseValue
trueValue="true"
falseValue="false"
[disabled]="cipher.isDeleted || viewOnly"
attr.aria-describedby="fieldName{{ i }}"
/>
</div>
<button
type="button"
class="btn btn-link text-danger ml-2"
(click)="removeField(f)"
appA11yTitle="{{ 'remove' | i18n }}"
*ngIf="!cipher.isDeleted && !viewOnly && !(!cipher.edit && editMode)"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn btn-link text-muted cursor-move"
appA11yTitle="{{ 'dragToSort' | i18n }}"
*ngIf="!cipher.isDeleted && !viewOnly && !(!cipher.edit && editMode)"
>
<i class="bwi bwi-hamburger bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Add new custom field -->
<a
href="#"
appStopClick
(click)="addField()"
class="d-inline-block mb-2"
*ngIf="!cipher.isDeleted && !viewOnly && !(!cipher.edit && editMode)"
>
<i class="bwi bwi-plus-circle bwi-fw" aria-hidden="true"></i> {{ "newCustomField" | i18n }}
</a>
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly && !(!cipher.edit && editMode)">
<div class="col-5">
<label for="addFieldType" class="sr-only">{{ "type" | i18n }}</label>
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{ o.name }}</option>
<option
*ngIf="cipher.linkedFieldOptions != null"
[ngValue]="addFieldLinkedTypeOption.value"
>
{{ addFieldLinkedTypeOption.name }}
</option>
</select>
</div>
</div>
</ng-container>

View File

@@ -1,18 +0,0 @@
import { Component, Input } from "@angular/core";
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/components/add-edit-custom-fields.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@Component({
selector: "app-vault-add-edit-custom-fields",
templateUrl: "add-edit-custom-fields.component.html",
})
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
@Input() viewOnly: boolean;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) {
super(i18nService, eventCollectionService);
}
}

View File

@@ -1,979 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="cipherAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="modal-header">
<h1 class="modal-title" id="cipherAddEditTitle">{{ title }}</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="cipher">
<app-callout type="info" *ngIf="allowOwnershipAssignment() && !allowPersonal">
{{ "personalOwnershipPolicyInEffect" | i18n }}
</app-callout>
<div class="row" *ngIf="!editMode && !viewOnly">
<div class="col-6 form-group">
<label for="type">{{ "whatTypeOfItem" | i18n }}</label>
<select
id="type"
name="Type"
[(ngModel)]="cipher.type"
class="form-control"
[disabled]="cipher.isDeleted"
appAutofocus
>
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="cipher.name"
required
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group" *ngIf="!organization">
<label for="folder">{{ "folder" | i18n }}</label>
<select
id="folder"
name="FolderId"
[(ngModel)]="cipher.folderId"
class="form-control"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select>
</div>
</div>
<!-- Login -->
<ng-container *ngIf="cipher.type === cipherType.Login">
<div class="row">
<div class="col-6 form-group">
<label for="loginUsername">{{ "username" | i18n }}</label>
<div class="input-group">
<input
id="loginUsername"
class="form-control"
type="text"
name="Login.Username"
[(ngModel)]="cipher.login.username"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
<div class="input-group-append" *ngIf="!cipher.isDeleted">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher.login.username, 'username', 'Username')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="col-6 form-group">
<div class="d-flex">
<label for="loginPassword">{{ "password" | i18n }}</label>
<div class="ml-auto d-flex" *ngIf="!cipher.isDeleted && !viewOnly">
<a
href="#"
class="d-block mr-2 bwi-icon-above-input"
appStopClick
appA11yTitle="{{ 'generatePassword' | i18n }}"
(click)="generatePassword()"
*ngIf="cipher.viewPassword && !(!cipher.edit && editMode)"
>
<i class="bwi bwi-lg bwi-fw bwi-generate" aria-hidden="true"></i>
</a>
<a
href="#"
class="d-block bwi-icon-above-input"
#checkPasswordBtn
appStopClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
>
<i
class="bwi bwi-lg bwi-fw bwi-check-circle"
[hidden]="$any(checkPasswordBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-lg bwi-fw bwi-spinner bwi-spin"
aria-hidden="true"
[hidden]="!$any(checkPasswordBtn).loading"
title="{{ 'loading' | i18n }}"
></i>
</a>
<a
href="#"
class="d-block bwi-icon-above-input"
appStopClick
[appA11yTitle]="'toggleCharacterCount' | i18n"
(click)="togglePasswordCount()"
*ngIf="cipher.viewPassword"
>
<i class="bwi bwi-lg bwi-fw bwi-numbered-list" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="input-group">
<input
id="loginPassword"
class="form-control text-monospace"
type="{{ showPassword ? 'text' : 'password' }}"
name="Login.Password"
[(ngModel)]="cipher.login.password"
appInputVerbatim
autocomplete="new-password"
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[disabled]="!cipher.viewPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher.login.password, 'password', 'Password')"
[disabled]="!cipher.viewPassword"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<div *ngIf="showPasswordCount" class="tw-mb-4">
<label>{{ "passwordCharacterCount" | i18n }}</label>
<div class="tw-flex tw-justify-between">
<bit-color-password
[password]="cipher.login.password"
[showCount]="true"
></bit-color-password>
<button type="button" bitLink (click)="togglePasswordCount()">
{{ "hide" | i18n }}
</button>
</div>
</div>
<div class="tw-flex tw-flex-row">
<div class="tw-mb-4 tw-w-1/2">
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
id="loginTotp"
type="{{ cipher.viewPassword ? 'text' : 'password' }}"
name="Login.Totp"
class="form-control text-monospace"
[(ngModel)]="cipher.login.totp"
appInputVerbatim
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="tw-mb-4 tw-ml-4 tw-flex tw-w-1/2 tw-items-end" [ngClass]="{ low: totpLow }">
<div
class="totp tw-flex tw-flex-row tw-items-center"
*ngIf="!cipher.login.totp || !totpCode"
>
<span class="totp-countdown">
<span class="totp-sec tw-text-muted">15</span>
<svg>
<g>
<circle
class="totp-circle-muted inner"
r="12.6"
cy="16"
cx="16"
opacity="0.25"
[ngStyle]="{ 'stroke-dashoffset.px': 40 }"
></circle>
<circle
class="totp-circle-muted outer"
opacity="0.25"
r="14"
cy="16"
cx="16"
></circle>
</g>
</svg>
</span>
<span
class="totp-code tw-mr-3 tw-ml-2 tw-text-muted"
title="{{ 'verificationCodeTotp' | i18n }}"
>--- ---</span
>
<i class="bwi bwi-lg bwi-clone tw-text-muted" aria-hidden="true"></i>
</div>
<div class="tw-pb-2" *ngIf="!cipher.login.totp || !totpCode">
<app-premium-badge
*ngIf="!organization && !cipher.organizationId"
class="ml-3"
></app-premium-badge>
<a
href="#"
appStopClick
bitBadge
badgeType="primary"
class="tw-ml-4"
(click)="upgradeOrganization()"
*ngIf="
(organization && !organization.useTotp) ||
(!organization &&
!canAccessPremium &&
cipher.organizationId &&
!cipher.organizationUseTotp)
"
>
{{ "upgrade" | i18n }}
</a>
</div>
<div
*ngIf="cipher.login.totp && totpCode"
class="totp tw-flex tw-flex-row tw-items-center"
>
<span class="totp-countdown">
<span class="totp-sec">{{ totpSec }}</span>
<svg>
<g>
<circle
class="totp-circle inner"
r="12.6"
cy="16"
cx="16"
[ngStyle]="{ 'stroke-dashoffset.px': totpDash }"
></circle>
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
</g>
</svg>
</span>
<span
class="totp-code tw-mr-2 tw-ml-2 tw-mt-1"
title="{{ 'verificationCodeTotp' | i18n }}"
>{{ totpCodeFormatted }}</span
>
<button
type="button"
class="tw-items-center tw-border-none tw-bg-transparent tw-text-primary-500"
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<ng-container *ngIf="cipher.login.hasUris">
<div
role="group"
class="row"
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
attr.aria-label="{{ 'uriPosition' | i18n: i + 1 }}"
>
<div class="col-7 form-group">
<label for="loginUri{{ i }}">{{ "uriPosition" | i18n: i + 1 }}</label>
<div class="input-group">
<input
class="form-control"
id="loginUri{{ i }}"
type="text"
name="Login.Uris[{{ i }}].Uri"
[(ngModel)]="u.uri"
[disabled]="cipher.isDeleted || viewOnly"
placeholder="{{ 'ex' | i18n }} https://google.com"
appInputVerbatim
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launch(u)"
[disabled]="!u.canLaunch"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyUri' | i18n }}"
(click)="copy(u.uri, 'uri', 'URI')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="col-5 form-group">
<div class="d-flex">
<label for="loginUriMatch{{ i }}">
{{ "matchDetection" | i18n }}
</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/uri-match-detection/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="d-flex">
<select
class="form-control overflow-hidden"
id="loginUriMatch{{ i }}"
name="Login.Uris[{{ i }}].Match"
[(ngModel)]="u.match"
(change)="loginUriMatchChanged(u)"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<button
type="button"
class="btn btn-link text-danger ml-2"
(click)="removeUri(u)"
appA11yTitle="{{ 'remove' | i18n }}"
*ngIf="!cipher.isDeleted && !viewOnly"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</ng-container>
<a
href="#"
appStopClick
(click)="addUri()"
class="d-inline-block mb-3"
*ngIf="!cipher.isDeleted && !viewOnly && !(!cipher.edit && editMode)"
>
<i class="bwi bwi-plus-circle bwi-fw" aria-hidden="true"></i> {{ "newUri" | i18n }}
</a>
</ng-container>
<!-- Card -->
<ng-container *ngIf="cipher.type === cipherType.Card">
<div class="row">
<div class="col-6 form-group">
<label for="cardCardholderName">{{ "cardholderName" | i18n }}</label>
<input
id="cardCardholderName"
class="form-control"
type="text"
name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group">
<label for="cardBrand">{{ "brand" | i18n }}</label>
<span *ngIf="!(!cipher.edit && editMode); else readonlyCardBrand">
<select
id="cardBrand"
class="form-control"
name="Card.Brand"
[(ngModel)]="cipher.card.brand"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</span>
<ng-template #readonlyCardBrand>
<input
id="cardBrand"
class="form-control"
name="Card.Brand"
type="text"
[readonly]="true"
[value]="cipher.card.brand"
/>
</ng-template>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="cardNumber">{{ "number" | i18n }}</label>
<div class="input-group">
<input
id="cardNumber"
class="form-control text-monospace"
type="{{ showCardNumber ? 'text' : 'password' }}"
name="Card.Number"
[(ngModel)]="cipher.card.number"
appInputVerbatim
autocomplete="new-password"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardNumber()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{
'bwi-eye': !showCardNumber,
'bwi-eye-slash': showCardNumber
}"
></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher.card.number, 'number', 'Number')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="col form-group">
<label for="cardExpMonth">{{ "expirationMonth" | i18n }}</label>
<span *ngIf="!(!cipher.edit && editMode); else readonlyCardExpMonth">
<select
id="cardExpMonth"
class="form-control"
name="Card.ExpMonth"
[(ngModel)]="cipher.card.expMonth"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</span>
<ng-template #readonlyCardExpMonth>
<input
id="cardExpMonth"
class="form-control"
type="text"
name="Card.ExpMonth"
[readonly]="true"
[value]="getCardExpMonthDisplay()"
/>
</ng-template>
</div>
<div class="col form-group">
<label for="cardExpYear">{{ "expirationYear" | i18n }}</label>
<input
id="cardExpYear"
class="form-control"
type="text"
name="Card.ExpYear"
[(ngModel)]="cipher.card.expYear"
placeholder="{{ 'ex' | i18n }} 2019"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="cardCode">{{ "securityCode" | i18n }}</label>
<div class="input-group">
<input
id="cardCode"
class="form-control text-monospace"
type="{{ showCardCode ? 'text' : 'password' }}"
name="Card.Code"
[(ngModel)]="cipher.card.code"
appInputVerbatim
autocomplete="new-password"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardCode()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showCardCode, 'bwi-eye-slash': showCardCode }"
></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'securityCode' | i18n }}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
</ng-container>
<!-- Identity -->
<ng-container *ngIf="cipher.type === cipherType.Identity">
<div class="row">
<div class="col-4 form-group">
<label for="idTitle">{{ "title" | i18n }}</label>
<span *ngIf="!(!cipher.edit && editMode); else readonlyIdTitle">
<select
id="idTitle"
class="form-control"
name="Identity.Title"
[(ngModel)]="cipher.identity.title"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</span>
<ng-template #readonlyIdTitle>
<input
id="idTitle"
class="form-control"
name="Identity.Title"
type="text"
[readonly]="true"
[value]="cipher.identity.title"
/>
</ng-template>
</div>
</div>
<div class="row">
<div class="col-4 form-group">
<label for="idFirstName">{{ "firstName" | i18n }}</label>
<input
id="idFirstName"
class="form-control"
type="text"
name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-4 form-group">
<label for="idMiddleName">{{ "middleName" | i18n }}</label>
<input
id="idMiddleName"
class="form-control"
type="text"
name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-4 form-group">
<label for="idLastName">{{ "lastName" | i18n }}</label>
<input
id="idLastName"
class="form-control"
type="text"
name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-4 form-group">
<label for="idUsername">{{ "username" | i18n }}</label>
<input
id="idUsername"
class="form-control"
type="text"
name="Identity.Username"
[(ngModel)]="cipher.identity.username"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-4 form-group">
<label for="idCompany">{{ "company" | i18n }}</label>
<input
id="idCompany"
class="form-control"
type="text"
name="Identity.Company"
[(ngModel)]="cipher.identity.company"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-4 form-group">
<label for="idSsn">{{ "ssn" | i18n }}</label>
<input
id="idSsn"
class="form-control"
type="text"
name="Identity.SSN"
[(ngModel)]="cipher.identity.ssn"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-4 form-group">
<label for="idPassportNumber">{{ "passportNumber" | i18n }}</label>
<input
id="idPassportNumber"
class="form-control"
type="text"
name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-4 form-group">
<label for="idLicenseNumber">{{ "licenseNumber" | i18n }}</label>
<input
id="idLicenseNumber"
class="form-control"
type="text"
name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idEmail">{{ "email" | i18n }}</label>
<input
id="idEmail"
class="form-control"
type="text"
inputmode="email"
name="Identity.Email"
[(ngModel)]="cipher.identity.email"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group">
<label for="idPhone">{{ "phone" | i18n }}</label>
<input
id="idPhone"
class="form-control"
type="text"
inputmode="tel"
name="Identity.Phone"
[(ngModel)]="cipher.identity.phone"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idAddress1">{{ "address1" | i18n }}</label>
<input
id="idAddress1"
class="form-control"
type="text"
name="Identity.Address1"
[(ngModel)]="cipher.identity.address1"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group">
<label for="idAddress2">{{ "address2" | i18n }}</label>
<input
id="idAddress2"
class="form-control"
type="text"
name="Identity.Address2"
[(ngModel)]="cipher.identity.address2"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idAddress3">{{ "address3" | i18n }}</label>
<input
id="idAddress3"
class="form-control"
type="text"
name="Identity.Address3"
[(ngModel)]="cipher.identity.address3"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group">
<label for="idCity">{{ "cityTown" | i18n }}</label>
<input
id="idCity"
class="form-control"
type="text"
name="Identity.City"
[(ngModel)]="cipher.identity.city"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idState">{{ "stateProvince" | i18n }}</label>
<input
id="idState"
class="form-control"
type="text"
name="Identity.State"
[(ngModel)]="cipher.identity.state"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="col-6 form-group">
<label for="idPostalCode">{{ "zipPostalCode" | i18n }}</label>
<input
id="idPostalCode"
class="form-control"
type="text"
name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idCountry">{{ "country" | i18n }}</label>
<input
id="idCountry"
class="form-control"
type="text"
name="Identity.Country"
[(ngModel)]="cipher.identity.country"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
</div>
</div>
</ng-container>
<div class="form-group">
<label for="notes">{{ "notes" | i18n }}</label>
<textarea
id="notes"
name="Notes"
rows="6"
[(ngModel)]="cipher.notes"
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
class="form-control"
></textarea>
</div>
<app-vault-add-edit-custom-fields
*ngIf="!(!cipher.hasFields && !cipher.edit && editMode)"
[cipher]="cipher"
[thisCipherType]="cipher.type"
[viewOnly]="viewOnly"
[copy]="copy.bind(this)"
[editMode]="editMode"
></app-vault-add-edit-custom-fields>
<ng-container *ngIf="allowOwnershipAssignment()">
<h3 class="mt-4">{{ "ownership" | i18n }}</h3>
<div class="row">
<div class="col-5">
<label for="organizationId">{{ "whoOwnsThisItem" | i18n }}</label>
<select
id="organizationId"
class="form-control"
name="OrganizationId"
[(ngModel)]="cipher.organizationId"
(change)="organizationChanged()"
[disabled]="cipher.isDeleted || viewOnly"
>
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
</ng-container>
<ng-container *ngIf="(!editMode || cloneMode) && cipher.organizationId">
<h3 class="mt-4">{{ "collections" | i18n }}</h3>
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<ng-container *ngIf="collections && collections.length">
<div class="form-check" *ngFor="let c of collections; let i = index">
<input
class="form-check-input"
type="checkbox"
[(ngModel)]="$any(c).checked"
id="collection-{{ i }}"
name="Collection[{{ i }}].Checked"
[disabled]="cipher.isDeleted || viewOnly"
/>
<label class="form-check-label" for="collection-{{ i }}">{{ c.name }}</label>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="editMode">
<div class="small text-muted mt-4">
<div>
<b class="font-weight-semibold">{{ "dateUpdated" | i18n }}:</b>
{{ cipher.revisionDate | date: "medium" }}
</div>
<div *ngIf="cipher.creationDate">
<b class="font-weight-semibold">{{ "dateCreated" | i18n }}:</b>
{{ cipher.creationDate | date: "medium" }}
</div>
<div *ngIf="showRevisionDate">
<b class="font-weight-semibold">{{ "datePasswordUpdated" | i18n }}:</b>
{{ cipher.passwordRevisionDisplayDate | date: "medium" }}
</div>
<div *ngIf="hasPasswordHistory">
<b class="font-weight-semibold">{{ "passwordHistory" | i18n }}:</b>
<a href="#" appStopClick (click)="viewHistory()" title="{{ 'view' | i18n }}">
{{ cipher.passwordHistory.length }}
</a>
</div>
<div class="ml-3" *ngIf="viewingPasswordHistory">
<div *ngFor="let ph of cipher.passwordHistory">
{{ ph.lastUsedDate | date: "short" }} -
<bit-color-password [password]="ph.password"></bit-color-password>
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="canUseReprompt">
<h3 class="mt-4">{{ "options" | i18n }}</h3>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
[ngModel]="reprompt"
(change)="repromptChanged()"
id="passwordPrompt"
name="passwordPrompt"
[disabled]="cipher.isDeleted || viewOnly || (!cipher.edit && editMode)"
/>
<label class="form-check-label" for="passwordPrompt">{{
"passwordPrompt" | i18n
}}</label>
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/managing-items/#protect-individual-items"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading"
*ngIf="!viewOnly"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ (cipher?.isDeleted ? "restore" : "save") | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ (viewOnly ? "close" : "cancel") | i18n }}
</button>
<div class="ml-auto" *ngIf="cipher && !viewOnly">
<button
*ngIf="!organization && !cipher.isDeleted"
type="button"
(click)="toggleFavorite()"
class="btn btn-link"
appA11yTitle="{{ (cipher.favorite ? 'unfavorite' : 'favorite') | i18n }}"
>
<i
class="bwi bwi-lg"
[ngClass]="{ 'bwi-star-f': cipher.favorite, 'bwi-star': !cipher.favorite }"
aria-hidden="true"
></i>
</button>
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ (cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n }}"
*ngIf="editMode && !cloneMode && !(!cipher.edit && editMode)"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,256 +0,0 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/components/add-edit.component";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { EventType } from "@bitwarden/common/enums/eventType";
import { LoginUriView } from "@bitwarden/common/models/view/login-uri.view";
@Component({
selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnDestroy {
canAccessPremium: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
totpSec: number;
totpLow: boolean;
showRevisionDate = false;
hasPasswordHistory = false;
viewingPasswordHistory = false;
viewOnly = false;
showPasswordCount = false;
protected totpInterval: number;
protected override componentName = "app-vault-add-edit";
constructor(
cipherService: CipherService,
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
stateService: StateService,
collectionService: CollectionService,
protected totpService: TotpService,
protected passwordGenerationService: PasswordGenerationService,
protected messagingService: MessagingService,
eventCollectionService: EventCollectionService,
protected policyService: PolicyService,
organizationService: OrganizationService,
logService: LogService,
passwordRepromptService: PasswordRepromptService
) {
super(
cipherService,
folderService,
i18nService,
platformUtilsService,
auditService,
stateService,
collectionService,
messagingService,
eventCollectionService,
policyService,
logService,
passwordRepromptService,
organizationService
);
}
async ngOnInit() {
await super.ngOnInit();
await this.load();
this.showRevisionDate = this.cipher.passwordRevisionDisplayDate != null;
this.hasPasswordHistory = this.cipher.hasPasswordHistory;
this.cleanUp();
this.canAccessPremium = await this.stateService.getCanAccessPremium();
if (
this.cipher.type === CipherType.Login &&
this.cipher.login.totp &&
(this.cipher.organizationUseTotp || this.canAccessPremium)
) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
this.totpInterval = window.setInterval(async () => {
await this.totpTick(interval);
}, 1000);
}
}
ngOnDestroy() {
super.ngOnDestroy();
}
toggleFavorite() {
this.cipher.favorite = !this.cipher.favorite;
}
togglePassword() {
super.togglePassword();
// Hide password count when password is hidden to be safe
if (!this.showPassword && this.showPasswordCount) {
this.togglePasswordCount();
}
}
togglePasswordCount() {
this.showPasswordCount = !this.showPasswordCount;
if (this.editMode && this.showPasswordCount) {
this.eventCollectionService.collect(
EventType.Cipher_ClientToggledPasswordVisible,
this.cipherId
);
}
}
launch(uri: LoginUriView) {
if (!uri.canLaunch) {
return;
}
this.platformUtilsService.launchUri(uri.launchUri);
}
copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
);
if (this.editMode) {
if (typeI18nKey === "password") {
this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,
this.cipherId
);
} else if (typeI18nKey === "securityCode") {
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === "H_Field") {
this.eventCollectionService.collect(
EventType.Cipher_ClientCopiedHiddenField,
this.cipherId
);
}
}
}
async generatePassword(): Promise<boolean> {
const confirmed = await super.generatePassword();
if (confirmed) {
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
}
return confirmed;
}
premiumRequired() {
if (!this.canAccessPremium) {
this.messagingService.send("premiumRequired");
return;
}
}
upgradeOrganization() {
this.messagingService.send("upgradeOrganization", {
organizationId: this.cipher.organizationId,
});
}
showGetPremium() {
if (this.canAccessPremium) {
return;
}
if (this.cipher.organizationUseTotp) {
this.upgradeOrganization();
} else {
this.premiumRequired();
}
}
viewHistory() {
this.viewingPasswordHistory = !this.viewingPasswordHistory;
}
protected cleanUp() {
if (this.totpInterval) {
window.clearInterval(this.totpInterval);
}
}
protected async totpUpdateCode() {
if (
this.cipher == null ||
this.cipher.type !== CipherType.Login ||
this.cipher.login.totp == null
) {
if (this.totpInterval) {
window.clearInterval(this.totpInterval);
}
return;
}
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
if (this.totpCode != null) {
if (this.totpCode.length > 4) {
const half = Math.floor(this.totpCode.length / 2);
this.totpCodeFormatted =
this.totpCode.substring(0, half) + " " + this.totpCode.substring(half);
} else {
this.totpCodeFormatted = this.totpCode;
}
} else {
this.totpCodeFormatted = null;
if (this.totpInterval) {
window.clearInterval(this.totpInterval);
}
}
}
protected allowOwnershipAssignment() {
return (
(!this.editMode || this.cloneMode) &&
this.ownershipOptions != null &&
(this.ownershipOptions.length > 1 || !this.allowPersonal)
);
}
private async totpTick(intervalSeconds: number) {
const epoch = Math.round(new Date().getTime() / 1000.0);
const mod = epoch % intervalSeconds;
this.totpSec = intervalSeconds - mod;
this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2");
this.totpLow = this.totpSec <= 7;
if (mod === 0) {
await this.totpUpdateCode();
}
}
}

View File

@@ -1,120 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="attachmentsTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h1 class="modal-title" id="attachmentsTitle">
{{ "attachments" | i18n }}
<small *ngIf="cipher">{{ cipher.name }}</small>
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<table class="table table-hover table-list" *ngIf="cipher && cipher.hasAttachments">
<tbody>
<tr *ngFor="let a of cipher.attachments">
<td class="table-list-icon">
<i
class="bwi bwi-fw bwi-lg bwi-file"
*ngIf="!$any(a).downloading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-lg bwi-fw bwi-spin"
*ngIf="$any(a).downloading"
aria-hidden="true"
></i>
</td>
<td class="wrap">
<div class="d-flex">
<a href="#" appStopClick (click)="download(a)">{{ a.fileName }}</a>
<div *ngIf="showFixOldAttachments(a)" class="ml-2">
<a
href="https://bitwarden.com/help/attachments/#fixing-old-attachments"
target="_blank"
rel="noopener"
>
<i
class="bwi bwi-exclamation-triangle text-warning"
title="{{ 'attachmentFixDesc' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachmentFixDesc" | i18n }}</span></a
>
<button
type="button"
class="btn btn-outline-primary btn-sm m-0 py-0 px-2"
(click)="reupload(a)"
#reuploadBtn
[appApiAction]="reuploadPromises[a.id]"
[disabled]="$any(reuploadBtn).loading"
>
{{ "fix" | i18n }}
</button>
</div>
</div>
<small>{{ a.sizeName }}</small>
</td>
<td class="table-list-options" *ngIf="!viewOnly">
<button
class="btn btn-outline-danger"
type="button"
appStopClick
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(a)"
#deleteBtn
[appApiAction]="deletePromises[a.id]"
[disabled]="$any(deleteBtn).loading"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
</td>
</tr>
</tbody>
</table>
<div *ngIf="!viewOnly">
<h3>{{ "newAttachment" | i18n }}</h3>
<label for="file" class="sr-only">{{ "file" | i18n }}</label>
<input type="file" id="file" class="form-control-file" name="file" required />
<small class="form-text text-muted">{{ "maxFileSize" | i18n }}</small>
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading"
*ngIf="!viewOnly"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,54 +0,0 @@
import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/components/attachments.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AttachmentView } from "@bitwarden/common/models/view/attachment.view";
@Component({
selector: "app-vault-attachments",
templateUrl: "attachments.component.html",
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = false;
protected override componentName = "app-vault-attachments";
constructor(
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
fileDownloadService: FileDownloadService
) {
super(
cipherService,
i18nService,
cryptoService,
platformUtilsService,
apiService,
window,
logService,
stateService,
fileDownloadService
);
}
protected async reupload(attachment: AttachmentView) {
if (this.showFixOldAttachments(attachment)) {
await this.reuploadCipherAttachment(attachment, false);
}
}
protected showFixOldAttachments(attachment: AttachmentView) {
return attachment.key == null && this.cipher.organizationId == null;
}
}

View File

@@ -1,25 +0,0 @@
<bit-simple-dialog>
<span bitDialogTitle>
{{ (permanent ? "permanentlyDeleteSelected" : "deleteSelected") | i18n }}
</span>
<span bitDialogContent>
<ng-container *ngIf="!permanent">
<span *ngIf="cipherIds?.length">
{{ "deleteSelectedItemsDesc" | i18n: cipherIds.length }}
</span>
<span *ngIf="collectionIds?.length">
{{ "deleteSelectedCollectionsDesc" | i18n: collectionIds.length }}
</span>
{{ "deleteSelectedConfirmation" | i18n }}
</ng-container>
<ng-container *ngIf="permanent">
{{ "permanentlyDeleteSelectedItemsDesc" | i18n: cipherIds.length }}
</ng-container>
</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<button bitButton type="submit" buttonType="danger" [bitAction]="submit">
{{ (permanent ? "permanentlyDelete" : "delete") | i18n }}
</button>
<button bitButton type="button" (click)="cancel()">{{ "cancel" | i18n }}</button>
</div>
</bit-simple-dialog>

View File

@@ -1,134 +0,0 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherBulkDeleteRequest } from "@bitwarden/common/models/request/cipher-bulk-delete.request";
import { CollectionBulkDeleteRequest } from "@bitwarden/common/models/request/collection-bulk-delete.request";
import { DialogService } from "@bitwarden/components";
export interface BulkDeleteDialogParams {
cipherIds?: string[];
collectionIds?: string[];
permanent?: boolean;
organization?: Organization;
}
export enum BulkDeleteDialogResult {
Deleted = "deleted",
Canceled = "canceled",
}
/**
* Strongly typed helper to open a BulkDeleteDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openBulkDeleteDialog = (
dialogService: DialogService,
config: DialogConfig<BulkDeleteDialogParams>
) => {
return dialogService.open<BulkDeleteDialogResult, BulkDeleteDialogParams>(
BulkDeleteDialogComponent,
config
);
};
@Component({
selector: "vault-bulk-delete-dialog",
templateUrl: "bulk-delete-dialog.component.html",
})
export class BulkDeleteDialogComponent {
cipherIds: string[];
collectionIds: string[];
permanent = false;
organization: Organization;
constructor(
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
private dialogRef: DialogRef<BulkDeleteDialogResult>,
private cipherService: CipherService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService
) {
this.cipherIds = params.cipherIds ?? [];
this.collectionIds = params.collectionIds ?? [];
this.permanent = params.permanent;
this.organization = params.organization;
}
protected async cancel() {
this.close(BulkDeleteDialogResult.Canceled);
}
protected submit = async () => {
const deletePromises: Promise<void>[] = [];
if (this.cipherIds.length) {
if (!this.organization || !this.organization.canEditAnyCollection) {
deletePromises.push(this.deleteCiphers());
} else {
deletePromises.push(this.deleteCiphersAdmin());
}
}
if (this.collectionIds.length && this.organization) {
deletePromises.push(this.deleteCollections());
}
await Promise.all(deletePromises);
if (this.cipherIds.length) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems")
);
}
if (this.collectionIds.length) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deletedCollections")
);
}
this.close(BulkDeleteDialogResult.Deleted);
};
private async deleteCiphers(): Promise<any> {
if (this.permanent) {
await this.cipherService.deleteManyWithServer(this.cipherIds);
} else {
await this.cipherService.softDeleteManyWithServer(this.cipherIds);
}
}
private async deleteCiphersAdmin(): Promise<any> {
const deleteRequest = new CipherBulkDeleteRequest(this.cipherIds, this.organization.id);
if (this.permanent) {
return await this.apiService.deleteManyCiphersAdmin(deleteRequest);
} else {
return await this.apiService.putDeleteManyCiphersAdmin(deleteRequest);
}
}
private async deleteCollections(): Promise<any> {
if (!this.organization.canDeleteAssignedCollections) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("missingPermissions")
);
return;
}
const deleteRequest = new CollectionBulkDeleteRequest(this.collectionIds, this.organization.id);
return await this.apiService.deleteManyCollections(deleteRequest);
}
private close(result: BulkDeleteDialogResult) {
this.dialogRef.close(result);
}
}

View File

@@ -1,25 +0,0 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../shared";
import { BulkDeleteDialogComponent } from "./bulk-delete-dialog/bulk-delete-dialog.component";
import { BulkMoveDialogComponent } from "./bulk-move-dialog/bulk-move-dialog.component";
import { BulkRestoreDialogComponent } from "./bulk-restore-dialog/bulk-restore-dialog.component";
import { BulkShareDialogComponent } from "./bulk-share-dialog/bulk-share-dialog.component";
@NgModule({
imports: [SharedModule],
declarations: [
BulkDeleteDialogComponent,
BulkMoveDialogComponent,
BulkRestoreDialogComponent,
BulkShareDialogComponent,
],
exports: [
BulkDeleteDialogComponent,
BulkMoveDialogComponent,
BulkRestoreDialogComponent,
BulkShareDialogComponent,
],
})
export class BulkDialogsModule {}

View File

@@ -1,24 +0,0 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="small">
<span bitDialogTitle>
{{ "moveSelected" | i18n }}
</span>
<span bitDialogContent>
<p>{{ "moveSelectedItemsDesc" | i18n: cipherIds.length }}</p>
<bit-form-field>
<bit-label for="folder">{{ "folder" | i18n }}</bit-label>
<select bitInput formControlName="folderId">
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select>
</bit-form-field>
</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<button bitButton bitFormButton type="submit" buttonType="primary">
{{ "save" | i18n }}
</button>
<button bitButton bitFormButton type="button" buttonType="secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</div>
</bit-dialog>
</form>

View File

@@ -1,85 +0,0 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { firstValueFrom, Observable } from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { DialogService } from "@bitwarden/components";
export interface BulkMoveDialogParams {
cipherIds?: string[];
}
export enum BulkMoveDialogResult {
Moved = "moved",
Canceled = "canceled",
}
/**
* Strongly typed helper to open a BulkMoveDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openBulkMoveDialog = (
dialogService: DialogService,
config: DialogConfig<BulkMoveDialogParams>
) => {
return dialogService.open<BulkMoveDialogResult, BulkMoveDialogParams>(
BulkMoveDialogComponent,
config
);
};
@Component({
selector: "vault-bulk-move-dialog",
templateUrl: "bulk-move-dialog.component.html",
})
export class BulkMoveDialogComponent implements OnInit {
cipherIds: string[] = [];
formGroup = this.formBuilder.group({
folderId: ["", [Validators.required]],
});
folders$: Observable<FolderView[]>;
constructor(
@Inject(DIALOG_DATA) params: BulkMoveDialogParams,
private dialogRef: DialogRef<BulkMoveDialogResult>,
private cipherService: CipherService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private folderService: FolderService,
private formBuilder: FormBuilder
) {
this.cipherIds = params.cipherIds ?? [];
}
async ngOnInit() {
this.folders$ = this.folderService.folderViews$;
this.formGroup.patchValue({
folderId: (await firstValueFrom(this.folders$))[0].id,
});
}
protected cancel() {
this.close(BulkMoveDialogResult.Canceled);
}
protected submit = async () => {
if (this.formGroup.invalid) {
return;
}
await this.cipherService.moveManyWithServer(this.cipherIds, this.formGroup.value.folderId);
this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems"));
this.close(BulkMoveDialogResult.Moved);
};
private close(result: BulkMoveDialogResult) {
this.dialogRef.close(result);
}
}

View File

@@ -1,14 +0,0 @@
<bit-simple-dialog>
<span bitDialogTitle>
{{ "restoreSelected" | i18n }}
</span>
<span bitDialogContent>
{{ "restoreSelectedItemsDesc" | i18n: cipherIds.length }}
</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<button bitButton type="submit" buttonType="primary" [bitAction]="submit">
{{ "restore" | i18n }}
</button>
<button bitButton type="button" (click)="cancel()">{{ "cancel" | i18n }}</button>
</div>
</bit-simple-dialog>

View File

@@ -1,63 +0,0 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { DialogService } from "@bitwarden/components";
export interface BulkRestoreDialogParams {
cipherIds: string[];
}
export enum BulkRestoreDialogResult {
Restored = "restored",
Canceled = "canceled",
}
/**
* Strongly typed helper to open a BulkRestoreDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openBulkRestoreDialog = (
dialogService: DialogService,
config: DialogConfig<BulkRestoreDialogParams>
) => {
return dialogService.open<BulkRestoreDialogResult, BulkRestoreDialogParams>(
BulkRestoreDialogComponent,
config
);
};
@Component({
selector: "vault-bulk-restore-dialog",
templateUrl: "bulk-restore-dialog.component.html",
})
export class BulkRestoreDialogComponent {
cipherIds: string[];
constructor(
@Inject(DIALOG_DATA) params: BulkRestoreDialogParams,
private dialogRef: DialogRef<BulkRestoreDialogResult>,
private cipherService: CipherService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {
this.cipherIds = params.cipherIds ?? [];
}
submit = async () => {
await this.cipherService.restoreManyWithServer(this.cipherIds);
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems"));
this.close(BulkRestoreDialogResult.Restored);
};
protected cancel() {
this.close(BulkRestoreDialogResult.Canceled);
}
private close(result: BulkRestoreDialogResult) {
this.dialogRef.close(result);
}
}

View File

@@ -1,73 +0,0 @@
<bit-dialog>
<span bitDialogTitle>
{{ "moveSelectedToOrg" | i18n }}
</span>
<span bitDialogContent>
<p>{{ "moveManyToOrgDesc" | i18n }}</p>
<p>
{{
"moveSelectedItemsCountDesc"
| i18n: this.ciphers.length:shareableCiphers.length:nonShareableCount
}}
</p>
<bit-form-field>
<bit-label for="organization">{{ "organization" | i18n }}</bit-label>
<select
bitInput
[(ngModel)]="organizationId"
id="organization"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select>
</bit-form-field>
<div class="d-flex">
<label class="tw-mb-1 tw-block tw-font-semibold tw-text-main">{{
"collections" | i18n
}}</label>
<div class="tw-ml-auto tw-flex tw-gap-2" *ngIf="collections && collections.length">
<button bitLink type="button" (click)="selectAll(true)" class="tw-px-2">
{{ "selectAll" | i18n }}
</button>
<button bitLink type="button" (click)="selectAll(false)" class="tw-px-2">
{{ "unselectAll" | i18n }}
</button>
</div>
</div>
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<table
class="table table-hover table-list mb-0"
*ngIf="collections && collections.length"
id="collections"
>
<tbody>
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
<td class="table-list-checkbox">
<input
bitInput
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
attr.aria-label="Check {{ c.name }}"
appStopProp
/>
</td>
<td>
{{ c.name }}
</td>
</tr>
</tbody>
</table>
</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<button bitButton type="submit" buttonType="primary" [bitAction]="submit">
{{ "save" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</div>
</bit-dialog>

View File

@@ -1,152 +0,0 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { Checkable, isChecked } from "@bitwarden/common/types/checkable";
import { DialogService } from "@bitwarden/components";
export interface BulkShareDialogParams {
ciphers: CipherView[];
organizationId?: string;
}
export enum BulkShareDialogResult {
Shared = "shared",
Canceled = "canceled",
}
/**
* Strongly typed helper to open a BulkShareDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openBulkShareDialog = (
dialogService: DialogService,
config: DialogConfig<BulkShareDialogParams>
) => {
return dialogService.open<BulkShareDialogResult, BulkShareDialogParams>(
BulkShareDialogComponent,
config
);
};
@Component({
selector: "vault-bulk-share-dialog",
templateUrl: "bulk-share-dialog.component.html",
})
export class BulkShareDialogComponent implements OnInit {
ciphers: CipherView[] = [];
organizationId: string;
nonShareableCount = 0;
collections: Checkable<CollectionView>[] = [];
organizations: Organization[] = [];
shareableCiphers: CipherView[] = [];
private writeableCollections: CollectionView[] = [];
constructor(
@Inject(DIALOG_DATA) params: BulkShareDialogParams,
private dialogRef: DialogRef<BulkShareDialogResult>,
private cipherService: CipherService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private collectionService: CollectionService,
private organizationService: OrganizationService,
private logService: LogService
) {
this.ciphers = params.ciphers ?? [];
this.organizationId = params.organizationId;
}
async ngOnInit() {
this.shareableCiphers = this.ciphers.filter(
(c) => !c.hasOldAttachments && c.organizationId == null
);
this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length;
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.filter((c) => !c.readOnly);
this.organizations = await this.organizationService.getAll();
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}
this.filterCollections();
}
ngOnDestroy() {
this.selectAll(false);
}
filterCollections() {
this.selectAll(false);
if (this.organizationId == null || this.writeableCollections.length === 0) {
this.collections = [];
} else {
this.collections = this.writeableCollections.filter(
(c) => c.organizationId === this.organizationId
);
}
}
submit = async () => {
const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id);
try {
await this.cipherService.shareManyWithServer(
this.shareableCiphers,
this.organizationId,
checkedCollectionIds
);
const orgName =
this.organizations.find((o) => o.id === this.organizationId)?.name ??
this.i18nService.t("organization");
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("movedItemsToOrg", orgName)
);
this.close(BulkShareDialogResult.Shared);
} catch (e) {
this.logService.error(e);
}
};
check(c: Checkable<CollectionView>, select?: boolean) {
c.checked = select == null ? !c.checked : select;
}
selectAll(select: boolean) {
const collections = select ? this.collections : this.writeableCollections;
collections.forEach((c) => this.check(c, select));
}
get canSave() {
if (
this.shareableCiphers != null &&
this.shareableCiphers.length > 0 &&
this.collections != null
) {
for (let i = 0; i < this.collections.length; i++) {
if (this.collections[i].checked) {
return true;
}
}
}
return false;
}
protected cancel() {
this.close(BulkShareDialogResult.Canceled);
}
private close(result: BulkShareDialogResult) {
this.dialogRef.close(result);
}
}

View File

@@ -1,63 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h1 class="modal-title" id="collectionsTitle">
{{ "collections" | i18n }}
<small *ngIf="cipher">{{ cipher.name }}</small>
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ "collectionsDesc" | i18n }}</p>
<div class="d-flex">
<h3>{{ "collections" | i18n }}</h3>
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{ "selectAll" | i18n }}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{ "unselectAll" | i18n }}
</button>
</div>
</div>
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
<tbody>
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
<td class="table-list-checkbox">
<input
type="checkbox"
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>
</td>
<td>
{{ c.name }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,37 +0,0 @@
import { Component, OnDestroy } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/components/collections.component";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
@Component({
selector: "app-vault-collections",
templateUrl: "collections.component.html",
})
export class CollectionsComponent extends BaseCollectionsComponent implements OnDestroy {
constructor(
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
cipherService: CipherService,
logService: LogService
) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
}
ngOnDestroy() {
this.selectAll(false);
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
}
selectAll(select: boolean) {
this.collections.forEach((c) => this.check(c, select));
}
}

View File

@@ -1,68 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h1 class="modal-title" id="folderAddEditTitle">{{ title }}</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="folder.name"
required
appAutofocus
/>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
<div class="ml-auto">
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,25 +0,0 @@
import { Component } from "@angular/core";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/components/folder-add-edit.component";
import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@Component({
selector: "app-folder-add-edit",
templateUrl: "folder-add-edit.component.html",
})
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
protected override componentName = "app-folder-add-edit";
constructor(
folderService: FolderService,
folderApiService: FolderApiServiceAbstraction,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(folderService, folderApiService, i18nService, platformUtilsService, logService);
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../shared";
import { OrganizationNameBadgeComponent } from "./organization-name-badge.component";
@NgModule({
imports: [SharedModule],
declarations: [OrganizationNameBadgeComponent],
exports: [OrganizationNameBadgeComponent],
})
export class OrganizationBadgeModule {}

View File

@@ -1,11 +0,0 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable @angular-eslint/template/button-has-type -->
<button
bitBadge
[style.color]="textColor"
[style.background-color]="color"
appA11yTitle="{{ organizationName }}"
(click)="emitOnOrganizationClicked()"
>
{{ organizationName | ellipsis: 13 }}
</button>

View File

@@ -1,49 +0,0 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { Utils } from "@bitwarden/common/misc/utils";
@Component({
selector: "app-org-badge",
templateUrl: "organization-name-badge.component.html",
})
export class OrganizationNameBadgeComponent implements OnInit {
@Input() organizationName: string;
@Input() profileName: string;
@Output() onOrganizationClicked = new EventEmitter<string>();
color: string;
textColor: string;
isMe: boolean;
constructor(
private i18nService: I18nService,
private avatarService: AvatarUpdateService,
private tokenService: TokenService
) {}
async ngOnInit(): Promise<void> {
if (this.organizationName == null || this.organizationName === "") {
this.organizationName = this.i18nService.t("me");
this.isMe = true;
}
if (this.isMe) {
this.color = await this.avatarService.loadColorFromState();
if (this.color == null) {
const userName =
(await this.tokenService.getName()) ?? (await this.tokenService.getEmail());
this.color = Utils.stringToColor(userName.toUpperCase());
}
} else {
this.color = Utils.stringToColor(this.organizationName.toUpperCase());
}
this.textColor = Utils.pickTextColorBasedOnBgColor(this.color, 135, true) + "!important";
}
emitOnOrganizationClicked() {
this.onOrganizationClicked.emit();
}
}

View File

@@ -1,13 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { CollectionView } from "@bitwarden/common/src/models/view/collection.view";
@Pipe({
name: "collectionNameFromId",
pure: true,
})
export class GetCollectionNameFromIdPipe implements PipeTransform {
transform(value: string, collections: CollectionView[]) {
return collections.find((o) => o.id === value)?.name;
}
}

View File

@@ -1,13 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { GroupView } from "../../organizations/core";
@Pipe({
name: "groupNameFromId",
pure: true,
})
export class GetGroupNameFromIdPipe implements PipeTransform {
transform(value: string, groups: GroupView[]) {
return groups.find((o) => o.id === value)?.name;
}
}

View File

@@ -1,14 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { Organization } from "@bitwarden/common/models/domain/organization";
@Pipe({
name: "orgNameFromId",
pure: true,
})
export class GetOrgNameFromIdPipe implements PipeTransform {
transform(value: string, organizations: Organization[]) {
const orgName = organizations.find((o) => o.id === value)?.name;
return orgName;
}
}

View File

@@ -1,11 +0,0 @@
import { NgModule } from "@angular/core";
import { GetCollectionNameFromIdPipe } from "./get-collection-name.pipe";
import { GetGroupNameFromIdPipe } from "./get-group-name.pipe";
import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe";
@NgModule({
declarations: [GetOrgNameFromIdPipe, GetCollectionNameFromIdPipe, GetGroupNameFromIdPipe],
exports: [GetOrgNameFromIdPipe, GetCollectionNameFromIdPipe, GetGroupNameFromIdPipe],
})
export class PipesModule {}

View File

@@ -1,101 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="shareTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h1 class="modal-title" id="shareTitle">
{{ "moveToOrganization" | i18n }}
<small *ngIf="cipher">{{ cipher.name }}</small>
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<ng-container *ngIf="organizations$ | async as organizations">
<div class="modal-body" *ngIf="!organizations || !organizations.length">
{{ "noOrganizationsList" | i18n }}
</div>
<div class="modal-body" *ngIf="organizations && organizations.length">
<p>{{ "moveToOrgDesc" | i18n }}</p>
<div class="form-group">
<label for="organization">{{ "organization" | i18n }}</label>
<select
id="organization"
name="OrganizationId"
[(ngModel)]="organizationId"
class="form-control"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select>
</div>
<div class="d-flex">
<h3>{{ "collections" | i18n }}</h3>
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{ "selectAll" | i18n }}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{ "unselectAll" | i18n }}
</button>
</div>
</div>
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<table
class="table table-hover table-list mb-0"
*ngIf="collections && collections.length"
>
<tbody>
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
<td class="table-list-checkbox">
<input
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>
</td>
<td>
{{ c.name }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit manual"
[disabled]="form.loading || !canSave"
[ngClass]="{ loading: form.loading }"
*ngIf="organizations && organizations.length"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "save" | i18n }}</span>
</button>
<a
href="#"
routerLink="/create-organization"
class="btn btn-primary"
*ngIf="!organizations || !organizations.length"
>
{{ "newOrganization" | i18n }}
</a>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</ng-container>
</form>
</div>
</div>

View File

@@ -1,47 +0,0 @@
import { Component, OnDestroy } from "@angular/core";
import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
@Component({
selector: "app-vault-share",
templateUrl: "share.component.html",
})
export class ShareComponent extends BaseShareComponent implements OnDestroy {
constructor(
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
cipherService: CipherService,
organizationService: OrganizationService,
logService: LogService
) {
super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
logService,
organizationService
);
}
ngOnDestroy() {
this.selectAll(false);
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
}
selectAll(select: boolean) {
const collections = select ? this.collections : this.writeableCollections;
collections.forEach((c) => this.check(c, select));
}
}

View File

@@ -1,9 +0,0 @@
<a
class="tw-block tw-cursor-pointer tw-border-none tw-bg-background tw-px-4 tw-py-2 tw-text-left !tw-text-main tw-no-underline hover:tw-bg-secondary-100 hover:tw-no-underline focus:tw-z-50 focus:tw-bg-secondary-100 focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2 active:!tw-ring-0 active:!tw-ring-offset-0"
href="#"
appStopClick
(click)="submit(returnUri, true)"
>
<i class="bwi bwi-fw bwi-link" aria-hidden="true"></i>
{{ "linkSso" | i18n }}
</a>

View File

@@ -1,59 +0,0 @@
import { AfterContentInit, Component, Input } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SsoComponent } from "@bitwarden/angular/components/sso.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
@Component({
selector: "app-link-sso",
templateUrl: "link-sso.component.html",
})
export class LinkSsoComponent extends SsoComponent implements AfterContentInit {
@Input() organization: Organization;
returnUri = "/settings/organizations";
constructor(
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
authService: AuthService,
router: Router,
route: ActivatedRoute,
cryptoFunctionService: CryptoFunctionService,
passwordGenerationService: PasswordGenerationService,
stateService: StateService,
environmentService: EnvironmentService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
this.returnUri = "/settings/organizations";
this.redirectUri = window.location.origin + "/sso-connector.html";
this.clientId = "web";
}
async ngAfterContentInit() {
this.identifier = this.organization.identifier;
}
}

View File

@@ -1,52 +0,0 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable @angular-eslint/template/button-has-type -->
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin text-muted tw-m-2"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<div
*ngIf="loaded"
class="tw-flex tw-min-w-[200px] tw-max-w-[300px] tw-flex-col"
[appApiAction]="actionPromise"
>
<button
*ngIf="allowEnrollmentChanges(organization) && !organization.resetPasswordEnrolled"
class="tw-block tw-cursor-pointer tw-border-none tw-bg-background tw-px-4 tw-py-2 tw-text-left !tw-text-main hover:tw-bg-secondary-100 focus:tw-z-50 focus:tw-bg-secondary-100 focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2 active:!tw-ring-0 active:!tw-ring-offset-0"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | i18n }}
</button>
<button
*ngIf="allowEnrollmentChanges(organization) && organization.resetPasswordEnrolled"
class="tw-block tw-cursor-pointer tw-border-none tw-bg-background tw-px-4 tw-py-2 tw-text-left !tw-text-main hover:tw-bg-secondary-100 focus:tw-z-50 focus:tw-bg-secondary-100 focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2 active:!tw-ring-0 active:!tw-ring-offset-0"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "withdrawPasswordReset" | i18n }}
</button>
<ng-container *ngIf="organization.useSso && organization.identifier">
<button
*ngIf="organization.ssoBound; else linkSso"
class="tw-block tw-cursor-pointer tw-border-none tw-bg-background tw-px-4 tw-py-2 tw-text-left !tw-text-main hover:tw-bg-secondary-100 focus:tw-z-50 focus:tw-bg-secondary-100 focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2 active:!tw-ring-0 active:!tw-ring-offset-0"
(click)="unlinkSso(organization)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
</button>
<ng-template #linkSso>
<app-link-sso [organization]="organization"> </app-link-sso>
</ng-template>
</ng-container>
<button
class="text-danger tw-block tw-cursor-pointer tw-border-none tw-bg-background tw-px-4 tw-py-2 tw-text-left hover:tw-bg-secondary-100 focus:tw-z-50 focus:tw-bg-secondary-100 focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2 active:!tw-ring-0 active:!tw-ring-offset-0"
(click)="leave(organization)"
>
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "leave" | i18n }}
</button>
</div>

View File

@@ -1,157 +0,0 @@
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { map, Subject, takeUntil } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { EnrollMasterPasswordReset } from "../../../organizations/users/enroll-master-password-reset.component";
import { OptionsInput } from "../shared/components/vault-filter-section.component";
import { OrganizationFilter } from "../shared/models/vault-filter.type";
@Component({
selector: "app-organization-options",
templateUrl: "organization-options.component.html",
})
export class OrganizationOptionsComponent implements OnInit, OnDestroy {
actionPromise: Promise<void | boolean>;
policies: Policy[];
loaded = false;
private destroy$ = new Subject<void>();
constructor(
@Inject(OptionsInput) protected organization: OrganizationFilter,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService,
private syncService: SyncService,
private policyService: PolicyService,
private modalService: ModalService,
private logService: LogService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService
) {}
async ngOnInit() {
this.policyService.policies$
.pipe(
map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)),
takeUntil(this.destroy$)
)
.subscribe((policies) => {
this.policies = policies;
this.loaded = true;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
allowEnrollmentChanges(org: OrganizationFilter): boolean {
if (org.usePolicies && org.useResetPassword && org.hasPublicAndPrivateKeys) {
const policy = this.policies.find((p) => p.organizationId === org.id);
if (policy != null && policy.enabled) {
return org.resetPasswordEnrolled && policy.data.autoEnrollEnabled ? false : true;
}
}
return false;
}
showEnrolledStatus(org: Organization): boolean {
return (
org.useResetPassword &&
org.resetPasswordEnrolled &&
this.policies.some((p) => p.organizationId === org.id && p.enabled)
);
}
async unlinkSso(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("unlinkSsoConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.apiService.deleteSsoUser(org.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, "Unlinked SSO");
} catch (e) {
this.logService.error(e);
}
}
async leave(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.organizationApiService.leave(org.id);
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
} catch (e) {
this.logService.error(e);
}
}
async toggleResetPasswordEnrollment(org: Organization) {
if (!this.organization.resetPasswordEnrolled) {
this.modalService.open(EnrollMasterPasswordReset, {
allowMultipleModals: true,
data: {
organization: org,
},
});
} else {
// Remove reset password
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.masterPasswordHash = "ignored";
request.resetPasswordKey = null;
this.actionPromise = this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
this.organization.id,
this.organization.userId,
request
);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("withdrawPasswordResetSuccess")
);
this.syncService.fullSync(true);
} catch (e) {
this.logService.error(e);
}
}
}
}

View File

@@ -1,35 +0,0 @@
<div class="card vault-filters">
<div class="container loading-spinner" *ngIf="!isLoaded">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="isLoaded">
<div class="card-header d-flex">
{{ "filters" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/searching-vault/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<input
type="search"
placeholder="{{ searchPlaceholder | i18n }}"
id="search"
class="form-control"
(input)="searchTextChanged($any($event.target).value)"
autocomplete="off"
appAutofocus
/>
<ng-container *ngFor="let f of filtersList">
<div class="filter">
<app-filter-section [activeFilter]="activeFilter" [section]="f"> </app-filter-section>
</div>
</ng-container>
</div>
</div>
</div>

View File

@@ -1,353 +0,0 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
import {
VaultFilterList,
VaultFilterSection,
VaultFilterType,
} from "../shared/models/vault-filter-section.type";
import { VaultFilter } from "../shared/models/vault-filter.model";
import {
CipherStatus,
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "../shared/models/vault-filter.type";
import { OrganizationOptionsComponent } from "./organization-options.component";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
})
export class VaultFilterComponent implements OnInit, OnDestroy {
filters?: VaultFilterList;
@Input() activeFilter: VaultFilter = new VaultFilter();
@Output() activeFilterChanged = new EventEmitter<VaultFilter>();
@Output() onSearchTextChanged = new EventEmitter<string>();
@Output() onAddFolder = new EventEmitter<never>();
@Output() onEditFolder = new EventEmitter<FolderFilter>();
isLoaded = false;
searchText = "";
protected destroy$: Subject<void> = new Subject<void>();
get filtersList() {
return this.filters ? Object.values(this.filters) : [];
}
get searchPlaceholder() {
if (this.activeFilter.isFavorites) {
return "searchFavorites";
}
if (this.activeFilter.isDeleted) {
return "searchTrash";
}
if (this.activeFilter.cipherType === CipherType.Login) {
return "searchLogin";
}
if (this.activeFilter.cipherType === CipherType.Card) {
return "searchCard";
}
if (this.activeFilter.cipherType === CipherType.Identity) {
return "searchIdentity";
}
if (this.activeFilter.cipherType === CipherType.SecureNote) {
return "searchSecureNote";
}
if (this.activeFilter.selectedFolderNode?.node) {
return "searchFolder";
}
if (this.activeFilter.selectedCollectionNode?.node) {
return "searchCollection";
}
if (this.activeFilter.organizationId === "MyVault") {
return "searchMyVault";
}
if (this.activeFilter.organizationId) {
return "searchOrganization";
}
return "searchVault";
}
constructor(
protected vaultFilterService: VaultFilterService,
protected policyService: PolicyService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService
) {
this.loadSubscriptions();
}
async ngOnInit(): Promise<void> {
this.filters = await this.buildAllFilters();
this.activeFilter.selectedCipherTypeNode =
(await this.getDefaultFilter()) as TreeNode<CipherTypeFilter>;
this.isLoaded = true;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
protected loadSubscriptions() {
this.vaultFilterService.filteredFolders$
.pipe(
switchMap(async (folders) => {
this.removeInvalidFolderSelection(folders);
}),
takeUntil(this.destroy$)
)
.subscribe();
this.vaultFilterService.filteredCollections$
.pipe(
switchMap(async (collections) => {
this.removeInvalidCollectionSelection(collections);
}),
takeUntil(this.destroy$)
)
.subscribe();
}
searchTextChanged(t: string) {
this.searchText = t;
this.onSearchTextChanged.emit(t);
}
protected applyVaultFilter(filter: VaultFilter) {
this.activeFilterChanged.emit(filter);
}
applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
if (!orgNode?.node.enabled) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("disabledOrganizationFilterError")
);
return;
}
const filter = this.activeFilter;
filter.resetOrganization();
if (orgNode?.node.id !== "AllVaults") {
filter.selectedOrganizationNode = orgNode;
}
this.vaultFilterService.setOrganizationFilter(orgNode.node);
await this.vaultFilterService.expandOrgFilter();
this.applyVaultFilter(filter);
};
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode = filterNode;
this.applyVaultFilter(filter);
};
applyFolderFilter = async (folderNode: TreeNode<FolderFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedFolderNode = folderNode;
this.applyVaultFilter(filter);
};
applyCollectionFilter = async (collectionNode: TreeNode<CollectionFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCollectionNode = collectionNode;
this.applyVaultFilter(filter);
};
addFolder = async (): Promise<void> => {
this.onAddFolder.emit();
};
editFolder = async (folder: FolderFilter): Promise<void> => {
this.onEditFolder.emit(folder);
};
async getDefaultFilter(): Promise<TreeNode<VaultFilterType>> {
return await firstValueFrom(this.filters?.typeFilter.data$);
}
protected async removeInvalidFolderSelection(folders: FolderView[]) {
if (this.activeFilter.selectedFolderNode) {
if (!folders.some((f) => f.id === this.activeFilter.folderId)) {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode =
(await this.getDefaultFilter()) as TreeNode<CipherTypeFilter>;
this.applyVaultFilter(filter);
}
}
}
protected async removeInvalidCollectionSelection(collections: CollectionView[]) {
if (this.activeFilter.selectedCollectionNode) {
if (!collections.some((f) => f.id === this.activeFilter.collectionId)) {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode =
(await this.getDefaultFilter()) as TreeNode<CipherTypeFilter>;
this.applyVaultFilter(filter);
}
}
}
async buildAllFilters(): Promise<VaultFilterList> {
const builderFilter = {} as VaultFilterList;
builderFilter.organizationFilter = await this.addOrganizationFilter();
builderFilter.typeFilter = await this.addTypeFilter();
builderFilter.folderFilter = await this.addFolderFilter();
builderFilter.collectionFilter = await this.addCollectionFilter();
builderFilter.trashFilter = await this.addTrashFilter();
return builderFilter;
}
protected async addOrganizationFilter(): Promise<VaultFilterSection> {
const singleOrgPolicy = await this.policyService.policyAppliesToUser(PolicyType.SingleOrg);
const personalVaultPolicy = await this.policyService.policyAppliesToUser(
PolicyType.PersonalOwnership
);
const addAction = !singleOrgPolicy
? { text: "newOrganization", route: "/create-organization" }
: null;
const orgFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.organizationTree$,
header: {
showHeader: !(singleOrgPolicy && personalVaultPolicy),
isSelectable: true,
},
action: this.applyOrganizationFilter,
options: { component: OrganizationOptionsComponent },
add: addAction,
divider: true,
};
return orgFilterSection;
}
protected async addTypeFilter(excludeTypes: CipherStatus[] = []): Promise<VaultFilterSection> {
const allTypeFilters: CipherTypeFilter[] = [
{
id: "favorites",
name: this.i18nService.t("favorites"),
type: "favorites",
icon: "bwi-star",
},
{
id: "login",
name: this.i18nService.t("typeLogin"),
type: CipherType.Login,
icon: "bwi-globe",
},
{
id: "card",
name: this.i18nService.t("typeCard"),
type: CipherType.Card,
icon: "bwi-credit-card",
},
{
id: "identity",
name: this.i18nService.t("typeIdentity"),
type: CipherType.Identity,
icon: "bwi-id-card",
},
{
id: "note",
name: this.i18nService.t("typeSecureNote"),
type: CipherType.SecureNote,
icon: "bwi-sticky-note",
},
];
const typeFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.buildTypeTree(
{ id: "AllItems", name: "allItems", type: "all", icon: "" },
allTypeFilters.filter((f) => !excludeTypes.includes(f.type))
),
header: {
showHeader: true,
isSelectable: true,
},
action: this.applyTypeFilter,
};
return typeFilterSection;
}
protected async addFolderFilter(): Promise<VaultFilterSection> {
const folderFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.folderTree$,
header: {
showHeader: true,
isSelectable: false,
},
action: this.applyFolderFilter,
edit: {
text: "editFolder",
action: this.editFolder,
},
add: {
text: "Add Folder",
action: this.addFolder,
},
};
return folderFilterSection;
}
protected async addCollectionFilter(): Promise<VaultFilterSection> {
const collectionFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.collectionTree$,
header: {
showHeader: true,
isSelectable: true,
},
action: this.applyCollectionFilter,
};
return collectionFilterSection;
}
protected async addTrashFilter(): Promise<VaultFilterSection> {
const trashFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.buildTypeTree(
{
id: "headTrash",
name: "HeadTrash",
type: "trash",
icon: "bwi-trash",
},
[
{
id: "trash",
name: this.i18nService.t("trash"),
type: "trash",
icon: "bwi-trash",
},
]
),
header: {
showHeader: false,
isSelectable: true,
},
action: this.applyTypeFilter,
};
return trashFilterSection;
}
}

View File

@@ -1,31 +0,0 @@
import { Observable } from "rxjs";
import { Organization } from "@bitwarden/common/src/models/domain/organization";
import { TreeNode } from "@bitwarden/common/src/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/src/models/view/collection.view";
import { FolderView } from "@bitwarden/common/src/models/view/folder.view";
import {
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "../../shared/models/vault-filter.type";
export abstract class VaultFilterService {
collapsedFilterNodes$: Observable<Set<string>>;
filteredFolders$: Observable<FolderView[]>;
filteredCollections$: Observable<CollectionView[]>;
organizationTree$: Observable<TreeNode<OrganizationFilter>>;
folderTree$: Observable<TreeNode<FolderFilter>>;
collectionTree$: Observable<TreeNode<CollectionFilter>>;
reloadCollections: () => Promise<void>;
getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
setCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
expandOrgFilter: () => Promise<void>;
setOrganizationFilter: (organization: Organization) => void;
buildTypeTree: (
head: CipherTypeFilter,
array: CipherTypeFilter[]
) => Observable<TreeNode<CipherTypeFilter>>;
}

View File

@@ -1,288 +0,0 @@
import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, ReplaySubject, take } from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { VaultFilterService } from "./vault-filter.service";
describe("vault filter service", () => {
let vaultFilterService: VaultFilterService;
let stateService: MockProxy<StateService>;
let organizationService: MockProxy<OrganizationService>;
let folderService: MockProxy<FolderService>;
let cipherService: MockProxy<CipherService>;
let collectionService: MockProxy<CollectionService>;
let policyService: MockProxy<PolicyService>;
let i18nService: MockProxy<I18nService>;
let organizations: ReplaySubject<Organization[]>;
let folderViews: ReplaySubject<FolderView[]>;
beforeEach(() => {
stateService = mock<StateService>();
organizationService = mock<OrganizationService>();
folderService = mock<FolderService>();
cipherService = mock<CipherService>();
collectionService = mock<CollectionService>();
policyService = mock<PolicyService>();
i18nService = mock<I18nService>();
i18nService.collator = new Intl.Collator("en-US");
organizations = new ReplaySubject<Organization[]>(1);
folderViews = new ReplaySubject<FolderView[]>(1);
organizationService.organizations$ = organizations;
folderService.folderViews$ = folderViews;
vaultFilterService = new VaultFilterService(
stateService,
organizationService,
folderService,
cipherService,
collectionService,
policyService,
i18nService
);
});
describe("collapsed filter nodes", () => {
const nodes = new Set(["1", "2"]);
it("updates observable when saving", (complete) => {
vaultFilterService.collapsedFilterNodes$.pipe(take(1)).subscribe((value) => {
if (value === nodes) {
complete();
}
});
vaultFilterService.setCollapsedFilterNodes(nodes);
});
it("loads from state on initialization", async () => {
stateService.getCollapsedGroupings.mockResolvedValue(["1", "2"]);
await expect(firstValueFrom(vaultFilterService.collapsedFilterNodes$)).resolves.toEqual(
nodes
);
});
});
describe("organizations", () => {
beforeEach(() => {
const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")];
organizations.next(storedOrgs);
});
it("returns a nested tree", async () => {
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
expect(tree.children.length).toBe(3);
expect(tree.children.find((o) => o.node.name === "org1"));
expect(tree.children.find((o) => o.node.name === "org2"));
});
it("hides My Vault if personal ownership policy is enabled", async () => {
policyService.policyAppliesToUser
.calledWith(PolicyType.PersonalOwnership)
.mockResolvedValue(true);
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
expect(tree.children.length).toBe(2);
expect(!tree.children.find((o) => o.node.id === "MyVault"));
});
it("returns 1 organization and My Vault if single organization policy is enabled", async () => {
policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true);
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
expect(tree.children.length).toBe(2);
expect(tree.children.find((o) => o.node.name === "org1"));
expect(tree.children.find((o) => o.node.id === "MyVault"));
});
it("returns 1 organization if both single organization and personal ownership policies are enabled", async () => {
policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true);
policyService.policyAppliesToUser
.calledWith(PolicyType.PersonalOwnership)
.mockResolvedValue(true);
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
expect(tree.children.length).toBe(1);
expect(tree.children.find((o) => o.node.name === "org1"));
});
});
describe("folders", () => {
describe("filtered folders with organization", () => {
beforeEach(() => {
// Org must be updated before folderService else the subscription uses the null org default value
vaultFilterService.setOrganizationFilter(createOrganization("org test id", "Test Org"));
});
it("returns folders filtered by current organization", async () => {
const storedCiphers = [
createCipherView("1", "org test id", "folder test id"),
createCipherView("2", "non matching org id", "non matching folder id"),
];
cipherService.getAllDecrypted.mockResolvedValue(storedCiphers);
const storedFolders = [
createFolderView("folder test id", "test"),
createFolderView("non matching folder id", "test2"),
];
folderViews.next(storedFolders);
await expect(firstValueFrom(vaultFilterService.filteredFolders$)).resolves.toEqual([
createFolderView("folder test id", "test"),
]);
});
});
describe("folder tree", () => {
it("returns a nested tree", async () => {
const storedFolders = [
createFolderView("Folder 1 Id", "Folder 1"),
createFolderView("Folder 2 Id", "Folder 1/Folder 2"),
createFolderView("Folder 3 Id", "Folder 1/Folder 3"),
];
folderViews.next(storedFolders);
const result = await firstValueFrom(vaultFilterService.folderTree$);
expect(result.children[0].node.id === "Folder 1 Id");
expect(result.children[0].children.find((c) => c.node.id === "Folder 2 Id"));
expect(result.children[0].children.find((c) => c.node.id === "Folder 3 Id"));
}, 10000);
});
});
describe("collections", () => {
describe("filtered collections", () => {
it("returns collections filtered by current organization", async () => {
vaultFilterService.setOrganizationFilter(createOrganization("org test id", "Test Org"));
const storedCollections = [
createCollectionView("1", "collection 1", "org test id"),
createCollectionView("2", "collection 2", "non matching org id"),
];
collectionService.getAllDecrypted.mockResolvedValue(storedCollections);
vaultFilterService.reloadCollections();
await expect(firstValueFrom(vaultFilterService.filteredCollections$)).resolves.toEqual([
createCollectionView("1", "collection 1", "org test id"),
]);
});
});
describe("collection tree", () => {
it("returns tree with children", async () => {
const storedCollections = [
createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-2", "Collection 1/Collection 2", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 3", "org test id"),
];
collectionService.getAllDecrypted.mockResolvedValue(storedCollections);
vaultFilterService.reloadCollections();
const result = await firstValueFrom(vaultFilterService.collectionTree$);
expect(result.children.map((c) => c.node.id)).toEqual(["id-1"]);
expect(result.children[0].children.map((c) => c.node.id)).toEqual(["id-2", "id-3"]);
});
it("returns tree where non-existing collections are excluded from children", async () => {
const storedCollections = [
createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
];
collectionService.getAllDecrypted.mockResolvedValue(storedCollections);
vaultFilterService.reloadCollections();
const result = await firstValueFrom(vaultFilterService.collectionTree$);
expect(result.children.map((c) => c.node.id)).toEqual(["id-1"]);
expect(result.children[0].children.map((c) => c.node.id)).toEqual(["id-3"]);
expect(result.children[0].children[0].node.name).toBe("Collection 2/Collection 3");
});
it("returns tree with parents", async () => {
const storedCollections = [
createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-2", "Collection 1/Collection 2", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
createCollectionView("id-4", "Collection 1/Collection 4", "org test id"),
];
collectionService.getAllDecrypted.mockResolvedValue(storedCollections);
vaultFilterService.reloadCollections();
const result = await firstValueFrom(vaultFilterService.collectionTree$);
const c1 = result.children[0];
const c2 = c1.children[0];
const c3 = c2.children[0];
const c4 = c1.children[1];
expect(c2.parent.node.id).toEqual("id-1");
expect(c3.parent.node.id).toEqual("id-2");
expect(c4.parent.node.id).toEqual("id-1");
});
it("returns tree where non-existing collections are excluded from parents", async () => {
const storedCollections = [
createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
];
collectionService.getAllDecrypted.mockResolvedValue(storedCollections);
vaultFilterService.reloadCollections();
const result = await firstValueFrom(vaultFilterService.collectionTree$);
const c1 = result.children[0];
const c3 = c1.children[0];
expect(c3.parent.node.id).toEqual("id-1");
});
});
});
function createOrganization(id: string, name: string) {
const org = new Organization();
org.id = id;
org.name = name;
org.identifier = name;
return org;
}
function createCipherView(id: string, orgId: string, folderId: string) {
const cipher = new CipherView();
cipher.id = id;
cipher.organizationId = orgId;
cipher.folderId = folderId;
return cipher;
}
function createFolderView(id: string, name: string): FolderView {
const folder = new FolderView();
folder.id = id;
folder.name = name;
return folder;
}
function createCollectionView(id: string, name: string, orgId: string): CollectionView {
const collection = new CollectionView();
collection.id = id;
collection.name = name;
collection.organizationId = orgId;
return collection;
}
});

View File

@@ -1,256 +0,0 @@
import { Injectable } from "@angular/core";
import {
BehaviorSubject,
combineLatestWith,
firstValueFrom,
map,
Observable,
of,
ReplaySubject,
switchMap,
} from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { CollectionAdminView } from "../../../organizations/core";
import {
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "../shared/models/vault-filter.type";
import { VaultFilterService as VaultFilterServiceAbstraction } from "./abstractions/vault-filter.service";
const NestingDelimiter = "/";
@Injectable()
export class VaultFilterService implements VaultFilterServiceAbstraction {
protected _collapsedFilterNodes = new BehaviorSubject<Set<string>>(null);
collapsedFilterNodes$: Observable<Set<string>> = this._collapsedFilterNodes.pipe(
switchMap(async (nodes) => nodes ?? (await this.getCollapsedFilterNodes()))
);
organizationTree$: Observable<TreeNode<OrganizationFilter>> =
this.organizationService.organizations$.pipe(
switchMap((orgs) => this.buildOrganizationTree(orgs))
);
protected _organizationFilter = new BehaviorSubject<Organization>(null);
filteredFolders$: Observable<FolderView[]> = this.folderService.folderViews$.pipe(
combineLatestWith(this._organizationFilter),
switchMap(([folders, org]) => {
return this.filterFolders(folders, org);
})
);
folderTree$: Observable<TreeNode<FolderFilter>> = this.filteredFolders$.pipe(
map((folders) => this.buildFolderTree(folders))
);
// TODO: Remove once collections is refactored with observables
// replace with collection service observable
private collectionViews$ = new ReplaySubject<CollectionView[]>(1);
filteredCollections$: Observable<CollectionView[]> = this.collectionViews$.pipe(
combineLatestWith(this._organizationFilter),
switchMap(([collections, org]) => {
return this.filterCollections(collections, org);
})
);
collectionTree$: Observable<TreeNode<CollectionFilter>> = this.filteredCollections$.pipe(
map((collections) => this.buildCollectionTree(collections))
);
constructor(
protected stateService: StateService,
protected organizationService: OrganizationService,
protected folderService: FolderService,
protected cipherService: CipherService,
protected collectionService: CollectionService,
protected policyService: PolicyService,
protected i18nService: I18nService
) {}
// TODO: Remove once collections is refactored with observables
async reloadCollections() {
this.collectionViews$.next(await this.collectionService.getAllDecrypted());
}
async getCollectionNodeFromTree(id: string) {
const collections = await firstValueFrom(this.collectionTree$);
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>;
}
async setCollapsedFilterNodes(collapsedFilterNodes: Set<string>): Promise<void> {
await this.stateService.setCollapsedGroupings(Array.from(collapsedFilterNodes));
this._collapsedFilterNodes.next(collapsedFilterNodes);
}
protected async getCollapsedFilterNodes(): Promise<Set<string>> {
const nodes = new Set(await this.stateService.getCollapsedGroupings());
return nodes;
}
setOrganizationFilter(organization: Organization) {
if (organization?.id != "AllVaults") {
this._organizationFilter.next(organization);
} else {
this._organizationFilter.next(null);
}
}
async expandOrgFilter() {
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
if (!collapsedFilterNodes.has("AllVaults")) {
return;
}
collapsedFilterNodes.delete("AllVaults");
await this.setCollapsedFilterNodes(collapsedFilterNodes);
}
protected async buildOrganizationTree(
orgs?: Organization[]
): Promise<TreeNode<OrganizationFilter>> {
const headNode = this.getOrganizationFilterHead();
if (!(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership))) {
const myVaultNode = this.getOrganizationFilterMyVault();
headNode.children.push(myVaultNode);
}
if (await this.policyService.policyAppliesToUser(PolicyType.SingleOrg)) {
orgs = orgs.slice(0, 1);
}
if (orgs) {
orgs.forEach((org) => {
const orgCopy = org as OrganizationFilter;
orgCopy.icon = "bwi-business";
const node = new TreeNode<OrganizationFilter>(orgCopy, headNode, orgCopy.name);
headNode.children.push(node);
});
}
return headNode;
}
protected getOrganizationFilterHead(): TreeNode<OrganizationFilter> {
const head = new Organization() as OrganizationFilter;
head.enabled = true;
return new TreeNode<OrganizationFilter>(head, null, "allVaults", "AllVaults");
}
protected getOrganizationFilterMyVault(): TreeNode<OrganizationFilter> {
const myVault = new Organization() as OrganizationFilter;
myVault.id = "MyVault";
myVault.icon = "bwi-user";
myVault.enabled = true;
myVault.hideOptions = true;
return new TreeNode<OrganizationFilter>(myVault, null, this.i18nService.t("myVault"));
}
buildTypeTree(
head: CipherTypeFilter,
array?: CipherTypeFilter[]
): Observable<TreeNode<CipherTypeFilter>> {
const headNode = new TreeNode<CipherTypeFilter>(head, null);
array?.forEach((filter) => {
const node = new TreeNode<CipherTypeFilter>(filter, headNode, filter.name);
headNode.children.push(node);
});
return of(headNode);
}
protected async filterCollections(
storedCollections: CollectionView[],
org?: Organization
): Promise<CollectionView[]> {
return org?.id != null
? storedCollections.filter((c) => c.organizationId === org.id)
: storedCollections;
}
protected buildCollectionTree(collections?: CollectionView[]): TreeNode<CollectionFilter> {
const headNode = this.getCollectionFilterHead();
if (!collections) {
return headNode;
}
const nodes: TreeNode<CollectionFilter>[] = [];
collections
.sort((a, b) => this.i18nService.collator.compare(a.name, b.name))
.forEach((c) => {
const collectionCopy = new CollectionView() as CollectionFilter;
collectionCopy.id = c.id;
collectionCopy.organizationId = c.organizationId;
collectionCopy.icon = "bwi-collection";
if (c instanceof CollectionAdminView) {
collectionCopy.groups = c.groups;
collectionCopy.assigned = c.assigned;
}
const parts =
c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter);
});
nodes.forEach((n) => {
n.parent = headNode;
headNode.children.push(n);
});
return headNode;
}
protected getCollectionFilterHead(): TreeNode<CollectionFilter> {
const head = new CollectionView() as CollectionFilter;
return new TreeNode<CollectionFilter>(head, null, "collections", "AllCollections");
}
protected async filterFolders(
storedFolders: FolderView[],
org?: Organization
): Promise<FolderView[]> {
if (org?.id == null) {
return storedFolders;
}
const ciphers = await this.cipherService.getAllDecrypted();
const orgCiphers = ciphers.filter((c) => c.organizationId == org?.id);
return storedFolders.filter(
(f) =>
orgCiphers.filter((oc) => oc.folderId == f.id).length > 0 ||
ciphers.filter((c) => c.folderId == f.id).length < 1
);
}
protected buildFolderTree(folders?: FolderView[]): TreeNode<FolderFilter> {
const headNode = this.getFolderFilterHead();
if (!folders) {
return headNode;
}
const nodes: TreeNode<FolderFilter>[] = [];
folders.forEach((f) => {
const folderCopy = new FolderView() as FolderFilter;
folderCopy.id = f.id;
folderCopy.revisionDate = f.revisionDate;
folderCopy.icon = "bwi-folder";
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
});
nodes.forEach((n) => {
n.parent = headNode;
headNode.children.push(n);
});
return headNode;
}
protected getFolderFilterHead(): TreeNode<FolderFilter> {
const head = new FolderView() as FolderFilter;
return new TreeNode<FolderFilter>(head, null, "folders", "AllFolders");
}
}

View File

@@ -1,143 +0,0 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable @angular-eslint/template/button-has-type -->
<ng-container *ngIf="filters && filters.length">
<div *ngIf="headerInfo.showHeader" class="filter-heading">
<button
class="toggle-button"
(click)="toggleCollapse(headerNode.node)"
[attr.aria-expanded]="!isCollapsed(headerNode.node)"
appA11yTitle="{{ 'toggleCollapse' | i18n }}: {{ headerNode.node.name | i18n }}"
aria-controls="sub-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="isCollapsed(headerNode.node) ? 'bwi-angle-right' : 'bwi-angle-down'"
></i>
</button>
<button
*ngIf="headerInfo.isSelectable"
appA11yTitle="{{ isOrganizationFilter ? 'vault' : ('filter' | i18n) }}: {{
headerNode.node.name | i18n
}}"
class="filter-button"
(click)="onFilterSelect(headerNode)"
>
<h3
[ngClass]="{
active: isAllVaultsSelected || isNodeSelected(headerNode)
}"
>
&nbsp;{{ headerNode.node.name | i18n }}
</h3>
</button>
<h3 *ngIf="!headerInfo.isSelectable" class="filter-title">
&nbsp;{{ headerNode.node.name | i18n }}
</h3>
<button
*ngIf="showAddButton"
(click)="onAdd()"
class="text-muted ml-auto add-button"
appA11yTitle="{{ addInfo.text | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
<ul
id="{{ headerNode.node.name }}-filters"
*ngIf="!isCollapsed(headerNode.node)"
class="filter-options"
>
<ng-template #recursiveFilters let-filters>
<li
*ngFor="let f of filters"
[ngClass]="{
active: isNodeSelected(f)
}"
class="filter-option"
>
<span class="filter-buttons">
<button
*ngIf="f.children.length"
appA11yTitle="{{ 'toggleCollapse' | i18n }}: {{ f.node.name }}"
(click)="toggleCollapse(f.node)"
[attr.aria-expanded]="!isCollapsed(f.node)"
[attr.aria-controls]="f.node.name + '_children'"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
aria-hidden="true"
></i>
</button>
<button
class="filter-button"
appA11yTitle="{{ isOrganizationFilter ? 'vault' : ('filter' | i18n) }}: {{
f.node.name
}}"
[ngClass]="{ 'disabled-organization': isOrganizationFilter && !f.node.enabled }"
(click)="onFilterSelect(f)"
>
<i
*ngIf="f.children.length === 0"
class="bwi bwi-fw {{ f.node.icon }}"
aria-hidden="true"
></i>
&nbsp;{{ f.node.name }}
</button>
<span class="ml-auto">
<button
*ngIf="editInfo && f.node.id"
class="edit-button"
(click)="onEdit(f)"
appA11yTitle="{{ editInfo.text | i18n }}"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</button>
<i
*ngIf="isOrganizationFilter && !f.node.enabled"
class="org-options bwi bwi-fw bwi-exclamation-triangle text-danger"
[attr.aria-label]="'organizationIsDisabled' | i18n"
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
></i
><ng-container *ngIf="optionsInfo && !f.node.hideOptions"
><button [bitMenuTriggerFor]="optionsMenu" class="filter-options-icon">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #optionsMenu>
<ng-container
*ngComponentOutlet="optionsInfo.component; injector: createInjector(f.node)"
></ng-container>
</bit-menu>
</ng-container>
</span>
</span>
<ul
[id]="f.node.name + '_children'"
class="nested-filter-options"
*ngIf="f.children.length && !isCollapsed(f.node)"
>
<ng-container *ngTemplateOutlet="recursiveFilters; context: { $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFilters; context: { $implicit: filters }"
></ng-container>
<li class="filter-option" *ngIf="showAddLink">
<span class="filter-buttons">
<a href="#" routerLink="{{ addInfo.route }}" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
&nbsp;{{ addInfo.text | i18n }}
</a>
</span>
</li>
</ul>
<hr *ngIf="divider" />
</ng-container>

View File

@@ -1,137 +0,0 @@
import { Component, InjectionToken, Injector, Input, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { ITreeNodeObject, TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { VaultFilterService } from "../../services/abstractions/vault-filter.service";
import { VaultFilterSection, VaultFilterType } from "../models/vault-filter-section.type";
import { VaultFilter } from "../models/vault-filter.model";
@Component({
selector: "app-filter-section",
templateUrl: "vault-filter-section.component.html",
})
export class VaultFilterSectionComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
@Input() activeFilter: VaultFilter;
@Input() section: VaultFilterSection;
data: TreeNode<VaultFilterType>;
collapsedFilterNodes: Set<string> = new Set();
private injectors = new Map<string, Injector>();
constructor(private vaultFilterService: VaultFilterService, private injector: Injector) {
this.vaultFilterService.collapsedFilterNodes$
.pipe(takeUntil(this.destroy$))
.subscribe((nodes) => {
this.collapsedFilterNodes = nodes;
});
}
ngOnInit() {
this.section?.data$?.pipe(takeUntil(this.destroy$)).subscribe((data) => {
this.data = data;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get headerNode() {
return this.data;
}
get headerInfo() {
return this.section.header;
}
get filters() {
return this.data?.children;
}
get isOrganizationFilter() {
return this.data.node instanceof Organization;
}
get isAllVaultsSelected() {
return this.isOrganizationFilter && !this.activeFilter.selectedOrganizationNode;
}
isNodeSelected(filterNode: TreeNode<VaultFilterType>) {
return (
this.activeFilter.organizationId === filterNode?.node.id ||
this.activeFilter.cipherTypeId === filterNode?.node.id ||
this.activeFilter.folderId === filterNode?.node.id ||
this.activeFilter.collectionId === filterNode?.node.id
);
}
async onFilterSelect(filterNode: TreeNode<VaultFilterType>) {
await this.section?.action(filterNode);
}
get editInfo() {
return this.section?.edit;
}
onEdit(filterNode: TreeNode<VaultFilterType>) {
this.section?.edit?.action(filterNode.node);
}
get addInfo() {
return this.section.add;
}
get showAddButton() {
return this.section.add && !this.section.add.route;
}
get showAddLink() {
return this.section.add && this.section.add.route;
}
async onAdd() {
this.section?.add?.action();
}
get optionsInfo() {
return this.section?.options;
}
get divider() {
return this.section?.divider;
}
isCollapsed(node: ITreeNodeObject) {
return this.collapsedFilterNodes.has(node.id);
}
async toggleCollapse(node: ITreeNodeObject) {
if (this.collapsedFilterNodes.has(node.id)) {
this.collapsedFilterNodes.delete(node.id);
} else {
this.collapsedFilterNodes.add(node.id);
}
await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes);
}
// an injector is necessary to pass data into a dynamic component
// here we are creating a new injector for each filter that has options
createInjector(data: VaultFilterType) {
let inject = this.injectors.get(data.id);
if (!inject) {
inject = Injector.create({
providers: [{ provide: OptionsInput, useValue: data }],
parent: this.injector,
});
this.injectors.set(data.id, inject);
}
return inject;
}
}
export const OptionsInput = new InjectionToken<VaultFilterType>("OptionsInput");

View File

@@ -1,50 +0,0 @@
import { Observable } from "rxjs";
import { TreeNode } from "@bitwarden/common/src/models/domain/tree-node";
import {
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "./vault-filter.type";
export type VaultFilterType =
| OrganizationFilter
| CipherTypeFilter
| FolderFilter
| CollectionFilter;
export enum VaultFilterLabel {
OrganizationFilter = "organizationFilter",
TypeFilter = "typeFilter",
FolderFilter = "folderFilter",
CollectionFilter = "collectionFilter",
TrashFilter = "trashFilter",
}
export type VaultFilterSection = {
data$: Observable<TreeNode<VaultFilterType>>;
header: {
showHeader: boolean;
isSelectable: boolean;
};
action: (filterNode: TreeNode<VaultFilterType>) => Promise<void>;
edit?: {
text: string;
action: (filter: VaultFilterType) => void;
};
add?: {
text: string;
route?: string;
action?: () => void;
};
options?: {
component: any;
};
divider?: boolean;
};
export type VaultFilterList = {
[key in VaultFilterLabel]?: VaultFilterSection;
};

View File

@@ -1,332 +0,0 @@
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { VaultFilter } from "./vault-filter.model";
import {
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "./vault-filter.type";
describe("VaultFilter", () => {
describe("filterFunction", () => {
const allCiphersFilter = new TreeNode<CipherTypeFilter>(
{
id: "AllItems",
name: "allItems",
type: "all",
icon: "",
},
null
);
const favoriteCiphersFilter = new TreeNode<CipherTypeFilter>(
{
id: "favorites",
name: "favorites",
type: "favorites",
icon: "bwi-star",
},
null
);
const identityCiphersFilter = new TreeNode<CipherTypeFilter>(
{
id: "identity",
name: "identity",
type: CipherType.Identity,
icon: "bwi-id-card",
},
null
);
const trashFilter = new TreeNode<CipherTypeFilter>(
{
id: "trash",
name: "trash",
type: "trash",
icon: "bwi-trash",
},
null
);
describe("generic cipher", () => {
it("should return true when no filter is applied", () => {
const cipher = createCipher();
const filterFunction = createFilterFunction({});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
describe("given a favorite cipher", () => {
const cipher = createCipher({ favorite: true });
it("should return true when filtering for favorites", () => {
const filterFunction = createFilterFunction({ selectedCipherTypeNode: allCiphersFilter });
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return false when filtering for trash", () => {
const filterFunction = createFilterFunction({ selectedCipherTypeNode: trashFilter });
const result = filterFunction(cipher);
expect(result).toBe(false);
});
});
describe("given a deleted cipher", () => {
const cipher = createCipher({ deletedDate: new Date() });
it("should return true when filtering for trash", () => {
const filterFunction = createFilterFunction({ selectedCipherTypeNode: trashFilter });
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return false when filtering for favorites", () => {
const filterFunction = createFilterFunction({
selectedCipherTypeNode: favoriteCiphersFilter,
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
});
describe("given a cipher with type", () => {
it("should return true when filter matches cipher type", () => {
const cipher = createCipher({ type: CipherType.Identity });
const filterFunction = createFilterFunction({
selectedCipherTypeNode: identityCiphersFilter,
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return false when filter does not match cipher type", () => {
const cipher = createCipher({ type: CipherType.Card });
const filterFunction = createFilterFunction({
selectedCipherTypeNode: identityCiphersFilter,
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
});
describe("given a cipher with folder id", () => {
it("should return true when filter matches folder id", () => {
const cipher = createCipher({ folderId: "folderId" });
const filterFunction = createFilterFunction({
selectedFolderNode: createFolderFilterNode({ id: "folderId" }),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return false when filter does not match folder id", () => {
const cipher = createCipher({ folderId: "folderId" });
const filterFunction = createFilterFunction({
selectedFolderNode: createFolderFilterNode({ id: "differentFolderId" }),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
});
describe("given a cipher without folder", () => {
const cipher = createCipher({ folderId: null });
it("should return true when filtering on unassigned folder", () => {
const filterFunction = createFilterFunction({
selectedFolderNode: createFolderFilterNode({ id: null }),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
describe("given an organizational cipher (with organization and collections)", () => {
const cipher = createCipher({
organizationId: "organizationId",
collectionIds: ["collectionId", "anotherId"],
});
it("should return true when filter matches collection id", () => {
const filterFunction = createFilterFunction({
selectedCollectionNode: createCollectionFilterNode({
id: "collectionId",
organizationId: "organizationId",
}),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return false when filter does not match collection id", () => {
const filterFunction = createFilterFunction({
selectedCollectionNode: createCollectionFilterNode({
id: "nonMatchingCollectionId",
organizationId: "organizationId",
}),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
it("should return false when filter does not match organization id", () => {
const filterFunction = createFilterFunction({
selectedOrganizationNode: createOrganizationFilterNode({
id: "nonMatchingOrganizationId",
}),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
it("should return false when filtering for my vault only", () => {
const filterFunction = createFilterFunction({
selectedOrganizationNode: createOrganizationFilterNode({ id: "MyVault" }),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
it("should return false when filtering by All Collections", () => {
const filterFunction = createFilterFunction({
selectedCollectionNode: createCollectionFilterNode({ id: "AllCollections" }),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
});
describe("given an unassigned organizational cipher (with organization, without collection)", () => {
const cipher = createCipher({ organizationId: "organizationId", collectionIds: [] });
it("should return true when filtering for unassigned collection", () => {
const filterFunction = createFilterFunction({
selectedCollectionNode: createCollectionFilterNode({ id: null }),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
it("should return true when filter matches organization id", () => {
const filterFunction = createFilterFunction({
selectedOrganizationNode: createOrganizationFilterNode({ id: "organizationId" }),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
describe("given an individual cipher (without organization or collection)", () => {
const cipher = createCipher({ organizationId: null, collectionIds: [] });
it("should return false when filtering for unassigned collection", () => {
const filterFunction = createFilterFunction({
selectedCollectionNode: createCollectionFilterNode({ id: null }),
});
const result = filterFunction(cipher);
expect(result).toBe(false);
});
it("should return true when filtering for my vault only", () => {
const cipher = createCipher({ organizationId: null });
const filterFunction = createFilterFunction({
selectedOrganizationNode: createOrganizationFilterNode({ id: "MyVault" }),
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
});
});
function createFilterFunction(options: Partial<VaultFilter> = {}) {
return new VaultFilter(options).buildFilter();
}
function createOrganizationFilterNode(
options: Partial<OrganizationFilter>
): TreeNode<OrganizationFilter> {
const org = new Organization() as OrganizationFilter;
org.id = options.id;
org.icon = options.icon ?? "";
return new TreeNode<OrganizationFilter>(org, null);
}
function createFolderFilterNode(options: Partial<FolderFilter>): TreeNode<FolderFilter> {
const folder = new FolderView() as FolderFilter;
folder.id = options.id;
folder.name = options.name;
folder.icon = options.icon ?? "";
folder.revisionDate = options.revisionDate ?? new Date();
return new TreeNode<FolderFilter>(folder, null);
}
function createCollectionFilterNode(
options: Partial<CollectionFilter>
): TreeNode<CollectionFilter> {
const collection = new CollectionView() as CollectionFilter;
collection.id = options.id;
collection.name = options.name ?? "";
collection.icon = options.icon ?? "";
collection.organizationId = options.organizationId;
collection.externalId = options.externalId ?? "";
collection.readOnly = options.readOnly ?? false;
collection.hidePasswords = options.hidePasswords ?? false;
return new TreeNode<CollectionFilter>(collection, null);
}
function createCipher(options: Partial<CipherView> = {}) {
const cipher = new CipherView();
cipher.favorite = options.favorite ?? false;
cipher.deletedDate = options.deletedDate;
cipher.type = options.type;
cipher.folderId = options.folderId;
cipher.collectionIds = options.collectionIds;
cipher.organizationId = options.organizationId;
return cipher;
}

Some files were not shown because too many files have changed in this diff Show More