mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-17 20:31:50 +01:00
[PS-1078] Refactor FolderService to use Observables (#3022)
This commit is contained in:
parent
a43aa9612c
commit
23253b3882
apps
browser/src
background
popup
cli/src
desktop/src/app
web/src/app
libs
angular/src
components
modules/vault-filter
services
common
spec/services
src
abstractions
models/domain
services
@ -44,6 +44,7 @@ import { ApiService } from "@bitwarden/common/services/api.service";
|
|||||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
import { AuthService } from "@bitwarden/common/services/auth.service";
|
import { AuthService } from "@bitwarden/common/services/auth.service";
|
||||||
|
import { BroadcasterService } from "@bitwarden/common/services/broadcaster.service";
|
||||||
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||||
@ -149,6 +150,7 @@ export default class MainBackground {
|
|||||||
vaultFilterService: VaultFilterService;
|
vaultFilterService: VaultFilterService;
|
||||||
usernameGenerationService: UsernameGenerationServiceAbstraction;
|
usernameGenerationService: UsernameGenerationServiceAbstraction;
|
||||||
encryptService: EncryptService;
|
encryptService: EncryptService;
|
||||||
|
broadcasterService: BroadcasterService;
|
||||||
folderApiService: FolderApiServiceAbstraction;
|
folderApiService: FolderApiServiceAbstraction;
|
||||||
|
|
||||||
onUpdatedRan: boolean;
|
onUpdatedRan: boolean;
|
||||||
@ -267,11 +269,13 @@ export default class MainBackground {
|
|||||||
this.logService,
|
this.logService,
|
||||||
this.stateService
|
this.stateService
|
||||||
);
|
);
|
||||||
|
this.broadcasterService = new BroadcasterService();
|
||||||
this.folderService = new FolderService(
|
this.folderService = new FolderService(
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.stateService
|
this.stateService,
|
||||||
|
this.broadcasterService
|
||||||
);
|
);
|
||||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||||
this.collectionService = new CollectionService(
|
this.collectionService = new CollectionService(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||||
@ -385,7 +387,7 @@ export default class NotificationBackground {
|
|||||||
model.login = loginModel;
|
model.login = loginModel;
|
||||||
|
|
||||||
if (!Utils.isNullOrWhitespace(folderId)) {
|
if (!Utils.isNullOrWhitespace(folderId)) {
|
||||||
const folders = await this.folderService.getAllDecrypted();
|
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
if (folders.some((x) => x.id === folderId)) {
|
if (folders.some((x) => x.id === folderId)) {
|
||||||
model.folderId = folderId;
|
model.folderId = folderId;
|
||||||
}
|
}
|
||||||
@ -437,7 +439,7 @@ export default class NotificationBackground {
|
|||||||
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
||||||
const responseData: any = {};
|
const responseData: any = {};
|
||||||
if (responseCommand === "notificationBarGetFoldersList") {
|
if (responseCommand === "notificationBarGetFoldersList") {
|
||||||
responseData.folders = await this.folderService.getAllDecrypted();
|
responseData.folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
}
|
}
|
||||||
|
|
||||||
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||||
|
@ -11,6 +11,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|||||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service";
|
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service";
|
||||||
|
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
@ -111,6 +112,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
: new BrowserMessagingService();
|
: new BrowserMessagingService();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: BroadcasterServiceAbstraction,
|
||||||
|
useFactory: getBgService<BroadcasterServiceAbstraction>("broadcasterService"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: TwoFactorService,
|
provide: TwoFactorService,
|
||||||
useFactory: getBgService<TwoFactorService>("twoFactorService"),
|
useFactory: getBgService<TwoFactorService>("twoFactorService"),
|
||||||
|
@ -20,20 +20,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main tabindex="-1">
|
<main tabindex="-1">
|
||||||
<div class="box list full-list" *ngIf="folders && folders.length">
|
<div class="box list full-list" *ngIf="(folders$ | async)?.length">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
appStopClick
|
appStopClick
|
||||||
(click)="folderSelected(f)"
|
(click)="folderSelected(f)"
|
||||||
class="box-content-row padded"
|
class="box-content-row padded"
|
||||||
*ngFor="let f of folders"
|
*ngFor="let f of folders$ | async"
|
||||||
>
|
>
|
||||||
{{ f.name }}
|
{{ f.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-items" *ngIf="!folders || !folders.length">
|
<div class="no-items" *ngIf="!(folders$ | async)?.length">
|
||||||
<p>{{ "noFolders" | i18n }}</p>
|
<p>{{ "noFolders" | i18n }}</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { map, Observable } from "rxjs";
|
||||||
|
|
||||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||||
@ -8,17 +9,19 @@ import { FolderView } from "@bitwarden/common/models/view/folderView";
|
|||||||
selector: "app-folders",
|
selector: "app-folders",
|
||||||
templateUrl: "folders.component.html",
|
templateUrl: "folders.component.html",
|
||||||
})
|
})
|
||||||
export class FoldersComponent implements OnInit {
|
export class FoldersComponent {
|
||||||
folders: FolderView[];
|
folders$: Observable<FolderView[]>;
|
||||||
|
|
||||||
constructor(private folderService: FolderService, private router: Router) {}
|
constructor(private folderService: FolderService, private router: Router) {
|
||||||
|
this.folders$ = this.folderService.folderViews$.pipe(
|
||||||
async ngOnInit() {
|
map((folders) => {
|
||||||
this.folders = await this.folderService.getAllDecrypted();
|
if (folders.length > 0) {
|
||||||
// Remove "No Folder"
|
folders = folders.slice(0, folders.length - 1);
|
||||||
if (this.folders.length > 0) {
|
|
||||||
this.folders = this.folders.slice(0, this.folders.length - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return folders;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
folderSelected(folder: FolderView) {
|
folderSelected(folder: FolderView) {
|
||||||
|
@ -515,7 +515,7 @@
|
|||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="folder">{{ "folder" | i18n }}</label>
|
<label for="folder">{{ "folder" | i18n }}</label>
|
||||||
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
|
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
|
||||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
@ -120,7 +120,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
|||||||
this.searchPlaceholder = this.i18nService.t("searchFolder");
|
this.searchPlaceholder = this.i18nService.t("searchFolder");
|
||||||
if (this.folderId != null) {
|
if (this.folderId != null) {
|
||||||
this.showOrganizations = false;
|
this.showOrganizations = false;
|
||||||
const folderNode = await this.folderService.getNested(this.folderId);
|
const folderNode = await this.vaultFilterService.getFolderNested(this.folderId);
|
||||||
if (folderNode != null && folderNode.node != null) {
|
if (folderNode != null && folderNode.node != null) {
|
||||||
this.groupingTitle = folderNode.node.name;
|
this.groupingTitle = folderNode.node.name;
|
||||||
this.nestedFolders =
|
this.nestedFolders =
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Location } from "@angular/common";
|
import { Location } from "@angular/common";
|
||||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { VaultFilter } from "@bitwarden/angular/modules/vault-filter/models/vault-filter.model";
|
import { VaultFilter } from "@bitwarden/angular/modules/vault-filter/models/vault-filter.model";
|
||||||
@ -182,9 +183,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadFolders() {
|
async loadFolders() {
|
||||||
const allFolders = await this.vaultFilterService.buildFolders(this.selectedOrganization);
|
const allFolders = await firstValueFrom(
|
||||||
|
this.vaultFilterService.buildNestedFolders(this.selectedOrganization)
|
||||||
|
);
|
||||||
this.folders = allFolders.fullList;
|
this.folders = allFolders.fullList;
|
||||||
this.nestedFolders = await allFolders.nestedList;
|
this.nestedFolders = allFolders.nestedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(timeout: number = null) {
|
async search(timeout: number = null) {
|
||||||
|
@ -14,6 +14,7 @@ import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
|||||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
import { AuthService } from "@bitwarden/common/services/auth.service";
|
import { AuthService } from "@bitwarden/common/services/auth.service";
|
||||||
|
import { BroadcasterService } from "@bitwarden/common/services/broadcaster.service";
|
||||||
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||||
@ -103,6 +104,7 @@ export class Main {
|
|||||||
organizationService: OrganizationService;
|
organizationService: OrganizationService;
|
||||||
providerService: ProviderService;
|
providerService: ProviderService;
|
||||||
twoFactorService: TwoFactorService;
|
twoFactorService: TwoFactorService;
|
||||||
|
broadcasterService: BroadcasterService;
|
||||||
folderApiService: FolderApiService;
|
folderApiService: FolderApiService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -198,11 +200,14 @@ export class Main {
|
|||||||
this.stateService
|
this.stateService
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.broadcasterService = new BroadcasterService();
|
||||||
|
|
||||||
this.folderService = new FolderService(
|
this.folderService = new FolderService(
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.stateService
|
this.stateService,
|
||||||
|
this.broadcasterService
|
||||||
);
|
);
|
||||||
|
|
||||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
@ -358,7 +360,7 @@ export class GetCommand extends DownloadCommand {
|
|||||||
decFolder = await folder.decrypt();
|
decFolder = await folder.decrypt();
|
||||||
}
|
}
|
||||||
} else if (id.trim() !== "") {
|
} else if (id.trim() !== "") {
|
||||||
let folders = await this.folderService.getAllDecrypted();
|
let folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
folders = CliUtils.searchFolders(folders, id);
|
folders = CliUtils.searchFolders(folders, id);
|
||||||
if (folders.length > 1) {
|
if (folders.length > 1) {
|
||||||
return Response.multipleResults(folders.map((f) => f.id));
|
return Response.multipleResults(folders.map((f) => f.id));
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||||
@ -126,7 +128,7 @@ export class ListCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async listFolders(options: Options) {
|
private async listFolders(options: Options) {
|
||||||
let folders = await this.folderService.getAllDecrypted();
|
let folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
|
|
||||||
if (options.search != null && options.search.trim() !== "") {
|
if (options.search != null && options.search.trim() !== "") {
|
||||||
folders = CliUtils.searchFolders(folders, options.search);
|
folders = CliUtils.searchFolders(folders, options.search);
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
[hide]="hideFolders"
|
[hide]="hideFolders"
|
||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||||
[folderNodes]="folders"
|
[folderNodes]="folders$ | async"
|
||||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||||
(onFilterChange)="applyFilter($event)"
|
(onFilterChange)="applyFilter($event)"
|
||||||
(onAddFolder)="addFolder()"
|
(onAddFolder)="addFolder()"
|
||||||
|
@ -455,7 +455,7 @@
|
|||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="folder">{{ "folder" | i18n }}</label>
|
<label for="folder">{{ "folder" | i18n }}</label>
|
||||||
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
|
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
|
||||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
[hide]="hideFolders"
|
[hide]="hideFolders"
|
||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||||
[folderNodes]="folders"
|
[folderNodes]="folders$ | async"
|
||||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||||
(onFilterChange)="applyFilter($event)"
|
(onFilterChange)="applyFilter($event)"
|
||||||
(onAddFolder)="addFolder()"
|
(onAddFolder)="addFolder()"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
|
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@ -192,7 +193,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
request.key = encKey[1].encryptedString;
|
request.key = encKey[1].encryptedString;
|
||||||
request.masterPasswordHash = masterPasswordHash;
|
request.masterPasswordHash = masterPasswordHash;
|
||||||
|
|
||||||
const folders = await this.folderService.getAllDecrypted();
|
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
if (folders[i].id == null) {
|
if (folders[i].id == null) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
@ -81,7 +82,7 @@ export class UpdateKeyComponent {
|
|||||||
|
|
||||||
await this.syncService.fullSync(true);
|
await this.syncService.fullSync(true);
|
||||||
|
|
||||||
const folders = await this.folderService.getAllDecrypted();
|
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
if (folders[i].id == null) {
|
if (folders[i].id == null) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
[disabled]="cipher.isDeleted || viewOnly"
|
[disabled]="cipher.isDeleted || viewOnly"
|
||||||
>
|
>
|
||||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="folder">{{ "folder" | i18n }}</label>
|
<label for="folder">{{ "folder" | i18n }}</label>
|
||||||
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
|
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
|
||||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
|
import { firstValueFrom, Observable } from "rxjs";
|
||||||
|
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||||
@ -15,7 +16,7 @@ export class BulkMoveComponent implements OnInit {
|
|||||||
@Output() onMoved = new EventEmitter();
|
@Output() onMoved = new EventEmitter();
|
||||||
|
|
||||||
folderId: string = null;
|
folderId: string = null;
|
||||||
folders: FolderView[] = [];
|
folders$: Observable<FolderView[]>;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -26,8 +27,8 @@ export class BulkMoveComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.folders = await this.folderService.getAllDecrypted();
|
this.folders$ = this.folderService.folderViews$;
|
||||||
this.folderId = this.folders[0].id;
|
this.folderId = (await firstValueFrom(this.folders$))[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
@ -51,7 +52,7 @@ export class AddEditComponent implements OnInit {
|
|||||||
|
|
||||||
editMode = false;
|
editMode = false;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
folders: FolderView[];
|
folders$: Observable<FolderView[]>;
|
||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
title: string;
|
title: string;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
@ -243,7 +244,7 @@ export class AddEditComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.folders = await this.folderService.getAllDecrypted();
|
this.folders$ = this.folderService.folderViews$;
|
||||||
|
|
||||||
if (this.editMode && this.previousCipherId !== this.cipherId) {
|
if (this.editMode && this.previousCipherId !== this.cipherId) {
|
||||||
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
|
import { firstValueFrom, Observable } from "rxjs";
|
||||||
|
|
||||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||||
import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode";
|
import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode";
|
||||||
@ -28,7 +29,7 @@ export class VaultFilterComponent implements OnInit {
|
|||||||
activePersonalOwnershipPolicy: boolean;
|
activePersonalOwnershipPolicy: boolean;
|
||||||
activeSingleOrganizationPolicy: boolean;
|
activeSingleOrganizationPolicy: boolean;
|
||||||
collections: DynamicTreeNode<CollectionView>;
|
collections: DynamicTreeNode<CollectionView>;
|
||||||
folders: DynamicTreeNode<FolderView>;
|
folders$: Observable<DynamicTreeNode<FolderView>>;
|
||||||
|
|
||||||
constructor(protected vaultFilterService: VaultFilterService) {}
|
constructor(protected vaultFilterService: VaultFilterService) {}
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ export class VaultFilterComponent implements OnInit {
|
|||||||
this.activeSingleOrganizationPolicy =
|
this.activeSingleOrganizationPolicy =
|
||||||
await this.vaultFilterService.checkForSingleOrganizationPolicy();
|
await this.vaultFilterService.checkForSingleOrganizationPolicy();
|
||||||
}
|
}
|
||||||
this.folders = await this.vaultFilterService.buildFolders();
|
this.folders$ = await this.vaultFilterService.buildNestedFolders();
|
||||||
this.collections = await this.initCollections();
|
this.collections = await this.initCollections();
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
}
|
}
|
||||||
@ -67,13 +68,13 @@ export class VaultFilterComponent implements OnInit {
|
|||||||
async applyFilter(filter: VaultFilter) {
|
async applyFilter(filter: VaultFilter) {
|
||||||
if (filter.refreshCollectionsAndFolders) {
|
if (filter.refreshCollectionsAndFolders) {
|
||||||
await this.reloadCollectionsAndFolders(filter);
|
await this.reloadCollectionsAndFolders(filter);
|
||||||
filter = this.pruneInvalidatedFilterSelections(filter);
|
filter = await this.pruneInvalidatedFilterSelections(filter);
|
||||||
}
|
}
|
||||||
this.onFilterChange.emit(filter);
|
this.onFilterChange.emit(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadCollectionsAndFolders(filter: VaultFilter) {
|
async reloadCollectionsAndFolders(filter: VaultFilter) {
|
||||||
this.folders = await this.vaultFilterService.buildFolders(filter.selectedOrganizationId);
|
this.folders$ = await this.vaultFilterService.buildNestedFolders(filter.selectedOrganizationId);
|
||||||
this.collections = filter.myVaultOnly
|
this.collections = filter.myVaultOnly
|
||||||
? null
|
? null
|
||||||
: await this.vaultFilterService.buildCollections(filter.selectedOrganizationId);
|
: await this.vaultFilterService.buildCollections(filter.selectedOrganizationId);
|
||||||
@ -95,14 +96,17 @@ export class VaultFilterComponent implements OnInit {
|
|||||||
this.onEditFolder.emit(folder);
|
this.onEditFolder.emit(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pruneInvalidatedFilterSelections(filter: VaultFilter): VaultFilter {
|
protected async pruneInvalidatedFilterSelections(filter: VaultFilter): Promise<VaultFilter> {
|
||||||
filter = this.pruneInvalidFolderSelection(filter);
|
filter = await this.pruneInvalidFolderSelection(filter);
|
||||||
filter = this.pruneInvalidCollectionSelection(filter);
|
filter = this.pruneInvalidCollectionSelection(filter);
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pruneInvalidFolderSelection(filter: VaultFilter): VaultFilter {
|
protected async pruneInvalidFolderSelection(filter: VaultFilter): Promise<VaultFilter> {
|
||||||
if (filter.selectedFolder && !this.folders?.hasId(filter.selectedFolderId)) {
|
if (
|
||||||
|
filter.selectedFolder &&
|
||||||
|
!(await firstValueFrom(this.folders$))?.hasId(filter.selectedFolderId)
|
||||||
|
) {
|
||||||
filter.selectedFolder = false;
|
filter.selectedFolder = false;
|
||||||
filter.selectedFolderId = null;
|
filter.selectedFolderId = null;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
import { from, mergeMap, Observable } from "rxjs";
|
||||||
|
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||||
@ -7,12 +8,16 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
|
|||||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||||
|
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
|
||||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||||
|
import { TreeNode } from "@bitwarden/common/models/domain/treeNode";
|
||||||
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
||||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||||
|
|
||||||
import { DynamicTreeNode } from "./models/dynamic-tree-node.model";
|
import { DynamicTreeNode } from "./models/dynamic-tree-node.model";
|
||||||
|
|
||||||
|
const NestingDelimiter = "/";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VaultFilterService {
|
export class VaultFilterService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -36,8 +41,8 @@ export class VaultFilterService {
|
|||||||
return await this.organizationService.getAll();
|
return await this.organizationService.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildFolders(organizationId?: string): Promise<DynamicTreeNode<FolderView>> {
|
buildNestedFolders(organizationId?: string): Observable<DynamicTreeNode<FolderView>> {
|
||||||
const storedFolders = await this.folderService.getAllDecrypted();
|
const transformation = async (storedFolders: FolderView[]) => {
|
||||||
let folders: FolderView[];
|
let folders: FolderView[];
|
||||||
if (organizationId != null) {
|
if (organizationId != null) {
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const ciphers = await this.cipherService.getAllDecrypted();
|
||||||
@ -50,11 +55,16 @@ export class VaultFilterService {
|
|||||||
} else {
|
} else {
|
||||||
folders = storedFolders;
|
folders = storedFolders;
|
||||||
}
|
}
|
||||||
const nestedFolders = await this.folderService.getAllNested(folders);
|
const nestedFolders = await this.getAllFoldersNested(folders);
|
||||||
return new DynamicTreeNode<FolderView>({
|
return new DynamicTreeNode<FolderView>({
|
||||||
fullList: folders,
|
fullList: folders,
|
||||||
nestedList: nestedFolders,
|
nestedList: nestedFolders,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.folderService.folderViews$.pipe(
|
||||||
|
mergeMap((folders) => from(transformation(folders)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> {
|
async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> {
|
||||||
@ -79,4 +89,21 @@ export class VaultFilterService {
|
|||||||
async checkForPersonalOwnershipPolicy(): Promise<boolean> {
|
async checkForPersonalOwnershipPolicy(): Promise<boolean> {
|
||||||
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async getAllFoldersNested(folders?: FolderView[]): Promise<TreeNode<FolderView>[]> {
|
||||||
|
const nodes: TreeNode<FolderView>[] = [];
|
||||||
|
folders.forEach((f) => {
|
||||||
|
const folderCopy = new FolderView();
|
||||||
|
folderCopy.id = f.id;
|
||||||
|
folderCopy.revisionDate = f.revisionDate;
|
||||||
|
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
||||||
|
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
|
||||||
|
});
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
|
||||||
|
const folders = await this.getAllFoldersNested();
|
||||||
|
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,6 +223,7 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
|
|||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
|
BroadcasterServiceAbstraction,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
@ -97,8 +98,8 @@ describe("ExportService", () => {
|
|||||||
folderService = Substitute.for<FolderService>();
|
folderService = Substitute.for<FolderService>();
|
||||||
cryptoService = Substitute.for<CryptoService>();
|
cryptoService = Substitute.for<CryptoService>();
|
||||||
|
|
||||||
folderService.getAllDecrypted().resolves([]);
|
folderService.folderViews$.returns(new BehaviorSubject([]));
|
||||||
folderService.getAll().resolves([]);
|
folderService.folders$.returns(new BehaviorSubject([]));
|
||||||
|
|
||||||
exportService = new ExportService(
|
exportService = new ExportService(
|
||||||
folderService,
|
folderService,
|
||||||
|
195
libs/common/spec/services/folder.service.spec.ts
Normal file
195
libs/common/spec/services/folder.service.spec.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||||
|
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
|
import { FolderData } from "@bitwarden/common/models/data/folderData";
|
||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||||
|
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||||
|
import { FolderService } from "@bitwarden/common/services/folder/folder.service";
|
||||||
|
import { StateService } from "@bitwarden/common/services/state.service";
|
||||||
|
|
||||||
|
describe("Folder Service", () => {
|
||||||
|
let folderService: FolderService;
|
||||||
|
|
||||||
|
let cryptoService: SubstituteOf<CryptoService>;
|
||||||
|
let i18nService: SubstituteOf<I18nService>;
|
||||||
|
let cipherService: SubstituteOf<CipherService>;
|
||||||
|
let stateService: SubstituteOf<StateService>;
|
||||||
|
let broadcasterService: SubstituteOf<BroadcasterService>;
|
||||||
|
let activeAccount: BehaviorSubject<string>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cryptoService = Substitute.for();
|
||||||
|
i18nService = Substitute.for();
|
||||||
|
cipherService = Substitute.for();
|
||||||
|
stateService = Substitute.for();
|
||||||
|
broadcasterService = Substitute.for();
|
||||||
|
activeAccount = new BehaviorSubject("123");
|
||||||
|
|
||||||
|
stateService.getEncryptedFolders().resolves({
|
||||||
|
"1": folderData("1", "test"),
|
||||||
|
});
|
||||||
|
stateService.activeAccount.returns(activeAccount);
|
||||||
|
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||||
|
|
||||||
|
folderService = new FolderService(
|
||||||
|
cryptoService,
|
||||||
|
i18nService,
|
||||||
|
cipherService,
|
||||||
|
stateService,
|
||||||
|
broadcasterService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encrypt", async () => {
|
||||||
|
const model = new FolderView();
|
||||||
|
model.id = "2";
|
||||||
|
model.name = "Test Folder";
|
||||||
|
|
||||||
|
cryptoService.encrypt(Arg.any()).resolves(new EncString("ENC"));
|
||||||
|
cryptoService.decryptToUtf8(Arg.any()).resolves("DEC");
|
||||||
|
|
||||||
|
const result = await folderService.encrypt(model);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: "2",
|
||||||
|
name: {
|
||||||
|
encryptedString: "ENC",
|
||||||
|
encryptionType: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get", () => {
|
||||||
|
it("exists", async () => {
|
||||||
|
const result = await folderService.get("1");
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: "1",
|
||||||
|
name: {
|
||||||
|
decryptedValue: [],
|
||||||
|
encryptedString: "test",
|
||||||
|
encryptionType: 0,
|
||||||
|
},
|
||||||
|
revisionDate: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("not exists", async () => {
|
||||||
|
const result = await folderService.get("2");
|
||||||
|
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsert", async () => {
|
||||||
|
await folderService.upsert(folderData("2", "test 2"));
|
||||||
|
|
||||||
|
expect(await firstValueFrom(folderService.folders$)).toEqual([
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: {
|
||||||
|
decryptedValue: [],
|
||||||
|
encryptedString: "test",
|
||||||
|
encryptionType: 0,
|
||||||
|
},
|
||||||
|
revisionDate: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: {
|
||||||
|
decryptedValue: [],
|
||||||
|
encryptedString: "test 2",
|
||||||
|
encryptionType: 0,
|
||||||
|
},
|
||||||
|
revisionDate: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||||
|
{ id: "1", name: [], revisionDate: null },
|
||||||
|
{ id: "2", name: [], revisionDate: null },
|
||||||
|
{ id: null, name: [], revisionDate: null },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replace", async () => {
|
||||||
|
await folderService.replace({ "2": folderData("2", "test 2") });
|
||||||
|
|
||||||
|
expect(await firstValueFrom(folderService.folders$)).toEqual([
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: {
|
||||||
|
decryptedValue: [],
|
||||||
|
encryptedString: "test 2",
|
||||||
|
encryptionType: 0,
|
||||||
|
},
|
||||||
|
revisionDate: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||||
|
{ id: "2", name: [], revisionDate: null },
|
||||||
|
{ id: null, name: [], revisionDate: null },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delete", async () => {
|
||||||
|
await folderService.delete("1");
|
||||||
|
|
||||||
|
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||||
|
{ id: null, name: [], revisionDate: null },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clearCache", async () => {
|
||||||
|
await folderService.clearCache();
|
||||||
|
|
||||||
|
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
|
||||||
|
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("clear", () => {
|
||||||
|
it("null userId", async () => {
|
||||||
|
await folderService.clear();
|
||||||
|
|
||||||
|
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
|
||||||
|
|
||||||
|
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
|
||||||
|
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matching userId", async () => {
|
||||||
|
stateService.getUserId().resolves("1");
|
||||||
|
await folderService.clear("1");
|
||||||
|
|
||||||
|
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
|
||||||
|
|
||||||
|
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
|
||||||
|
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("missmatching userId", async () => {
|
||||||
|
await folderService.clear("12");
|
||||||
|
|
||||||
|
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
|
||||||
|
|
||||||
|
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
|
||||||
|
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function folderData(id: string, name: string) {
|
||||||
|
const data = new FolderData({} as any);
|
||||||
|
data.id = id;
|
||||||
|
data.name = name;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
@ -1,22 +1,22 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { FolderData } from "../../models/data/folderData";
|
import { FolderData } from "../../models/data/folderData";
|
||||||
import { Folder } from "../../models/domain/folder";
|
import { Folder } from "../../models/domain/folder";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
||||||
import { TreeNode } from "../../models/domain/treeNode";
|
|
||||||
import { FolderView } from "../../models/view/folderView";
|
import { FolderView } from "../../models/view/folderView";
|
||||||
|
|
||||||
export abstract class FolderService {
|
export abstract class FolderService {
|
||||||
clearCache: (userId?: string) => Promise<void>;
|
folders$: Observable<Folder[]>;
|
||||||
|
folderViews$: Observable<FolderView[]>;
|
||||||
|
|
||||||
|
clearCache: () => Promise<void>;
|
||||||
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
|
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
|
||||||
get: (id: string) => Promise<Folder>;
|
get: (id: string) => Promise<Folder>;
|
||||||
getAll: () => Promise<Folder[]>;
|
|
||||||
getAllDecrypted: () => Promise<FolderView[]>;
|
|
||||||
getAllNested: (folders?: FolderView[]) => Promise<TreeNode<FolderView>[]>;
|
|
||||||
getNested: (id: string) => Promise<TreeNode<FolderView>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class InternalFolderService extends FolderService {
|
export abstract class InternalFolderService extends FolderService {
|
||||||
upsert: (folder: FolderData | FolderData[]) => Promise<any>;
|
upsert: (folder: FolderData | FolderData[]) => Promise<void>;
|
||||||
replace: (folders: { [id: string]: FolderData }) => Promise<any>;
|
replace: (folders: { [id: string]: FolderData }) => Promise<void>;
|
||||||
clear: (userId: string) => Promise<any>;
|
clear: (userId: string) => Promise<any>;
|
||||||
delete: (id: string | string[]) => Promise<any>;
|
delete: (id: string | string[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
|||||||
import { WindowState } from "../models/domain/windowState";
|
import { WindowState } from "../models/domain/windowState";
|
||||||
import { CipherView } from "../models/view/cipherView";
|
import { CipherView } from "../models/view/cipherView";
|
||||||
import { CollectionView } from "../models/view/collectionView";
|
import { CollectionView } from "../models/view/collectionView";
|
||||||
import { FolderView } from "../models/view/folderView";
|
|
||||||
import { SendView } from "../models/view/sendView";
|
import { SendView } from "../models/view/sendView";
|
||||||
|
|
||||||
export abstract class StateService<T extends Account = Account> {
|
export abstract class StateService<T extends Account = Account> {
|
||||||
@ -88,8 +87,6 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
value: SymmetricCryptoKey,
|
value: SymmetricCryptoKey,
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getDecryptedFolders: (options?: StorageOptions) => Promise<FolderView[]>;
|
|
||||||
setDecryptedFolders: (value: FolderView[], options?: StorageOptions) => Promise<void>;
|
|
||||||
getDecryptedOrganizationKeys: (
|
getDecryptedOrganizationKeys: (
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<Map<string, SymmetricCryptoKey>>;
|
) => Promise<Map<string, SymmetricCryptoKey>>;
|
||||||
@ -183,7 +180,13 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
||||||
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
|
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* @deprecated Do not call this directly, use FolderService
|
||||||
|
*/
|
||||||
getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>;
|
getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>;
|
||||||
|
/**
|
||||||
|
* @deprecated Do not call this directly, use FolderService
|
||||||
|
*/
|
||||||
setEncryptedFolders: (
|
setEncryptedFolders: (
|
||||||
value: { [id: string]: FolderData },
|
value: { [id: string]: FolderData },
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
|
@ -11,7 +11,6 @@ import { ProviderData } from "../data/providerData";
|
|||||||
import { SendData } from "../data/sendData";
|
import { SendData } from "../data/sendData";
|
||||||
import { CipherView } from "../view/cipherView";
|
import { CipherView } from "../view/cipherView";
|
||||||
import { CollectionView } from "../view/collectionView";
|
import { CollectionView } from "../view/collectionView";
|
||||||
import { FolderView } from "../view/folderView";
|
|
||||||
import { SendView } from "../view/sendView";
|
import { SendView } from "../view/sendView";
|
||||||
|
|
||||||
import { EncString } from "./encString";
|
import { EncString } from "./encString";
|
||||||
@ -31,15 +30,19 @@ export class DataEncryptionPair<TEncrypted, TDecrypted> {
|
|||||||
decrypted?: TDecrypted[];
|
decrypted?: TDecrypted[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a temporary structure to handle migrated `DataEncryptionPair` to
|
||||||
|
// avoid needing a data migration at this stage. It should be replaced with
|
||||||
|
// proper data migrations when `DataEncryptionPair` is deprecated.
|
||||||
|
export class TemporaryDataEncryption<TEncrypted> {
|
||||||
|
encrypted?: { [id: string]: TEncrypted };
|
||||||
|
}
|
||||||
|
|
||||||
export class AccountData {
|
export class AccountData {
|
||||||
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<
|
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<
|
||||||
CipherData,
|
CipherData,
|
||||||
CipherView
|
CipherView
|
||||||
>();
|
>();
|
||||||
folders?: DataEncryptionPair<FolderData, FolderView> = new DataEncryptionPair<
|
folders? = new TemporaryDataEncryption<FolderData>();
|
||||||
FolderData,
|
|
||||||
FolderView
|
|
||||||
>();
|
|
||||||
localData?: any;
|
localData?: any;
|
||||||
sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>();
|
sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>();
|
||||||
collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair<
|
collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair<
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as papa from "papaparse";
|
import * as papa from "papaparse";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "../abstractions/api.service";
|
import { ApiService } from "../abstractions/api.service";
|
||||||
import { CipherService } from "../abstractions/cipher.service";
|
import { CipherService } from "../abstractions/cipher.service";
|
||||||
@ -115,7 +116,7 @@ export class ExportService implements ExportServiceAbstraction {
|
|||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.folderService.getAllDecrypted().then((folders) => {
|
firstValueFrom(this.folderService.folderViews$).then((folders) => {
|
||||||
decFolders = folders;
|
decFolders = folders;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -191,7 +192,7 @@ export class ExportService implements ExportServiceAbstraction {
|
|||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.folderService.getAll().then((f) => {
|
firstValueFrom(this.folderService.folders$).then((f) => {
|
||||||
folders = f;
|
folders = f;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1,31 +1,70 @@
|
|||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { BroadcasterService } from "../../abstractions/broadcaster.service";
|
||||||
import { CipherService } from "../../abstractions/cipher.service";
|
import { CipherService } from "../../abstractions/cipher.service";
|
||||||
import { CryptoService } from "../../abstractions/crypto.service";
|
import { CryptoService } from "../../abstractions/crypto.service";
|
||||||
import { FolderService as FolderServiceAbstraction } from "../../abstractions/folder/folder.service.abstraction";
|
import { FolderService as FolderServiceAbstraction } from "../../abstractions/folder/folder.service.abstraction";
|
||||||
import { I18nService } from "../../abstractions/i18n.service";
|
import { I18nService } from "../../abstractions/i18n.service";
|
||||||
import { StateService } from "../../abstractions/state.service";
|
import { StateService } from "../../abstractions/state.service";
|
||||||
import { ServiceUtils } from "../../misc/serviceUtils";
|
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import { CipherData } from "../../models/data/cipherData";
|
import { CipherData } from "../../models/data/cipherData";
|
||||||
import { FolderData } from "../../models/data/folderData";
|
import { FolderData } from "../../models/data/folderData";
|
||||||
import { Folder } from "../../models/domain/folder";
|
import { Folder } from "../../models/domain/folder";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
||||||
import { TreeNode } from "../../models/domain/treeNode";
|
|
||||||
import { FolderView } from "../../models/view/folderView";
|
import { FolderView } from "../../models/view/folderView";
|
||||||
|
|
||||||
const NestingDelimiter = "/";
|
const BroadcasterSubscriptionId = "FolderService";
|
||||||
|
|
||||||
export class FolderService implements FolderServiceAbstraction {
|
export class FolderService implements FolderServiceAbstraction {
|
||||||
|
private _folders: BehaviorSubject<Folder[]> = new BehaviorSubject([]);
|
||||||
|
private _folderViews: BehaviorSubject<FolderView[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
folders$ = this._folders.asObservable();
|
||||||
|
folderViews$ = this._folderViews.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
) {}
|
private broadcasterService: BroadcasterService
|
||||||
|
) {
|
||||||
async clearCache(userId?: string): Promise<void> {
|
this.stateService.activeAccount.subscribe(async (activeAccount) => {
|
||||||
await this.stateService.setDecryptedFolders(null, { userId: userId });
|
if ((Utils.global as any).bitwardenContainerService == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeAccount == null) {
|
||||||
|
this._folders.next([]);
|
||||||
|
this._folderViews.next([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.stateService.getEncryptedFolders();
|
||||||
|
|
||||||
|
await this.updateObservables(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Broadcasterservice should be removed or replaced with observables
|
||||||
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "unlocked": {
|
||||||
|
const data = await this.stateService.getEncryptedFolders();
|
||||||
|
|
||||||
|
await this.updateObservables(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearCache(): Promise<void> {
|
||||||
|
this._folderViews.next([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This should be moved to EncryptService or something
|
||||||
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
|
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
|
||||||
const folder = new Folder();
|
const folder = new Folder();
|
||||||
folder.id = model.id;
|
folder.id = model.id;
|
||||||
@ -34,75 +73,12 @@ export class FolderService implements FolderServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(id: string): Promise<Folder> {
|
async get(id: string): Promise<Folder> {
|
||||||
const folders = await this.stateService.getEncryptedFolders();
|
const folders = this._folders.getValue();
|
||||||
// eslint-disable-next-line
|
|
||||||
if (folders == null || !folders.hasOwnProperty(id)) {
|
return folders.find((folder) => folder.id === id);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Folder(folders[id]);
|
async upsert(folder: FolderData | FolderData[]): Promise<void> {
|
||||||
}
|
|
||||||
|
|
||||||
async getAll(): Promise<Folder[]> {
|
|
||||||
const folders = await this.stateService.getEncryptedFolders();
|
|
||||||
const response: Folder[] = [];
|
|
||||||
for (const id in folders) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (folders.hasOwnProperty(id)) {
|
|
||||||
response.push(new Folder(folders[id]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllDecrypted(): Promise<FolderView[]> {
|
|
||||||
const decryptedFolders = await this.stateService.getDecryptedFolders();
|
|
||||||
if (decryptedFolders != null) {
|
|
||||||
return decryptedFolders;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasKey = await this.cryptoService.hasKey();
|
|
||||||
if (!hasKey) {
|
|
||||||
throw new Error("No key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const decFolders: FolderView[] = [];
|
|
||||||
const promises: Promise<any>[] = [];
|
|
||||||
const folders = await this.getAll();
|
|
||||||
folders.forEach((folder) => {
|
|
||||||
promises.push(folder.decrypt().then((f) => decFolders.push(f)));
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
decFolders.sort(Utils.getSortFunction(this.i18nService, "name"));
|
|
||||||
|
|
||||||
const noneFolder = new FolderView();
|
|
||||||
noneFolder.name = this.i18nService.t("noneFolder");
|
|
||||||
decFolders.push(noneFolder);
|
|
||||||
|
|
||||||
await this.stateService.setDecryptedFolders(decFolders);
|
|
||||||
return decFolders;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllNested(folders?: FolderView[]): Promise<TreeNode<FolderView>[]> {
|
|
||||||
folders = folders ?? (await this.getAllDecrypted());
|
|
||||||
const nodes: TreeNode<FolderView>[] = [];
|
|
||||||
folders.forEach((f) => {
|
|
||||||
const folderCopy = new FolderView();
|
|
||||||
folderCopy.id = f.id;
|
|
||||||
folderCopy.revisionDate = f.revisionDate;
|
|
||||||
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
|
||||||
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNested(id: string): Promise<TreeNode<FolderView>> {
|
|
||||||
const folders = await this.getAllNested();
|
|
||||||
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async upsert(folder: FolderData | FolderData[]): Promise<any> {
|
|
||||||
let folders = await this.stateService.getEncryptedFolders();
|
let folders = await this.stateService.getEncryptedFolders();
|
||||||
if (folders == null) {
|
if (folders == null) {
|
||||||
folders = {};
|
folders = {};
|
||||||
@ -117,17 +93,20 @@ export class FolderService implements FolderServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.stateService.setDecryptedFolders(null);
|
await this.updateObservables(folders);
|
||||||
await this.stateService.setEncryptedFolders(folders);
|
await this.stateService.setEncryptedFolders(folders);
|
||||||
}
|
}
|
||||||
|
|
||||||
async replace(folders: { [id: string]: FolderData }): Promise<any> {
|
async replace(folders: { [id: string]: FolderData }): Promise<void> {
|
||||||
await this.stateService.setDecryptedFolders(null);
|
await this.updateObservables(folders);
|
||||||
await this.stateService.setEncryptedFolders(folders);
|
await this.stateService.setEncryptedFolders(folders);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear(userId?: string): Promise<any> {
|
async clear(userId?: string): Promise<any> {
|
||||||
await this.stateService.setDecryptedFolders(null, { userId: userId });
|
if (userId == null || userId == (await this.stateService.getUserId())) {
|
||||||
|
this._folders.next([]);
|
||||||
|
this._folderViews.next([]);
|
||||||
|
}
|
||||||
await this.stateService.setEncryptedFolders(null, { userId: userId });
|
await this.stateService.setEncryptedFolders(null, { userId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +127,7 @@ export class FolderService implements FolderServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.stateService.setDecryptedFolders(null);
|
await this.updateObservables(folders);
|
||||||
await this.stateService.setEncryptedFolders(folders);
|
await this.stateService.setEncryptedFolders(folders);
|
||||||
|
|
||||||
// Items in a deleted folder are re-assigned to "No Folder"
|
// Items in a deleted folder are re-assigned to "No Folder"
|
||||||
@ -166,4 +145,20 @@ export class FolderService implements FolderServiceAbstraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateObservables(foldersMap: { [id: string]: FolderData }) {
|
||||||
|
const folders = Object.values(foldersMap || {}).map((f) => new Folder(f));
|
||||||
|
|
||||||
|
const decryptFolderPromises = folders.map((f) => f.decrypt());
|
||||||
|
const decryptedFolders = await Promise.all(decryptFolderPromises);
|
||||||
|
|
||||||
|
decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||||
|
|
||||||
|
const noneFolder = new FolderView();
|
||||||
|
noneFolder.name = this.i18nService.t("noneFolder");
|
||||||
|
decryptedFolders.push(noneFolder);
|
||||||
|
|
||||||
|
this._folders.next(folders);
|
||||||
|
this._folderViews.next(decryptedFolders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
|||||||
import { WindowState } from "../models/domain/windowState";
|
import { WindowState } from "../models/domain/windowState";
|
||||||
import { CipherView } from "../models/view/cipherView";
|
import { CipherView } from "../models/view/cipherView";
|
||||||
import { CollectionView } from "../models/view/collectionView";
|
import { CollectionView } from "../models/view/collectionView";
|
||||||
import { FolderView } from "../models/view/folderView";
|
|
||||||
import { SendView } from "../models/view/sendView";
|
import { SendView } from "../models/view/sendView";
|
||||||
|
|
||||||
const keys = {
|
const keys = {
|
||||||
@ -658,24 +657,6 @@ export class StateService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@withPrototypeForArrayMembers(FolderView)
|
|
||||||
async getDecryptedFolders(options?: StorageOptions): Promise<FolderView[]> {
|
|
||||||
return (
|
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
|
||||||
)?.data?.folders?.decrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDecryptedFolders(value: FolderView[], options?: StorageOptions): Promise<void> {
|
|
||||||
const account = await this.getAccount(
|
|
||||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
|
||||||
);
|
|
||||||
account.data.folders.decrypted = value;
|
|
||||||
await this.saveAccount(
|
|
||||||
account,
|
|
||||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
|
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
|
||||||
async getDecryptedOrganizationKeys(
|
async getDecryptedOrganizationKeys(
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
|
@ -80,6 +80,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
|
|
||||||
if (userId == null || userId === (await this.stateService.getUserId())) {
|
if (userId == null || userId === (await this.stateService.getUserId())) {
|
||||||
this.searchService.clearIndex();
|
this.searchService.clearIndex();
|
||||||
|
await this.folderService.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
|
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
|
||||||
@ -91,7 +92,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
await this.cryptoService.clearKeyPair(true, userId);
|
await this.cryptoService.clearKeyPair(true, userId);
|
||||||
await this.cryptoService.clearEncKey(true, userId);
|
await this.cryptoService.clearEncKey(true, userId);
|
||||||
|
|
||||||
await this.folderService.clearCache(userId);
|
|
||||||
await this.cipherService.clearCache(userId);
|
await this.cipherService.clearCache(userId);
|
||||||
await this.collectionService.clearCache(userId);
|
await this.collectionService.clearCache(userId);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user