mirror of
https://github.com/bitwarden/browser
synced 2026-02-04 10:43:47 +00:00
Merge branch 'main' into billing/PM-24996/implement-upgrade-from-free-dialog
This commit is contained in:
@@ -119,10 +119,12 @@ import { SystemNotificationsService } from "@bitwarden/common/platform/system-no
|
||||
import { UnsupportedSystemNotificationsService } from "@bitwarden/common/platform/system-notifications/unsupported-system-notifications.service";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||
import {
|
||||
@@ -145,8 +147,6 @@ import {
|
||||
DefaultSshImportPromptService,
|
||||
PasswordRepromptService,
|
||||
SshImportPromptService,
|
||||
CipherArchiveService,
|
||||
DefaultCipherArchiveService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { AccountSwitcherService } from "../../auth/popup/account-switching/services/account-switcher.service";
|
||||
@@ -708,14 +708,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: CipherArchiveService,
|
||||
useClass: DefaultCipherArchiveService,
|
||||
deps: [
|
||||
CipherService,
|
||||
ApiService,
|
||||
DialogService,
|
||||
PasswordRepromptService,
|
||||
BillingAccountProfileStateService,
|
||||
ConfigService,
|
||||
],
|
||||
deps: [CipherService, ApiService, BillingAccountProfileStateService, ConfigService],
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
MenuModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { CipherArchiveService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { mockAccountServiceWith, ObservableTracker } from "@bitwarden/common/spec";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
RestrictedItemTypesService,
|
||||
} from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { CipherArchiveService } from "@bitwarden/vault";
|
||||
|
||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
|
||||
@@ -26,6 +26,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { CipherArchiveService } from "@bitwarden/vault";
|
||||
|
||||
import { runInsideAngular } from "../../../platform/browser/run-inside-angular.operator";
|
||||
import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service";
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
@@ -22,7 +23,11 @@ import {
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { CanDeleteCipherDirective, CipherArchiveService } from "@bitwarden/vault";
|
||||
import {
|
||||
CanDeleteCipherDirective,
|
||||
DecryptionFailureDialogComponent,
|
||||
PasswordRepromptService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
@@ -56,6 +61,7 @@ export class ArchiveComponent {
|
||||
private toastService = inject(ToastService);
|
||||
private i18nService = inject(I18nService);
|
||||
private cipherArchiveService = inject(CipherArchiveService);
|
||||
private passwordRepromptService = inject(PasswordRepromptService);
|
||||
|
||||
private userId$: Observable<UserId> = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
@@ -69,7 +75,7 @@ export class ArchiveComponent {
|
||||
);
|
||||
|
||||
async view(cipher: CipherView) {
|
||||
if (!(await this.cipherArchiveService.canInteract(cipher))) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +85,7 @@ export class ArchiveComponent {
|
||||
}
|
||||
|
||||
async edit(cipher: CipherView) {
|
||||
if (!(await this.cipherArchiveService.canInteract(cipher))) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ export class ArchiveComponent {
|
||||
}
|
||||
|
||||
async delete(cipher: CipherView) {
|
||||
if (!(await this.cipherArchiveService.canInteract(cipher))) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
@@ -118,7 +124,7 @@ export class ArchiveComponent {
|
||||
}
|
||||
|
||||
async unarchive(cipher: CipherView) {
|
||||
if (!(await this.cipherArchiveService.canInteract(cipher))) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
const activeUserId = await firstValueFrom(this.userId$);
|
||||
@@ -132,7 +138,7 @@ export class ArchiveComponent {
|
||||
}
|
||||
|
||||
async clone(cipher: CipherView) {
|
||||
if (!(await this.cipherArchiveService.canInteract(cipher))) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,4 +162,21 @@ export class ArchiveComponent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is able to interact with the cipher
|
||||
* (password re-prompt / decryption failure checks).
|
||||
* @param cipher
|
||||
* @private
|
||||
*/
|
||||
private canInteract(cipher: CipherView) {
|
||||
if (cipher.decryptionFailure) {
|
||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||
cipherIds: [cipher.id as CipherId],
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components";
|
||||
import { CipherArchiveService } from "@bitwarden/vault";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils";
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "27.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.16.1",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import * as inquirer from "inquirer";
|
||||
import { firstValueFrom, map, switchMap } from "rxjs";
|
||||
|
||||
import { UpdateCollectionRequest } from "@bitwarden/admin-console/common";
|
||||
@@ -9,6 +10,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
@@ -40,6 +42,7 @@ export class EditCommand {
|
||||
private accountService: AccountService,
|
||||
private cliRestrictedItemTypesService: CliRestrictedItemTypesService,
|
||||
private policyService: PolicyService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async run(
|
||||
@@ -92,6 +95,10 @@ export class EditCommand {
|
||||
private async editCipher(id: string, req: CipherExport) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
const hasPremium = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
|
||||
);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
@@ -102,6 +109,17 @@ export class EditCommand {
|
||||
}
|
||||
cipherView = CipherExport.toView(req, cipherView);
|
||||
|
||||
// When a user is editing an archived cipher and does not have premium, automatically unarchive it
|
||||
if (cipherView.isArchived && !hasPremium) {
|
||||
const acceptedPrompt = await this.promptForArchiveEdit();
|
||||
|
||||
if (!acceptedPrompt) {
|
||||
return Response.error("Edit cancelled.");
|
||||
}
|
||||
|
||||
cipherView.archivedDate = null;
|
||||
}
|
||||
|
||||
const isCipherRestricted =
|
||||
await this.cliRestrictedItemTypesService.isCipherRestricted(cipherView);
|
||||
if (isCipherRestricted) {
|
||||
@@ -240,6 +258,38 @@ export class EditCommand {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prompt the user to accept movement of their cipher back to the their vault. */
|
||||
private async promptForArchiveEdit(): Promise<boolean> {
|
||||
// When running in serve or no interaction mode, automatically accept the prompt
|
||||
if (process.env.BW_SERVE === "true" || process.env.BW_NOINTERACTION === "true") {
|
||||
CliUtils.writeLn(
|
||||
"Archive is only available with a Premium subscription, which has ended. Your edit was saved and the item was moved back to your vault.",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const answer: inquirer.Answers = await inquirer.createPromptModule({
|
||||
output: process.stderr,
|
||||
})({
|
||||
type: "list",
|
||||
name: "confirm",
|
||||
message:
|
||||
"When you edit and save details for an archived item without a Premium subscription, it'll be moved from your archive back to your vault.",
|
||||
choices: [
|
||||
{
|
||||
name: "Move now",
|
||||
value: "confirmed",
|
||||
},
|
||||
{
|
||||
name: "Cancel",
|
||||
value: "cancel",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return answer.confirm === "confirmed";
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
@@ -45,6 +46,7 @@ export class ListCommand {
|
||||
private accountService: AccountService,
|
||||
private keyService: KeyService,
|
||||
private cliRestrictedItemTypesService: CliRestrictedItemTypesService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
) {}
|
||||
|
||||
async run(object: string, cmdOptions: Record<string, any>): Promise<Response> {
|
||||
@@ -71,8 +73,13 @@ export class ListCommand {
|
||||
let ciphers: CipherView[];
|
||||
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const userCanArchive = await firstValueFrom(
|
||||
this.cipherArchiveService.userCanArchive$(activeUserId),
|
||||
);
|
||||
|
||||
options.trash = options.trash || false;
|
||||
options.archived = userCanArchive && options.archived;
|
||||
|
||||
if (options.url != null && options.url.trim() !== "") {
|
||||
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId);
|
||||
} else {
|
||||
@@ -85,9 +92,12 @@ export class ListCommand {
|
||||
options.organizationId != null
|
||||
) {
|
||||
ciphers = ciphers.filter((c) => {
|
||||
if (options.trash !== c.isDeleted) {
|
||||
const matchesStateOptions = this.matchesStateOptions(c, options);
|
||||
|
||||
if (!matchesStateOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.folderId != null) {
|
||||
if (options.folderId === "notnull" && c.folderId != null) {
|
||||
return true;
|
||||
@@ -131,11 +141,16 @@ export class ListCommand {
|
||||
return false;
|
||||
});
|
||||
} else if (options.search == null || options.search.trim() === "") {
|
||||
ciphers = ciphers.filter((c) => options.trash === c.isDeleted);
|
||||
ciphers = ciphers.filter((c) => this.matchesStateOptions(c, options));
|
||||
}
|
||||
|
||||
if (options.search != null && options.search.trim() !== "") {
|
||||
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash);
|
||||
ciphers = this.searchService.searchCiphersBasic(
|
||||
ciphers,
|
||||
options.search,
|
||||
options.trash,
|
||||
options.archived,
|
||||
);
|
||||
}
|
||||
|
||||
ciphers = await this.cliRestrictedItemTypesService.filterRestrictedCiphers(ciphers);
|
||||
@@ -287,6 +302,17 @@ export class ListCommand {
|
||||
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));
|
||||
return Response.success(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cipher passes either the trash or the archive options.
|
||||
* @returns true if the cipher passes *any* of the filters
|
||||
*/
|
||||
private matchesStateOptions(c: CipherView, options: Options): boolean {
|
||||
const passesTrashFilter = options.trash && c.isDeleted;
|
||||
const passesArchivedFilter = options.archived && c.isArchived;
|
||||
|
||||
return passesTrashFilter || passesArchivedFilter;
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
@@ -296,6 +322,7 @@ class Options {
|
||||
search: string;
|
||||
url: string;
|
||||
trash: boolean;
|
||||
archived: boolean;
|
||||
|
||||
constructor(passedOptions: Record<string, any>) {
|
||||
this.organizationId = passedOptions?.organizationid || passedOptions?.organizationId;
|
||||
@@ -304,5 +331,6 @@ class Options {
|
||||
this.search = passedOptions?.search;
|
||||
this.url = passedOptions?.url;
|
||||
this.trash = CliUtils.convertBooleanOption(passedOptions?.trash);
|
||||
this.archived = CliUtils.convertBooleanOption(passedOptions?.archived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,14 @@ import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
|
||||
@@ -12,6 +18,8 @@ export class RestoreCommand {
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
private cipherAuthorizationService: CipherAuthorizationService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async run(object: string, id: string): Promise<Response> {
|
||||
@@ -30,10 +38,23 @@ export class RestoreCommand {
|
||||
private async restoreCipher(id: string) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
const isArchivedVaultEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM19148_InnovationArchive),
|
||||
);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
if (cipher.archivedDate && isArchivedVaultEnabled) {
|
||||
return this.restoreArchivedCipher(cipher, activeUserId);
|
||||
} else {
|
||||
return this.restoreDeletedCipher(cipher, activeUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Restores a cipher from the trash. */
|
||||
private async restoreDeletedCipher(cipher: Cipher, userId: UserId) {
|
||||
if (cipher.deletedDate == null) {
|
||||
return Response.badRequest("Cipher is not in trash.");
|
||||
}
|
||||
@@ -47,7 +68,17 @@ export class RestoreCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(id, activeUserId);
|
||||
await this.cipherService.restoreWithServer(cipher.id, userId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore a cipher from the archive vault */
|
||||
private async restoreArchivedCipher(cipher: Cipher, userId: UserId) {
|
||||
try {
|
||||
await this.cipherArchiveService.unarchiveWithServer(cipher.id as CipherId, userId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ServeCommand {
|
||||
.use(koaBodyParser())
|
||||
.use(koaJson({ pretty: false, param: "pretty" }));
|
||||
|
||||
this.serveConfigurator.configureRouter(router);
|
||||
await this.serveConfigurator.configureRouter(router);
|
||||
|
||||
server.use(router.routes()).use(router.allowedMethods());
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import * as koaRouter from "@koa/router";
|
||||
import * as koa from "koa";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||
import { LockCommand } from "./auth/commands/lock.command";
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
SendListCommand,
|
||||
SendRemovePasswordCommand,
|
||||
} from "./tools/send";
|
||||
import { ArchiveCommand } from "./vault/archive.command";
|
||||
import { CreateCommand } from "./vault/create.command";
|
||||
import { DeleteCommand } from "./vault/delete.command";
|
||||
import { SyncCommand } from "./vault/sync.command";
|
||||
@@ -40,6 +43,7 @@ export class OssServeConfigurator {
|
||||
private statusCommand: StatusCommand;
|
||||
private syncCommand: SyncCommand;
|
||||
private deleteCommand: DeleteCommand;
|
||||
private archiveCommand: ArchiveCommand;
|
||||
private confirmCommand: ConfirmCommand;
|
||||
private restoreCommand: RestoreCommand;
|
||||
private lockCommand: LockCommand;
|
||||
@@ -81,6 +85,7 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.keyService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
);
|
||||
this.createCommand = new CreateCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
@@ -104,6 +109,7 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.policyService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
this.generateCommand = new GenerateCommand(
|
||||
this.serviceContainer.passwordGenerationService,
|
||||
@@ -127,6 +133,13 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
);
|
||||
this.archiveCommand = new ArchiveCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
this.confirmCommand = new ConfirmCommand(
|
||||
this.serviceContainer.apiService,
|
||||
this.serviceContainer.keyService,
|
||||
@@ -140,6 +153,8 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.configService,
|
||||
);
|
||||
this.shareCommand = new ShareCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
@@ -199,7 +214,7 @@ export class OssServeConfigurator {
|
||||
);
|
||||
}
|
||||
|
||||
configureRouter(router: koaRouter) {
|
||||
async configureRouter(router: koaRouter) {
|
||||
router.get("/generate", async (ctx, next) => {
|
||||
const response = await this.generateCommand.run(ctx.request.query);
|
||||
this.processResponse(ctx.response, response);
|
||||
@@ -401,6 +416,23 @@ export class OssServeConfigurator {
|
||||
this.processResponse(ctx.response, response);
|
||||
await next();
|
||||
});
|
||||
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
router.post("/archive/:object/:id", async (ctx, next) => {
|
||||
if (await this.errorIfLocked(ctx.response)) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
let response: Response = null;
|
||||
response = await this.archiveCommand.run(ctx.params.object, ctx.params.id);
|
||||
this.processResponse(ctx.response, response);
|
||||
await next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected processResponse(res: koa.Response, commandResponse: Response) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { program, Command, OptionValues } from "commander";
|
||||
import { firstValueFrom, of, switchMap } from "rxjs";
|
||||
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { LockCommand } from "./auth/commands/lock.command";
|
||||
import { LoginCommand } from "./auth/commands/login.command";
|
||||
@@ -26,6 +27,10 @@ const writeLn = CliUtils.writeLn;
|
||||
|
||||
export class Program extends BaseProgram {
|
||||
async register() {
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
program
|
||||
.option("--pretty", "Format output. JSON is tabbed with two spaces.")
|
||||
.option("--raw", "Return raw output instead of a descriptive message.")
|
||||
@@ -94,6 +99,9 @@ export class Program extends BaseProgram {
|
||||
" bw edit folder c7c7b60b-9c61-40f2-8ccd-36c49595ed72 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg==",
|
||||
);
|
||||
writeLn(" bw delete item 99ee88d2-6046-4ea7-92c2-acac464b1412");
|
||||
if (isArchivedEnabled) {
|
||||
writeLn(" bw archive item 99ee88d2-6046-4ea7-92c2-acac464b1412");
|
||||
}
|
||||
writeLn(" bw generate -lusn --length 18");
|
||||
writeLn(" bw config server https://bitwarden.example.com");
|
||||
writeLn(" bw send -f ./file.ext");
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function registerOssPrograms(serviceContainer: ServiceContainer) {
|
||||
await program.register();
|
||||
|
||||
const vaultProgram = new VaultProgram(serviceContainer);
|
||||
vaultProgram.register();
|
||||
await vaultProgram.register();
|
||||
|
||||
const sendProgram = new SendProgram(serviceContainer);
|
||||
sendProgram.register();
|
||||
|
||||
@@ -125,6 +125,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import {
|
||||
@@ -132,6 +133,7 @@ import {
|
||||
DefaultCipherAuthorizationService,
|
||||
} from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
|
||||
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
@@ -303,6 +305,7 @@ export class ServiceContainer {
|
||||
cipherEncryptionService: CipherEncryptionService;
|
||||
restrictedItemTypesService: RestrictedItemTypesService;
|
||||
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
|
||||
cipherArchiveService: CipherArchiveService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -730,6 +733,13 @@ export class ServiceContainer {
|
||||
this.messagingService,
|
||||
);
|
||||
|
||||
this.cipherArchiveService = new DefaultCipherArchiveService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.billingAccountProfileStateService,
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.folderService = new FolderService(
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// @ts-strict-ignore
|
||||
import { program, Command } from "commander";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||
import { BaseProgram } from "./base-program";
|
||||
@@ -13,25 +15,34 @@ import { Response } from "./models/response";
|
||||
import { ExportCommand } from "./tools/export.command";
|
||||
import { ImportCommand } from "./tools/import.command";
|
||||
import { CliUtils } from "./utils";
|
||||
import { ArchiveCommand } from "./vault/archive.command";
|
||||
import { CreateCommand } from "./vault/create.command";
|
||||
import { DeleteCommand } from "./vault/delete.command";
|
||||
|
||||
const writeLn = CliUtils.writeLn;
|
||||
|
||||
export class VaultProgram extends BaseProgram {
|
||||
register() {
|
||||
async register() {
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
program
|
||||
.addCommand(this.listCommand())
|
||||
.addCommand(this.listCommand(isArchivedEnabled))
|
||||
.addCommand(this.getCommand())
|
||||
.addCommand(this.createCommand())
|
||||
.addCommand(this.editCommand())
|
||||
.addCommand(this.deleteCommand())
|
||||
.addCommand(this.restoreCommand())
|
||||
.addCommand(this.restoreCommand(isArchivedEnabled))
|
||||
.addCommand(this.shareCommand("move", false))
|
||||
.addCommand(this.confirmCommand())
|
||||
.addCommand(this.importCommand())
|
||||
.addCommand(this.exportCommand())
|
||||
.addCommand(this.shareCommand("share", true));
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
program.addCommand(this.archiveCommand());
|
||||
}
|
||||
}
|
||||
|
||||
private validateObject(requestedObject: string, validObjects: string[]): boolean {
|
||||
@@ -42,7 +53,7 @@ export class VaultProgram extends BaseProgram {
|
||||
Response.badRequest(
|
||||
'Unknown object "' +
|
||||
requestedObject +
|
||||
'". Allowed objects are ' +
|
||||
'". Allowed objects are: ' +
|
||||
validObjects.join(", ") +
|
||||
".",
|
||||
),
|
||||
@@ -51,7 +62,7 @@ export class VaultProgram extends BaseProgram {
|
||||
return success;
|
||||
}
|
||||
|
||||
private listCommand(): Command {
|
||||
private listCommand(isArchivedEnabled: boolean): Command {
|
||||
const listObjects = [
|
||||
"items",
|
||||
"folders",
|
||||
@@ -61,7 +72,7 @@ export class VaultProgram extends BaseProgram {
|
||||
"organizations",
|
||||
];
|
||||
|
||||
return new Command("list")
|
||||
const command = new Command("list")
|
||||
.argument("<object>", "Valid objects are: " + listObjects.join(", "))
|
||||
.description("List an array of objects from the vault.")
|
||||
.option("--search <search>", "Perform a search on the listed objects.")
|
||||
@@ -94,6 +105,9 @@ export class VaultProgram extends BaseProgram {
|
||||
" bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull",
|
||||
);
|
||||
writeLn(" bw list items --trash");
|
||||
if (isArchivedEnabled) {
|
||||
writeLn(" bw list items --archived");
|
||||
}
|
||||
writeLn(" bw list folders --search email");
|
||||
writeLn(" bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2");
|
||||
writeLn("", true);
|
||||
@@ -116,11 +130,18 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.keyService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
);
|
||||
const response = await command.run(object, cmd);
|
||||
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
command.option("--archived", "Filter items that are archived.");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private getCommand(): Command {
|
||||
@@ -286,6 +307,7 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.policyService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
const response = await command.run(object, id, encodedJson, cmd);
|
||||
this.processResponse(response);
|
||||
@@ -336,12 +358,41 @@ export class VaultProgram extends BaseProgram {
|
||||
});
|
||||
}
|
||||
|
||||
private restoreCommand(): Command {
|
||||
private archiveCommand(): Command {
|
||||
const archiveObjects = ["item"];
|
||||
return new Command("archive")
|
||||
.argument("<object>", "Valid objects are: " + archiveObjects.join(", "))
|
||||
.argument("<id>", "Object's globally unique `id`.")
|
||||
.description("Archive an object from the vault.")
|
||||
.on("--help", () => {
|
||||
writeLn("\n Examples:");
|
||||
writeLn("");
|
||||
writeLn(" bw archive item 7063feab-4b10-472e-b64c-785e2b870b92");
|
||||
writeLn("", true);
|
||||
})
|
||||
.action(async (object, id) => {
|
||||
if (!this.validateObject(object, archiveObjects)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.exitIfLocked();
|
||||
const command = new ArchiveCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
const response = await command.run(object, id);
|
||||
this.processResponse(response);
|
||||
});
|
||||
}
|
||||
|
||||
private restoreCommand(isArchivedEnabled: boolean): Command {
|
||||
const restoreObjects = ["item"];
|
||||
return new Command("restore")
|
||||
const command = new Command("restore")
|
||||
.argument("<object>", "Valid objects are: " + restoreObjects.join(", "))
|
||||
.argument("<id>", "Object's globally unique `id`.")
|
||||
.description("Restores an object from the trash.")
|
||||
.on("--help", () => {
|
||||
writeLn("\n Examples:");
|
||||
writeLn("");
|
||||
@@ -358,10 +409,20 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.configService,
|
||||
);
|
||||
const response = await command.run(object, id);
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
command.description("Restores an object from the trash or archive.");
|
||||
} else {
|
||||
command.description("Restores an object from the trash.");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private shareCommand(commandName: string, deprecated: boolean): Command {
|
||||
|
||||
109
apps/cli/src/vault/archive.command.ts
Normal file
109
apps/cli/src/vault/archive.command.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
|
||||
export class ArchiveCommand {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async run(object: string, id: string): Promise<Response> {
|
||||
const featureFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
if (!featureFlagEnabled) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
id = id.toLowerCase();
|
||||
}
|
||||
|
||||
const normalizedObject = object.toLowerCase();
|
||||
|
||||
if (normalizedObject === "item") {
|
||||
return this.archiveCipher(id);
|
||||
}
|
||||
|
||||
return Response.badRequest("Unknown object.");
|
||||
}
|
||||
|
||||
private async archiveCipher(cipherId: string) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
const cipherView = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
|
||||
const { canArchive, errorMessage } = await this.userCanArchiveCipher(cipherView, activeUserId);
|
||||
|
||||
if (!canArchive) {
|
||||
return Response.error(errorMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherArchiveService.archiveWithServer(cipherView.id as CipherId, activeUserId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the user can archive the given cipher.
|
||||
* When the user cannot archive the cipher, an appropriate error message is provided.
|
||||
*/
|
||||
private async userCanArchiveCipher(
|
||||
cipher: CipherView,
|
||||
userId: UserId,
|
||||
): Promise<
|
||||
{ canArchive: true; errorMessage?: never } | { canArchive: false; errorMessage: string }
|
||||
> {
|
||||
const hasPremiumFromAnySource = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId),
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case !hasPremiumFromAnySource: {
|
||||
return {
|
||||
canArchive: false,
|
||||
errorMessage: "Premium status is required to use this feature.",
|
||||
};
|
||||
}
|
||||
case CipherViewLikeUtils.isArchived(cipher): {
|
||||
return { canArchive: false, errorMessage: "Item is already archived." };
|
||||
}
|
||||
case CipherViewLikeUtils.isDeleted(cipher): {
|
||||
return {
|
||||
canArchive: false,
|
||||
errorMessage: "Item is in the trash, the item must be restored before archiving.",
|
||||
};
|
||||
}
|
||||
case cipher.organizationId != null: {
|
||||
return { canArchive: false, errorMessage: "Cannot archive items in an organization." };
|
||||
}
|
||||
default:
|
||||
return { canArchive: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { CipherArchiveService } from "@bitwarden/vault";
|
||||
|
||||
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component";
|
||||
import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
|
||||
@@ -19,12 +19,12 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { CipherArchiveService } from "@bitwarden/vault";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
||||
|
||||
@@ -54,6 +54,7 @@ import { uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.se
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
@@ -77,7 +78,6 @@ import {
|
||||
AttachmentDialogCloseResult,
|
||||
AttachmentDialogResult,
|
||||
AttachmentsV2Component,
|
||||
CipherArchiveService,
|
||||
CipherFormConfig,
|
||||
CollectionAssignmentResult,
|
||||
DecryptionFailureDialogComponent,
|
||||
|
||||
@@ -16,9 +16,9 @@ export class BitServeConfigurator extends OssServeConfigurator {
|
||||
super(serviceContainer);
|
||||
}
|
||||
|
||||
override configureRouter(router: koaRouter): void {
|
||||
override async configureRouter(router: koaRouter): Promise<void> {
|
||||
// Register OSS endpoints
|
||||
super.configureRouter(router);
|
||||
await super.configureRouter(router);
|
||||
|
||||
// Register bit endpoints
|
||||
this.serveDeviceApprovals(router);
|
||||
|
||||
@@ -264,6 +264,7 @@ import {
|
||||
InternalSendService,
|
||||
SendService as SendServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
|
||||
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
||||
@@ -284,6 +285,7 @@ import {
|
||||
DefaultCipherAuthorizationService,
|
||||
} from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
|
||||
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
@@ -296,7 +298,6 @@ import { DefaultTaskService, TaskService } from "@bitwarden/common/vault/tasks";
|
||||
import {
|
||||
AnonLayoutWrapperDataService,
|
||||
DefaultAnonLayoutWrapperDataService,
|
||||
DialogService,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
@@ -345,11 +346,7 @@ import {
|
||||
import { SafeInjectionToken } from "@bitwarden/ui-common";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
CipherArchiveService,
|
||||
DefaultCipherArchiveService,
|
||||
PasswordRepromptService,
|
||||
} from "@bitwarden/vault";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
IndividualVaultExportService,
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
@@ -1652,8 +1649,6 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [
|
||||
CipherServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
DialogService,
|
||||
PasswordRepromptService,
|
||||
BillingAccountProfileStateService,
|
||||
ConfigService,
|
||||
],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
|
||||
export abstract class CipherArchiveService {
|
||||
@@ -10,5 +9,4 @@ export abstract class CipherArchiveService {
|
||||
abstract showArchiveVault$(userId: UserId): Observable<boolean>;
|
||||
abstract archiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise<void>;
|
||||
abstract unarchiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise<void>;
|
||||
abstract canInteract(cipher: CipherView): Promise<boolean>;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export abstract class SearchService {
|
||||
ciphers: C[],
|
||||
query: string,
|
||||
deleted?: boolean,
|
||||
archived?: boolean,
|
||||
): C[];
|
||||
abstract searchSends(sends: SendView[], query: string): SendView[];
|
||||
}
|
||||
|
||||
@@ -11,21 +11,14 @@ import {
|
||||
CipherBulkArchiveRequest,
|
||||
CipherBulkUnarchiveRequest,
|
||||
} from "@bitwarden/common/vault/models/request/cipher-bulk-archive.request";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherListView } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { DecryptionFailureDialogComponent } from "../components/decryption-failure-dialog/decryption-failure-dialog.component";
|
||||
|
||||
import { DefaultCipherArchiveService } from "./default-cipher-archive.service";
|
||||
import { PasswordRepromptService } from "./password-reprompt.service";
|
||||
|
||||
describe("DefaultCipherArchiveService", () => {
|
||||
let service: DefaultCipherArchiveService;
|
||||
let mockCipherService: jest.Mocked<CipherService>;
|
||||
let mockApiService: jest.Mocked<ApiService>;
|
||||
let mockDialogService: jest.Mocked<DialogService>;
|
||||
let mockPasswordRepromptService: jest.Mocked<PasswordRepromptService>;
|
||||
let mockBillingAccountProfileStateService: jest.Mocked<BillingAccountProfileStateService>;
|
||||
let mockConfigService: jest.Mocked<ConfigService>;
|
||||
|
||||
@@ -35,16 +28,12 @@ describe("DefaultCipherArchiveService", () => {
|
||||
beforeEach(() => {
|
||||
mockCipherService = mock<CipherService>();
|
||||
mockApiService = mock<ApiService>();
|
||||
mockDialogService = mock<DialogService>();
|
||||
mockPasswordRepromptService = mock<PasswordRepromptService>();
|
||||
mockBillingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
mockConfigService = mock<ConfigService>();
|
||||
|
||||
service = new DefaultCipherArchiveService(
|
||||
mockCipherService,
|
||||
mockApiService,
|
||||
mockDialogService,
|
||||
mockPasswordRepromptService,
|
||||
mockBillingAccountProfileStateService,
|
||||
mockConfigService,
|
||||
);
|
||||
@@ -244,46 +233,4 @@ describe("DefaultCipherArchiveService", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canInteract", () => {
|
||||
let mockCipherView: CipherView;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCipherView = {
|
||||
id: cipherId,
|
||||
decryptionFailure: false,
|
||||
} as unknown as CipherView;
|
||||
});
|
||||
|
||||
it("should return false and open dialog when cipher has decryption failure", async () => {
|
||||
mockCipherView.decryptionFailure = true;
|
||||
const openSpy = jest.spyOn(DecryptionFailureDialogComponent, "open").mockImplementation();
|
||||
|
||||
const result = await service.canInteract(mockCipherView);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(openSpy).toHaveBeenCalledWith(mockDialogService, {
|
||||
cipherIds: [cipherId],
|
||||
});
|
||||
});
|
||||
|
||||
it("should return password reprompt result when no decryption failure", async () => {
|
||||
mockPasswordRepromptService.passwordRepromptCheck.mockResolvedValue(true);
|
||||
|
||||
const result = await service.canInteract(mockCipherView);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockPasswordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(
|
||||
mockCipherView,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false when password reprompt fails", async () => {
|
||||
mockPasswordRepromptService.passwordRepromptCheck.mockResolvedValue(false);
|
||||
|
||||
const result = await service.canInteract(mockCipherView);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,27 +12,21 @@ import {
|
||||
CipherBulkUnarchiveRequest,
|
||||
} from "@bitwarden/common/vault/models/request/cipher-bulk-archive.request";
|
||||
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { CipherArchiveService } from "../abstractions/cipher-archive.service";
|
||||
import { DecryptionFailureDialogComponent } from "../components/decryption-failure-dialog/decryption-failure-dialog.component";
|
||||
|
||||
import { PasswordRepromptService } from "./password-reprompt.service";
|
||||
|
||||
export class DefaultCipherArchiveService implements CipherArchiveService {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private apiService: ApiService,
|
||||
private dialogService: DialogService,
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Observable that contains the list of ciphers that have been archived.
|
||||
*/
|
||||
@@ -125,21 +119,4 @@ export class DefaultCipherArchiveService implements CipherArchiveService {
|
||||
|
||||
await this.cipherService.replace(currentCiphers, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is able to interact with the cipher
|
||||
* (password re-prompt / decryption failure checks).
|
||||
* @param cipher
|
||||
* @private
|
||||
*/
|
||||
async canInteract(cipher: CipherView) {
|
||||
if (cipher.decryptionFailure) {
|
||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||
cipherIds: [cipher.id as CipherId],
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return await this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||
}
|
||||
}
|
||||
@@ -296,12 +296,20 @@ export class SearchService implements SearchServiceAbstraction {
|
||||
return results;
|
||||
}
|
||||
|
||||
searchCiphersBasic<C extends CipherViewLike>(ciphers: C[], query: string, deleted = false) {
|
||||
searchCiphersBasic<C extends CipherViewLike>(
|
||||
ciphers: C[],
|
||||
query: string,
|
||||
deleted = false,
|
||||
archived = false,
|
||||
) {
|
||||
query = SearchService.normalizeSearchQuery(query.trim().toLowerCase());
|
||||
return ciphers.filter((c) => {
|
||||
if (deleted !== CipherViewLikeUtils.isDeleted(c)) {
|
||||
return false;
|
||||
}
|
||||
if (archived !== CipherViewLikeUtils.isArchived(c)) {
|
||||
return false;
|
||||
}
|
||||
if (c.name != null && c.name.toLowerCase().indexOf(query) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,5 +27,3 @@ export { SshImportPromptService } from "./services/ssh-import-prompt.service";
|
||||
|
||||
export * from "./abstractions/change-login-password.service";
|
||||
export * from "./services/default-change-login-password.service";
|
||||
export * from "./abstractions/cipher-archive.service";
|
||||
export * from "./services/default-cipher-archive.service";
|
||||
|
||||
242
package-lock.json
generated
242
package-lock.json
generated
@@ -45,7 +45,7 @@
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "27.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.16.1",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
@@ -208,7 +208,7 @@
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "27.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.16.1",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
@@ -2586,54 +2586,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
|
||||
"integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
||||
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^2.1.4",
|
||||
"@csstools/css-color-parser": "^3.1.0",
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4",
|
||||
"lru-cache": "^11.2.1"
|
||||
"@csstools/css-calc": "^2.1.3",
|
||||
"@csstools/css-color-parser": "^3.0.9",
|
||||
"@csstools/css-parser-algorithms": "^3.0.4",
|
||||
"@csstools/css-tokenizer": "^3.0.3",
|
||||
"lru-cache": "^10.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
||||
"version": "11.2.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "6.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz",
|
||||
"integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.1.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
|
||||
"version": "11.2.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"license": "MIT"
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
@@ -5409,9 +5378,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
|
||||
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5451,9 +5420,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
|
||||
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
|
||||
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5466,7 +5435,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^5.1.0",
|
||||
"@csstools/color-helpers": "^5.0.2",
|
||||
"@csstools/css-calc": "^2.1.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5499,28 +5468,6 @@
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
|
||||
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
|
||||
@@ -16972,15 +16919,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
|
||||
@@ -19171,19 +19109,6 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||
"integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.12.2",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
@@ -19225,17 +19150,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cssstyle": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz",
|
||||
"integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz",
|
||||
"integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^4.0.3",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.0.14",
|
||||
"css-tree": "^3.1.0"
|
||||
"@asamuzakjp/css-color": "^3.2.0",
|
||||
"rrweb-cssom": "^0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
@@ -19259,16 +19183,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
|
||||
"integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
|
||||
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^15.0.0"
|
||||
"whatwg-url": "^14.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
@@ -26986,34 +26910,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "27.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz",
|
||||
"integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/dom-selector": "^6.5.4",
|
||||
"cssstyle": "^5.3.0",
|
||||
"data-urls": "^6.0.0",
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"html-encoding-sniffer": "^4.0.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"parse5": "^7.3.0",
|
||||
"nwsapi": "^2.2.16",
|
||||
"parse5": "^7.2.1",
|
||||
"rrweb-cssom": "^0.8.0",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"tough-cookie": "^5.1.1",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^8.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^3.1.1",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^15.0.0",
|
||||
"ws": "^8.18.2",
|
||||
"whatwg-url": "^14.1.1",
|
||||
"ws": "^8.18.0",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
@@ -27025,38 +26949,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/tldts": {
|
||||
"version": "7.0.16",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz",
|
||||
"integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==",
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.0.16"
|
||||
"tldts-core": "^6.1.86"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/tldts-core": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
|
||||
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom/node_modules/tough-cookie": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
|
||||
"integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
|
||||
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
"tldts": "^6.1.32"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/webidl-conversions": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
|
||||
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@@ -29016,12 +28937,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||
"integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
@@ -30353,6 +30268,7 @@
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -31508,7 +31424,6 @@
|
||||
"version": "2.2.20",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
|
||||
"integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nx": {
|
||||
@@ -33498,6 +33413,7 @@
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -34795,6 +34711,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -36154,6 +36071,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -37679,9 +37597,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.0.16",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz",
|
||||
"integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==",
|
||||
"version": "7.0.9",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.9.tgz",
|
||||
"integrity": "sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
@@ -37755,15 +37673,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-dump": {
|
||||
@@ -39949,7 +39867,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -40890,25 +40807,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
|
||||
"integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.0"
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url/node_modules/webidl-conversions": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
|
||||
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "27.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.16.1",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
|
||||
Reference in New Issue
Block a user