From bcddf575a1935e2c29189f0707587afaf82f5f84 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:56:09 -0500 Subject: [PATCH 01/23] [PM-11306] Disable Animation Setting (#11157) * add appearance option to disable animations * add check to only show the form after it's populated to avoid flash of inaccurate values * switch to form loading while waiting for form values --- apps/browser/src/_locales/en/messages.json | 3 +++ .../settings/appearance-v2.component.html | 9 ++++++-- .../settings/appearance-v2.component.spec.ts | 17 +++++++++++++- .../popup/settings/appearance-v2.component.ts | 22 +++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 8eae45e6b38..900c3b37caa 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4201,6 +4201,9 @@ "enableAnimations": { "message": "Enable animations" }, + "showAnimations": { + "message": "Show animations" + }, "addAccount": { "message": "Add account" }, diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html index 565699a6f5b..b267e1c5cb2 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.html +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -1,4 +1,4 @@ - + @@ -23,10 +23,15 @@ {{ "showNumberOfAutofillSuggestions" | i18n }} - + {{ "enableFavicon" | i18n }} + + + + {{ "showAnimations" | i18n }} + diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts index 69186359e2b..bbd210b65a3 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -5,6 +5,7 @@ import { BehaviorSubject } from "rxjs"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -41,14 +42,17 @@ describe("AppearanceV2Component", () => { const showFavicons$ = new BehaviorSubject(true); const enableBadgeCounter$ = new BehaviorSubject(true); const selectedTheme$ = new BehaviorSubject(ThemeType.Nord); + const enableRoutingAnimation$ = new BehaviorSubject(true); const setSelectedTheme = jest.fn().mockResolvedValue(undefined); const setShowFavicons = jest.fn().mockResolvedValue(undefined); const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined); + const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined); beforeEach(async () => { setSelectedTheme.mockClear(); setShowFavicons.mockClear(); setEnableBadgeCounter.mockClear(); + setEnableRoutingAnimation.mockClear(); await TestBed.configureTestingModule({ imports: [AppearanceV2Component], @@ -58,11 +62,15 @@ describe("AppearanceV2Component", () => { { provide: MessagingService, useValue: mock() }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: DomainSettingsService, useValue: { showFavicons$, setShowFavicons } }, + { provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } }, + { + provide: AnimationControlService, + useValue: { enableRoutingAnimation$, setEnableRoutingAnimation }, + }, { provide: BadgeSettingsServiceAbstraction, useValue: { enableBadgeCounter$, setEnableBadgeCounter }, }, - { provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } }, ], }) .overrideComponent(AppearanceV2Component, { @@ -82,6 +90,7 @@ describe("AppearanceV2Component", () => { it("populates the form with the user's current settings", () => { expect(component.appearanceForm.value).toEqual({ + enableAnimations: true, enableFavicon: true, enableBadgeCounter: true, theme: ThemeType.Nord, @@ -106,5 +115,11 @@ describe("AppearanceV2Component", () => { expect(setEnableBadgeCounter).toHaveBeenCalledWith(false); }); + + it("updates the animation setting", () => { + component.appearanceForm.controls.enableAnimations.setValue(false); + + expect(setEnableRoutingAnimation).toHaveBeenCalledWith(false); + }); }); }); diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 12f5c540409..9d600ec83e8 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -7,6 +7,7 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; @@ -41,8 +42,12 @@ export class AppearanceV2Component implements OnInit { enableFavicon: false, enableBadgeCounter: true, theme: ThemeType.System, + enableAnimations: true, }); + /** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */ + formLoading = true; + /** Available theme options */ themeOptions: { name: string; value: ThemeType }[]; @@ -53,6 +58,7 @@ export class AppearanceV2Component implements OnInit { private themeStateService: ThemeStateService, private formBuilder: FormBuilder, private destroyRef: DestroyRef, + private animationControlService: AnimationControlService, i18nService: I18nService, ) { this.themeOptions = [ @@ -66,14 +72,20 @@ export class AppearanceV2Component implements OnInit { const enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$); const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); const theme = await firstValueFrom(this.themeStateService.selectedTheme$); + const enableAnimations = await firstValueFrom( + this.animationControlService.enableRoutingAnimation$, + ); // Set initial values for the form this.appearanceForm.setValue({ enableFavicon, enableBadgeCounter, theme, + enableAnimations, }); + this.formLoading = false; + this.appearanceForm.controls.theme.valueChanges .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((newTheme) => { @@ -91,6 +103,12 @@ export class AppearanceV2Component implements OnInit { .subscribe((enableBadgeCounter) => { void this.updateBadgeCounter(enableBadgeCounter); }); + + this.appearanceForm.controls.enableAnimations.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableBadgeCounter) => { + void this.updateAnimations(enableBadgeCounter); + }); } async updateFavicon(enableFavicon: boolean) { @@ -105,4 +123,8 @@ export class AppearanceV2Component implements OnInit { async saveTheme(newTheme: ThemeType) { await this.themeStateService.setSelectedTheme(newTheme); } + + async updateAnimations(enableAnimations: boolean) { + await this.animationControlService.setEnableRoutingAnimation(enableAnimations); + } } From 08f0dadc2f370737db54e4bb69e1148b5b56ea7b Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:23:53 +0100 Subject: [PATCH 02/23] Resolve the bug on change payment method (#11202) --- .../change-plan-dialog.component.html | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index bd8ae20c92e..27d51af92b3 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -332,20 +332,16 @@

{{ "paymentMethod" | i18n }}

-

+

- {{ billing?.paymentSource?.description }} - {{ - "changePaymentMethod" | i18n - }} - -

-

- - {{ paymentSource?.description }} - {{ - "changePaymentMethod" | i18n - }} + {{ + deprecateStripeSourcesAPI + ? paymentSource?.description + : billing?.paymentSource?.description + }} + + {{ "changePaymentMethod" | i18n }} +

Date: Mon, 23 Sep 2024 16:39:57 -0400 Subject: [PATCH 03/23] Add Retries to `get` (#11176) --- .../browser/src/background/main.background.ts | 2 +- .../services/browser-local-storage.service.ts | 61 ++++++++++++++++++- .../src/popup/services/services.module.ts | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 18883a5fe5e..7111b34875b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -419,7 +419,7 @@ export default class MainBackground { this.logService = new ConsoleLogService(isDev); this.cryptoFunctionService = new WebCryptoFunctionService(self); this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); - this.storageService = new BrowserLocalStorageService(); + this.storageService = new BrowserLocalStorageService(this.logService); this.intraprocessMessagingSubject = new Subject>>(); diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index 61a2653f137..9c315b7f6f4 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -1,10 +1,67 @@ -import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import AbstractChromeStorageService, { + SerializedValue, +} from "./abstractions/abstract-chrome-storage-api.service"; export default class BrowserLocalStorageService extends AbstractChromeStorageService { - constructor() { + constructor(private readonly logService: LogService) { super(chrome.storage.local); } + override async get(key: string): Promise { + return await this.getWithRetries(key, 0); + } + + private async getWithRetries(key: string, retryNum: number): Promise { + // See: https://github.com/EFForg/privacybadger/pull/2980 + const MAX_RETRIES = 5; + const WAIT_TIME = 200; + + const store = await this.getStore(key); + + if (store == null) { + if (retryNum >= MAX_RETRIES) { + throw new Error(`Failed to get a value for key '${key}', see logs for more details.`); + } + + retryNum++; + this.logService.warning(`Retrying attempt to get value for key '${key}' in ${WAIT_TIME}ms`); + await new Promise((resolve) => setTimeout(resolve, WAIT_TIME)); + return await this.getWithRetries(key, retryNum); + } + + // We have a store + return this.processGetObject(store[key] as T | SerializedValue); + } + + private async getStore(key: string) { + if (this.chromeStorageApi == null) { + this.logService.warning( + `chrome.storage.local was not initialized while retrieving key '${key}'.`, + ); + return null; + } + + return new Promise<{ [key: string]: unknown }>((resolve) => { + this.chromeStorageApi.get(key, (store) => { + if (chrome.runtime.lastError) { + this.logService.warning(`Failed to get value for key '${key}'`, chrome.runtime.lastError); + resolve(null); + return; + } + + if (store == null) { + this.logService.warning(`Store was empty while retrieving value for key '${key}'`); + resolve(null); + return; + } + + resolve(store); + }); + }); + } + async fillBuffer() { // Write 4MB of data in chrome.storage.local, log files will hold 4MB of data (by default) // before forcing a compaction. To force a compaction and have it remove previously saved data, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index efbe9ce6bf5..129744fd3bc 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -304,7 +304,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: AbstractStorageService, useClass: BrowserLocalStorageService, - deps: [], + deps: [LogService], }), safeProvider({ provide: AutofillServiceAbstraction, From 0db179e97443db9996b8a65459516584ef7ba6c7 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Mon, 23 Sep 2024 16:45:53 -0400 Subject: [PATCH 04/23] [PM-739] Using a space at the beginning of otpauth:// generate a wrong OTP (#11204) * Trimmed tariling whitespace from totp field * Trimmed tariling whitespace from totp field * Fix failing test --- libs/angular/src/vault/components/add-edit.component.ts | 5 +++++ .../login-details-section/login-details-section.component.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 45475440d0a..255d553a3ec 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -329,6 +329,11 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); } + // trim whitespace from the TOTP field + if (this.cipher.type === this.cipherType.Login && this.cipher.login.totp) { + this.cipher.login.totp = this.cipher.login.totp.trim(); + } + if (this.cipher.name == null || this.cipher.name === "") { this.platformUtilsService.showToast( "error", diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index eb34f820df8..691b05be2b4 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -125,7 +125,7 @@ export class LoginDetailsSectionComponent implements OnInit { Object.assign(cipher.login, { username: value.username, password: value.password, - totp: value.totp, + totp: value.totp?.trim(), } as LoginView); return cipher; From 4b9935b28c48e5824a19166170604ec3a6911593 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 23 Sep 2024 15:01:48 -0700 Subject: [PATCH 05/23] [PM-12528] AC Fix Collection Refresh (#11207) * [PM-12528] Ensure collections refresh when the refresh$ subject emits * [PM-12528] Cleanup all collections observable --- .../app/vault/org-vault/vault.component.ts | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index fce33a972bf..0a8e8769715 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -11,7 +11,6 @@ import { ActivatedRoute, Params, Router } from "@angular/router"; import { BehaviorSubject, combineLatest, - defer, firstValueFrom, lastValueFrom, Observable, @@ -283,27 +282,10 @@ export class VaultComponent implements OnInit, OnDestroy { this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); - this.allCollectionsWithoutUnassigned$ = combineLatest([ - organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))), - defer(() => this.collectionService.getAllDecrypted()), - ]).pipe( - map(([adminCollections, syncCollections]) => { - const syncCollectionDict = Object.fromEntries(syncCollections.map((c) => [c.id, c])); - - return adminCollections.map((collection) => { - const currentId: any = collection.id; - - const match = syncCollectionDict[currentId]; - - if (match) { - collection.manage = match.manage; - collection.readOnly = match.readOnly; - collection.hidePasswords = match.hidePasswords; - } - return collection; - }); - }), - shareReplay({ refCount: true, bufferSize: 1 }), + this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe( + switchMap(() => organizationId$), + switchMap((orgId) => this.collectionAdminService.getAll(orgId)), + shareReplay({ refCount: false, bufferSize: 1 }), ); this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe( @@ -367,7 +349,6 @@ export class VaultComponent implements OnInit, OnDestroy { map((ciphers) => { return Object.fromEntries(ciphers.map((c) => [c.id, c])); }), - shareReplay({ refCount: true, bufferSize: 1 }), ); const nestedCollections$ = allCollections$.pipe( From e691e2dadba1c959c92e6c8ca5a9b2afe0fd6858 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:48:23 +0000 Subject: [PATCH 06/23] Bumped client version(s) (#11211) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 8 ++++---- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 4d008b684cb..fff378c72b9 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.9.1", + "version": "2024.9.2", "scripts": { "build": "cross-env MANIFEST_VERSION=3 webpack", "build:mv2": "webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 2d7f46fa59a..35692dd5674 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.9.1", + "version": "2024.9.2", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 5e132774e6e..7bd40691768 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.9.1", + "version": "2024.9.2", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index b5317794c35..8ab2afc4129 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2024.9.0", + "version": "2024.9.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 40ce2fec5d9..c50e7ccbac4 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2024.9.1", + "version": "2024.9.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index ce08dfde2cd..3bd771c66af 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.9.1", + "version": "2024.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.9.1", + "version": "2024.9.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index e34641f1e09..29b3f8ed7d1 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2024.9.1", + "version": "2024.9.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/web/package.json b/apps/web/package.json index 37deab411b9..520c096a50c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.9.1", + "version": "2024.9.2", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index bfc943b55b5..b2058de0dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,11 +192,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2024.9.1" + "version": "2024.9.2" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2024.9.0", + "version": "2024.9.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.0.2", @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.9.1", + "version": "2024.9.2", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -246,7 +246,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.9.1" + "version": "2024.9.2" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From aa91a8d5ca0444b09bff6ff00cb4a13aff1cb789 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:12:27 -0700 Subject: [PATCH 07/23] fix send form defects (#11212) --- .../options/send-options.component.html | 13 +++++++---- .../send-details/send-details.component.html | 6 ++--- .../send-file-details.component.html | 22 ++++++++++--------- .../send-file-details.component.ts | 8 ++++++- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html index 9c5183aabd2..53483065b73 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html @@ -1,6 +1,6 @@ -

{{ "additionalOptions" | i18n }}

+

{{ "additionalOptions" | i18n }}

@@ -16,16 +16,21 @@ {{ "newPassword" | i18n }} - + {{ "sendPasswordDescV2" | i18n }} {{ "hideYourEmail" | i18n }} - + {{ "privateNote" | i18n }} - +
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html index e5b99828fe4..98f399760be 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html @@ -1,6 +1,6 @@ - -

{{ "sendDetails" | i18n }}

+ +

{{ "sendDetails" | i18n }}

@@ -34,7 +34,7 @@ > - + {{ "deletionDate" | i18n }}
-
{{ "file" | i18n }}
+
{{ "file" | i18n }}
{{ originalSendView.file.fileName }}
{{ originalSendView.file.sizeName }}
{{ "fileToShare" | i18n }} - - - {{ fileName || ("noFileChosen" | i18n) }} +
+ + + {{ fileName || ("noFileChosen" | i18n) }} +
Date: Tue, 24 Sep 2024 00:26:25 -0700 Subject: [PATCH 08/23] [PM-12505] - add delete send button to footer (#11187) * add delete send button to footer * add basic error handling * update copy. user bitAction * use arrow function. remove border class --- apps/browser/src/_locales/en/messages.json | 4 ++ .../add-edit/send-add-edit.component.html | 9 ++++ .../add-edit/send-add-edit.component.ts | 45 ++++++++++++++++++- .../send-list-items-container.component.ts | 2 +- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 900c3b37caa..8c08caec353 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2374,6 +2374,10 @@ "message": "Are you sure you want to delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "editSend": { "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html index 3e9a8d7c50d..7f723cc7364 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html @@ -13,5 +13,14 @@ +
diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index 49526bb032b..91311ab7e7a 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -8,8 +8,16 @@ import { map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendId } from "@bitwarden/common/types/guid"; -import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + DialogService, + IconButtonModule, + SearchModule, + ToastService, +} from "@bitwarden/components"; import { DefaultSendFormConfigService, SendFormConfig, @@ -58,6 +66,7 @@ export type AddEditQueryParams = Partial>; JslibModule, FormsModule, ButtonModule, + IconButtonModule, PopupPageComponent, PopupHeaderComponent, PopupFooterComponent, @@ -81,6 +90,9 @@ export class SendAddEditComponent { private location: Location, private i18nService: I18nService, private addEditFormConfigService: SendFormConfigService, + private sendApiService: SendApiService, + private toastService: ToastService, + private dialogService: DialogService, ) { this.subscribeToParams(); } @@ -92,6 +104,37 @@ export class SendAddEditComponent { this.location.back(); } + deleteSend = async () => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteSend" }, + content: { key: "deleteSendPermanentConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.sendApiService.delete(this.config.originalSend?.id); + } catch (e) { + this.toastService.showToast({ + variant: "error", + title: null, + message: e.message, + }); + return; + } + + this.location.back(); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedSend"), + }); + }; + /** * Subscribes to the route query parameters and builds the configuration based on the parameters. */ diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts index 2dd8078fd7a..a0f10c2cea9 100644 --- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts +++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts @@ -64,7 +64,7 @@ export class SendListItemsContainerComponent { async deleteSend(s: SendView): Promise { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "deleteSend" }, - content: { key: "deleteSendConfirmation" }, + content: { key: "deleteSendPermanentConfirmation" }, type: "warning", }); From d92b2cbea27feb62fd23d0bceed96fa588b6b519 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 24 Sep 2024 11:28:33 +0200 Subject: [PATCH 09/23] [PM-11477] Remove deprecated cryptoservice functions (#10854) * Remove deprecated cryptoservice functions * Use getUserkeyWithLegacySupport to get userkey * Fix tests * Fix tests * Fix tests * Remove unused cryptoservice instances * Fix build * Remove unused apiService in constructor * Fix encryption * Ensure passed in key is used if present * Fix sends and folders * Fix tests * Remove logged key * Fix import for account restricted keys --- .../browser/src/background/main.background.ts | 8 +++ .../background/nativeMessaging.background.ts | 6 ++- .../add-edit-folder-dialog.component.spec.ts | 35 ++++++++++-- .../add-edit-folder-dialog.component.ts | 9 +++- .../components/vault/attachments.component.ts | 3 ++ .../popup/components/vault/view.component.ts | 3 ++ .../settings/folder-add-edit.component.ts | 6 +++ apps/cli/src/commands/download.command.ts | 6 +-- apps/cli/src/commands/edit.command.ts | 9 +++- apps/cli/src/commands/get.command.ts | 8 +-- apps/cli/src/oss-serve-configurator.ts | 5 +- .../service-container/service-container.ts | 7 +++ .../src/tools/send/commands/get.command.ts | 6 +-- .../tools/send/commands/receive.command.ts | 8 +-- apps/cli/src/tools/send/send.program.ts | 7 +-- apps/cli/src/vault.program.ts | 3 ++ apps/cli/src/vault/create.command.ts | 8 ++- .../src/app/services/services.module.ts | 2 +- .../native-message-handler.service.ts | 8 +-- .../src/services/native-messaging.service.ts | 6 ++- .../vault/app/vault/attachments.component.ts | 3 ++ .../app/vault/folder-add-edit.component.ts | 6 +++ .../src/vault/app/vault/view.component.ts | 3 ++ .../emergency-access-attachments.component.ts | 3 ++ .../organization-plans.component.ts | 6 ++- .../tools/send/send-access-file.component.ts | 6 +-- .../vault/core/collection-admin.service.ts | 6 ++- .../individual-vault/attachments.component.ts | 3 ++ .../folder-add-edit.component.ts | 11 +++- .../vault/org-vault/attachments.component.ts | 3 ++ .../bit-cli/src/service-container.ts | 1 + .../organization-auth-request.service.spec.ts | 4 ++ .../organization-auth-request.service.ts | 4 +- .../device-approvals.component.ts | 8 ++- .../services/web-provider.service.ts | 2 +- .../src/services/jslib-services.module.ts | 10 +++- .../vault/components/attachments.component.ts | 6 ++- .../components/folder-add-edit.component.ts | 9 +++- .../src/vault/components/view.component.ts | 4 +- .../auth-request-login.strategy.spec.ts | 3 ++ .../login-strategies/login.strategy.spec.ts | 5 ++ .../common/login-strategies/login.strategy.ts | 2 + .../password-login.strategy.spec.ts | 4 ++ .../sso-login.strategy.spec.ts | 4 ++ .../user-api-login.strategy.spec.ts | 3 ++ .../webauthn-login.strategy.spec.ts | 14 +++-- .../webauthn-login.strategy.ts | 2 +- .../login-strategy.service.ts | 1 + .../platform/abstractions/crypto.service.ts | 32 ----------- .../src/platform/services/crypto.service.ts | 53 ------------------- .../src/tools/send/models/domain/send.spec.ts | 12 +++-- .../src/tools/send/models/domain/send.ts | 4 +- .../folder/folder.service.abstraction.ts | 2 +- .../src/vault/services/cipher.service.spec.ts | 16 +++++- .../src/vault/services/cipher.service.ts | 43 +++++++++------ .../src/vault/services/collection.service.ts | 5 +- .../services/folder/folder.service.spec.ts | 14 +++-- .../vault/services/folder/folder.service.ts | 7 ++- ...warden-password-protected-importer.spec.ts | 6 ++- .../src/components/import.component.ts | 2 + .../bitwarden/bitwarden-json-importer.ts | 14 +++-- .../bitwarden-password-protected-importer.ts | 8 +-- .../src/services/import.service.spec.ts | 4 ++ libs/importer/src/services/import.service.ts | 6 ++- .../src/services/base-vault-export.service.ts | 8 +-- .../individual-vault-export.service.spec.ts | 27 ++++++++-- .../individual-vault-export.service.ts | 15 ++++-- .../src/services/org-vault-export.service.ts | 8 +-- .../src/services/vault-export.service.spec.ts | 26 +++++++-- 69 files changed, 404 insertions(+), 197 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7111b34875b..43a6d5968e9 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -693,6 +693,7 @@ export default class MainBackground { this.collectionService = new CollectionService( this.cryptoService, + this.encryptService, this.i18nService, this.stateProvider, ); @@ -803,9 +804,11 @@ export default class MainBackground { this.cipherFileUploadService, this.configService, this.stateProvider, + this.accountService, ); this.folderService = new FolderService( this.cryptoService, + this.encryptService, this.i18nService, this.cipherService, this.stateProvider, @@ -977,6 +980,7 @@ export default class MainBackground { this.i18nService, this.collectionService, this.cryptoService, + this.encryptService, this.pinService, this.accountService, ); @@ -986,8 +990,10 @@ export default class MainBackground { this.cipherService, this.pinService, this.cryptoService, + this.encryptService, this.cryptoFunctionService, this.kdfConfigService, + this.accountService, ); this.organizationVaultExportService = new OrganizationVaultExportService( @@ -995,6 +1001,7 @@ export default class MainBackground { this.apiService, this.pinService, this.cryptoService, + this.encryptService, this.cryptoFunctionService, this.collectionService, this.kdfConfigService, @@ -1098,6 +1105,7 @@ export default class MainBackground { ); this.nativeMessagingBackground = new NativeMessagingBackground( this.cryptoService, + this.encryptService, this.cryptoFunctionService, this.runtimeBackground, this.messagingService, diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 8f2cac7915c..68a43fbdfe3 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -6,6 +6,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -73,6 +74,7 @@ export class NativeMessagingBackground { constructor( private cryptoService: CryptoService, + private encryptService: EncryptService, private cryptoFunctionService: CryptoFunctionService, private runtimeBackground: RuntimeBackground, private messagingService: MessagingService, @@ -227,7 +229,7 @@ export class NativeMessagingBackground { await this.secureCommunication(); } - return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); + return await this.encryptService.encrypt(JSON.stringify(message), this.sharedSecret); } getResponse(): Promise { @@ -273,7 +275,7 @@ export class NativeMessagingBackground { let message = rawMessage as ReceiveMessage; if (!this.platformUtilsService.isSafari()) { message = JSON.parse( - await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecret), + await this.encryptService.decryptToUtf8(rawMessage as EncString, this.sharedSecret), ); } diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts index 8453b4cc63e..156cc9d8195 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -1,9 +1,13 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { BehaviorSubject } from "rxjs"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { Folder } from "@bitwarden/common/vault/models/domain/folder"; @@ -25,6 +29,7 @@ describe("AddEditFolderDialogComponent", () => { const save = jest.fn().mockResolvedValue(null); const deleteFolder = jest.fn().mockResolvedValue(null); const openSimpleDialog = jest.fn().mockResolvedValue(true); + const getUserKeyWithLegacySupport = jest.fn().mockResolvedValue(""); const error = jest.fn(); const close = jest.fn(); const showToast = jest.fn(); @@ -41,12 +46,29 @@ describe("AddEditFolderDialogComponent", () => { close.mockClear(); showToast.mockClear(); + const userId = "" as UserId; + const accountInfo: AccountInfo = { + email: "", + emailVerified: true, + name: undefined, + }; + await TestBed.configureTestingModule({ imports: [AddEditFolderDialogComponent, NoopAnimationsModule], providers: [ { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: FolderService, useValue: { encrypt } }, { provide: FolderApiServiceAbstraction, useValue: { save, delete: deleteFolder } }, + { + provide: AccountService, + useValue: { activeAccount$: new BehaviorSubject({ id: userId, ...accountInfo }) }, + }, + { + provide: CryptoService, + useValue: { + getUserKeyWithLegacySupport, + }, + }, { provide: LogService, useValue: { error } }, { provide: ToastService, useValue: { showToast } }, { provide: DIALOG_DATA, useValue: dialogData }, @@ -82,7 +104,7 @@ describe("AddEditFolderDialogComponent", () => { const newFolder = new FolderView(); newFolder.name = "New Folder"; - expect(encrypt).toHaveBeenCalledWith(newFolder); + expect(encrypt).toHaveBeenCalledWith(newFolder, ""); expect(save).toHaveBeenCalled(); }); @@ -137,10 +159,13 @@ describe("AddEditFolderDialogComponent", () => { component.folderForm.controls.name.setValue("Edited Folder"); await component.submit(); - expect(encrypt).toHaveBeenCalledWith({ - ...dialogData.editFolderConfig.folder, - name: "Edited Folder", - }); + expect(encrypt).toHaveBeenCalledWith( + { + ...dialogData.editFolderConfig.folder, + name: "Edited Folder", + }, + "", + ); }); it("deletes the folder", async () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index 33263533990..4f793abb6a0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -11,8 +11,11 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -68,6 +71,8 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { private formBuilder: FormBuilder, private folderService: FolderService, private folderApiService: FolderApiServiceAbstraction, + private accountService: AccountService, + private cryptoService: CryptoService, private toastService: ToastService, private i18nService: I18nService, private logService: LogService, @@ -107,7 +112,9 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { this.folder.name = this.folderForm.controls.name.value; try { - const folder = await this.folderService.encrypt(this.folder); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId.id); + const folder = await this.folderService.encrypt(this.folder, userKey); await this.folderApiService.save(folder); this.toastService.showToast({ diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts index 75819689b44..1ab1ff428b2 100644 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault/attachments.component.ts @@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -28,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On cipherService: CipherService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, platformUtilsService: PlatformUtilsService, apiService: ApiService, private location: Location, @@ -44,6 +46,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On cipherService, i18nService, cryptoService, + encryptService, platformUtilsService, apiService, window, diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index f8e7de21dc8..a77a27046af 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -13,6 +13,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service" import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -80,6 +81,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro tokenService: TokenService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, platformUtilsService: PlatformUtilsService, auditService: AuditService, private route: ActivatedRoute, @@ -108,6 +110,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro tokenService, i18nService, cryptoService, + encryptService, platformUtilsService, auditService, window, diff --git a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts b/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts index b873735b460..3ef5fc73aa9 100644 --- a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts +++ b/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts @@ -4,6 +4,8 @@ import { ActivatedRoute, Router } from "@angular/router"; import { first } from "rxjs/operators"; import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -20,6 +22,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent implement constructor( folderService: FolderService, folderApiService: FolderApiServiceAbstraction, + accountService: AccountService, + cryptoService: CryptoService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, private router: Router, @@ -31,6 +35,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent implement super( folderService, folderApiService, + accountService, + cryptoService, i18nService, platformUtilsService, logService, diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index e417b7c3b88..f819875063d 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -1,6 +1,6 @@ import * as fet from "node-fetch"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; @@ -9,7 +9,7 @@ import { FileResponse } from "../models/response/file.response"; import { CliUtils } from "../utils"; export abstract class DownloadCommand { - constructor(protected cryptoService: CryptoService) {} + constructor(protected encryptService: EncryptService) {} protected async saveAttachmentToFile( url: string, @@ -26,7 +26,7 @@ export abstract class DownloadCommand { try { const encBuf = await EncArrayBuffer.fromResponse(response); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const decBuf = await this.encryptService.decryptToBytes(encBuf, key); if (process.env.BW_SERVE === "true") { const res = new FileResponse(Buffer.from(decBuf), fileName); return Response.success(res); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index bac1cce7c75..84ed7190a54 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -7,6 +7,7 @@ import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -25,6 +26,7 @@ export class EditCommand { private cipherService: CipherService, private folderService: FolderService, private cryptoService: CryptoService, + private encryptService: EncryptService, private apiService: ApiService, private folderApiService: FolderApiServiceAbstraction, private accountService: AccountService, @@ -139,7 +141,10 @@ export class EditCommand { let folderView = await folder.decrypt(); folderView = FolderExport.toView(req, folderView); - const encFolder = await this.folderService.encrypt(folderView); + + const activeUserId = await firstValueFrom(this.accountService.activeAccount$); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId.id); + const encFolder = await this.folderService.encrypt(folderView, userKey); try { await this.folderApiService.save(encFolder); const updatedFolder = await this.folderService.get(folder.id); @@ -187,7 +192,7 @@ export class EditCommand { (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), ); const request = new CollectionRequest(); - request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; + request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; request.users = users; diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 923187bfcd0..3b2b18c66e3 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -20,6 +20,7 @@ import { LoginExport } from "@bitwarden/common/models/export/login.export"; import { SecureNoteExport } from "@bitwarden/common/models/export/secure-note.export"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -56,7 +57,8 @@ export class GetCommand extends DownloadCommand { private collectionService: CollectionService, private totpService: TotpService, private auditService: AuditService, - cryptoService: CryptoService, + private cryptoService: CryptoService, + encryptService: EncryptService, private stateService: StateService, private searchService: SearchService, private apiService: ApiService, @@ -65,7 +67,7 @@ export class GetCommand extends DownloadCommand { private accountProfileService: BillingAccountProfileStateService, private accountService: AccountService, ) { - super(cryptoService); + super(encryptService); } async run(object: string, id: string, cmdOptions: Record): Promise { @@ -451,7 +453,7 @@ export class GetCommand extends DownloadCommand { const response = await this.apiService.getCollectionAccessDetails(options.organizationId, id); const decCollection = new CollectionView(response); - decCollection.name = await this.cryptoService.decryptToUtf8( + decCollection.name = await this.encryptService.decryptToUtf8( new EncString(response.name), orgKey, ); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index d7ef9ac871d..fd2a10975f3 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -57,6 +57,7 @@ export class OssServeConfigurator { this.serviceContainer.totpService, this.serviceContainer.auditService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, @@ -79,6 +80,7 @@ export class OssServeConfigurator { this.serviceContainer.cipherService, this.serviceContainer.folderService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, @@ -89,6 +91,7 @@ export class OssServeConfigurator { this.serviceContainer.cipherService, this.serviceContainer.folderService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.accountService, @@ -150,7 +153,7 @@ export class OssServeConfigurator { this.serviceContainer.sendService, this.serviceContainer.environmentService, this.serviceContainer.searchService, - this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, ); this.sendEditCommand = new SendEditCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index b9225fec43d..98c5fc86b52 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -494,6 +494,7 @@ export class ServiceContainer { this.collectionService = new CollectionService( this.cryptoService, + this.encryptService, this.i18nService, this.stateProvider, ); @@ -631,10 +632,12 @@ export class ServiceContainer { this.cipherFileUploadService, this.configService, this.stateProvider, + this.accountService, ); this.folderService = new FolderService( this.cryptoService, + this.encryptService, this.i18nService, this.cipherService, this.stateProvider, @@ -721,6 +724,7 @@ export class ServiceContainer { this.i18nService, this.collectionService, this.cryptoService, + this.encryptService, this.pinService, this.accountService, ); @@ -730,8 +734,10 @@ export class ServiceContainer { this.cipherService, this.pinService, this.cryptoService, + this.encryptService, this.cryptoFunctionService, this.kdfConfigService, + this.accountService, ); this.organizationExportService = new OrganizationVaultExportService( @@ -739,6 +745,7 @@ export class ServiceContainer { this.apiService, this.pinService, this.cryptoService, + this.encryptService, this.cryptoFunctionService, this.collectionService, this.kdfConfigService, diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts index 2ffe2423174..057a1c27f33 100644 --- a/apps/cli/src/tools/send/commands/get.command.ts +++ b/apps/cli/src/tools/send/commands/get.command.ts @@ -2,7 +2,7 @@ import { OptionValues } from "commander"; import { firstValueFrom } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; @@ -17,9 +17,9 @@ export class SendGetCommand extends DownloadCommand { private sendService: SendService, private environmentService: EnvironmentService, private searchService: SearchService, - cryptoService: CryptoService, + encryptService: EncryptService, ) { - super(cryptoService); + super(encryptService); } async run(id: string, options: OptionValues) { diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index dc662f02727..a8740992f72 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -2,10 +2,10 @@ import { OptionValues } from "commander"; import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -27,14 +27,14 @@ export class SendReceiveCommand extends DownloadCommand { private sendAccessRequest: SendAccessRequest; constructor( - private apiService: ApiService, - cryptoService: CryptoService, + private cryptoService: CryptoService, + encryptService: EncryptService, private cryptoFunctionService: CryptoFunctionService, private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService, private sendApiService: SendApiService, ) { - super(cryptoService); + super(encryptService); } async run(url: string, options: OptionValues): Promise { diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 05e7e7d22d7..878eaa52b24 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -100,8 +100,8 @@ export class SendProgram extends BaseProgram { }) .action(async (url: string, options: OptionValues) => { const cmd = new SendReceiveCommand( - this.serviceContainer.apiService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.cryptoFunctionService, this.serviceContainer.platformUtilsService, this.serviceContainer.environmentService, @@ -143,6 +143,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.totpService, this.serviceContainer.auditService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, @@ -187,7 +188,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.sendService, this.serviceContainer.environmentService, this.serviceContainer.searchService, - this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, ); const response = await cmd.run(id, options); this.processResponse(response); @@ -246,7 +247,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.sendService, this.serviceContainer.environmentService, this.serviceContainer.searchService, - this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, ); const cmd = new SendEditCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 2dad9a7c68a..11876ef6ece 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -178,6 +178,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.totpService, this.serviceContainer.auditService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, @@ -224,6 +225,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.cipherService, this.serviceContainer.folderService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, @@ -272,6 +274,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.cipherService, this.serviceContainer.folderService, this.serviceContainer.cryptoService, + this.serviceContainer.encryptService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.accountService, diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 0284ccc37bd..fc52720f77d 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -12,6 +12,7 @@ import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -31,6 +32,7 @@ export class CreateCommand { private cipherService: CipherService, private folderService: FolderService, private cryptoService: CryptoService, + private encryptService: EncryptService, private apiService: ApiService, private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, @@ -167,7 +169,9 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const folder = await this.folderService.encrypt(FolderExport.toView(req)); + const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeAccountId.id); + const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { await this.folderApiService.save(folder); const newFolder = await this.folderService.get(folder.id); @@ -210,7 +214,7 @@ export class CreateCommand { (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), ); const request = new CollectionRequest(); - request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; + request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; request.users = users; diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index d5672f54c0f..a6db7fe5db4 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -234,7 +234,7 @@ const safeProviders: SafeProvider[] = [ provide: NativeMessageHandlerService, deps: [ StateServiceAbstraction, - CryptoServiceAbstraction, + EncryptService, CryptoFunctionServiceAbstraction, MessagingServiceAbstraction, EncryptedMessageHandlerService, diff --git a/apps/desktop/src/services/native-message-handler.service.ts b/apps/desktop/src/services/native-message-handler.service.ts index 065726559b1..106dc11e41b 100644 --- a/apps/desktop/src/services/native-message-handler.service.ts +++ b/apps/desktop/src/services/native-message-handler.service.ts @@ -3,7 +3,7 @@ import { firstValueFrom } from "rxjs"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -31,7 +31,7 @@ export class NativeMessageHandlerService { constructor( private stateService: StateService, - private cryptoService: CryptoService, + private encryptService: EncryptService, private cryptoFunctionService: CryptoFunctionService, private messagingService: MessagingService, private encryptedMessageHandlerService: EncryptedMessageHandlerService, @@ -162,7 +162,7 @@ export class NativeMessageHandlerService { payload: DecryptedCommandData, key: SymmetricCryptoKey, ): Promise { - return await this.cryptoService.encrypt(JSON.stringify(payload), key); + return await this.encryptService.encrypt(JSON.stringify(payload), key); } private async decryptPayload(message: EncryptedMessage): Promise { @@ -182,7 +182,7 @@ export class NativeMessageHandlerService { } try { - let decryptedResult = await this.cryptoService.decryptToUtf8( + let decryptedResult = await this.encryptService.decryptToUtf8( message.encryptedCommand as EncString, this.ddgSharedSecret, ); diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index f106d137b76..2b218001947 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -6,6 +6,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; @@ -33,6 +34,7 @@ export class NativeMessagingService { constructor( private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService, + private encryptService: EncryptService, private logService: LogService, private messagingService: MessagingService, private desktopSettingService: DesktopSettingsService, @@ -111,7 +113,7 @@ export class NativeMessagingService { } const message: LegacyMessage = JSON.parse( - await this.cryptoService.decryptToUtf8( + await this.encryptService.decryptToUtf8( rawMessage as EncString, SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), ), @@ -224,7 +226,7 @@ export class NativeMessagingService { private async send(message: any, appId: string) { message.timestamp = Date.now(); - const encrypted = await this.cryptoService.encrypt( + const encrypted = await this.encryptService.encrypt( JSON.stringify(message), SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), ); diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index 2e25d390872..47e08a13cde 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -22,6 +23,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { cipherService: CipherService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, platformUtilsService: PlatformUtilsService, apiService: ApiService, logService: LogService, @@ -36,6 +38,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { cipherService, i18nService, cryptoService, + encryptService, platformUtilsService, apiService, window, diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index 3865ec4837c..0cc55d65f3e 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -2,6 +2,8 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -17,6 +19,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( folderService: FolderService, folderApiService: FolderApiServiceAbstraction, + accountService: AccountService, + cryptoService: CryptoService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -26,6 +30,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { super( folderService, folderApiService, + accountService, + cryptoService, i18nService, platformUtilsService, logService, diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 140e1e9ced6..7f2fb9ceae5 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -19,6 +19,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service" import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -48,6 +49,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro tokenService: TokenService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, platformUtilsService: PlatformUtilsService, auditService: AuditService, broadcasterService: BroadcasterService, @@ -72,6 +74,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro tokenService, i18nService, cryptoService, + encryptService, platformUtilsService, auditService, window, diff --git a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts index e0a6f6c53d5..4912a866fd1 100644 --- a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -26,6 +27,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen cipherService: CipherService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, stateService: StateService, platformUtilsService: PlatformUtilsService, apiService: ApiService, @@ -40,6 +42,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen cipherService, i18nService, cryptoService, + encryptService, platformUtilsService, apiService, window, diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 3d02fa027ef..924b128a50a 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -31,6 +31,7 @@ import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.res import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -147,6 +148,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService, + private encryptService: EncryptService, private router: Router, private syncService: SyncService, private policyService: PolicyService, @@ -590,7 +592,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.createOrganization) { const orgKey = await this.cryptoService.makeOrgKey(); const key = orgKey[0].encryptedString; - const collection = await this.cryptoService.encrypt( + const collection = await this.encryptService.encrypt( this.i18nService.t("defaultCollection"), orgKey[1], ); @@ -744,7 +746,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); const providerKey = await this.cryptoService.getProviderKey(this.providerId); providerRequest.organizationCreateRequest.key = ( - await this.cryptoService.encrypt(orgKey.key, providerKey) + await this.encryptService.encrypt(orgKey.key, providerKey) ).encryptedString; const orgId = ( await this.apiService.postProviderCreateOrganization(this.providerId, providerRequest) diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access-file.component.ts index 8bb3558a69d..1efabb5fec7 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access-file.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from "@angular/core"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -26,7 +26,7 @@ export class SendAccessFileComponent { constructor( private i18nService: I18nService, private toastService: ToastService, - private cryptoService: CryptoService, + private encryptService: EncryptService, private fileDownloadService: FileDownloadService, private sendApiService: SendApiService, ) {} @@ -62,7 +62,7 @@ export class SendAccessFileComponent { try { const encBuf = await EncArrayBuffer.fromResponse(response); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, this.decKey); + const decBuf = await this.encryptService.decryptToBytes(encBuf, this.decKey); this.fileDownloadService.download({ fileName: this.send.file.fileName, blobData: decBuf, diff --git a/apps/web/src/app/vault/core/collection-admin.service.ts b/apps/web/src/app/vault/core/collection-admin.service.ts index b6ddd452a1d..e0c15e34047 100644 --- a/apps/web/src/app/vault/core/collection-admin.service.ts +++ b/apps/web/src/app/vault/core/collection-admin.service.ts @@ -3,6 +3,7 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data"; @@ -23,6 +24,7 @@ export class CollectionAdminService { constructor( private apiService: ApiService, private cryptoService: CryptoService, + private encryptService: EncryptService, private collectionService: CollectionService, ) {} @@ -116,7 +118,7 @@ export class CollectionAdminService { const promises = collections.map(async (c) => { const view = new CollectionAdminView(); view.id = c.id; - view.name = await this.cryptoService.decryptToUtf8(new EncString(c.name), orgKey); + view.name = await this.encryptService.decryptToUtf8(new EncString(c.name), orgKey); view.externalId = c.externalId; view.organizationId = c.organizationId; @@ -146,7 +148,7 @@ export class CollectionAdminService { } const collection = new CollectionRequest(); collection.externalId = model.externalId; - collection.name = (await this.cryptoService.encrypt(model.name, key)).encryptedString; + collection.name = (await this.encryptService.encrypt(model.name, key)).encryptedString; collection.groups = model.groups.map( (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage), diff --git a/apps/web/src/app/vault/individual-vault/attachments.component.ts b/apps/web/src/app/vault/individual-vault/attachments.component.ts index b578efcae67..bb070ef6887 100644 --- a/apps/web/src/app/vault/individual-vault/attachments.component.ts +++ b/apps/web/src/app/vault/individual-vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -25,6 +26,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { cipherService: CipherService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, stateService: StateService, platformUtilsService: PlatformUtilsService, apiService: ApiService, @@ -39,6 +41,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { cipherService, i18nService, cryptoService, + encryptService, platformUtilsService, apiService, window, diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index fe61d9b9a11..bc31cdc8cde 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -1,8 +1,11 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -19,6 +22,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( folderService: FolderService, folderApiService: FolderApiServiceAbstraction, + protected accountSerivce: AccountService, + protected cryptoService: CryptoService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -31,6 +36,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { super( folderService, folderApiService, + accountSerivce, + cryptoService, i18nService, platformUtilsService, logService, @@ -73,7 +80,9 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - const folder = await this.folderService.encrypt(this.folder); + const activeAccountId = (await firstValueFrom(this.accountSerivce.activeAccount$)).id; + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeAccountId); + const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder); await this.formPromise; this.platformUtilsService.showToast( diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index 2bba4d389c0..62e65ade5de 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -5,6 +5,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -31,6 +32,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On cipherService: CipherService, i18nService: I18nService, cryptoService: CryptoService, + encryptService: EncryptService, stateService: StateService, platformUtilsService: PlatformUtilsService, apiService: ApiService, @@ -45,6 +47,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On cipherService, i18nService, cryptoService, + encryptService, stateService, platformUtilsService, apiService, diff --git a/bitwarden_license/bit-cli/src/service-container.ts b/bitwarden_license/bit-cli/src/service-container.ts index 716c045fd16..c624afd9460 100644 --- a/bitwarden_license/bit-cli/src/service-container.ts +++ b/bitwarden_license/bit-cli/src/service-container.ts @@ -18,6 +18,7 @@ export class ServiceContainer extends OssServiceContainer { this.organizationAuthRequestService = new OrganizationAuthRequestService( this.organizationAuthRequestApiService, this.cryptoService, + this.encryptService, this.organizationUserApiService, ); } diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts index a8e6445d331..3ee89cbda51 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts @@ -6,6 +6,7 @@ import { } from "@bitwarden/admin-console/common"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; @@ -16,16 +17,19 @@ import { PendingAuthRequestView } from "./pending-auth-request.view"; describe("OrganizationAuthRequestService", () => { let organizationAuthRequestApiService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; let organizationUserApiService: MockProxy; let organizationAuthRequestService: OrganizationAuthRequestService; beforeEach(() => { organizationAuthRequestApiService = mock(); cryptoService = mock(); + encryptService = mock(); organizationUserApiService = mock(); organizationAuthRequestService = new OrganizationAuthRequestService( organizationAuthRequestApiService, cryptoService, + encryptService, organizationUserApiService, ); }); diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index edba399b8b2..ad6e29c5834 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -3,6 +3,7 @@ import { OrganizationUserResetPasswordDetailsResponse, } from "@bitwarden/admin-console/common"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; @@ -15,6 +16,7 @@ export class OrganizationAuthRequestService { constructor( private organizationAuthRequestApiService: OrganizationAuthRequestApiService, private cryptoService: CryptoService, + private encryptService: EncryptService, private organizationUserApiService: OrganizationUserApiService, ) {} @@ -109,7 +111,7 @@ export class OrganizationAuthRequestService { // Decrypt Organization's encrypted Private Key with org key const orgSymKey = await this.cryptoService.getOrgKey(organizationId); - const decOrgPrivateKey = await this.cryptoService.decryptToBytes( + const decOrgPrivateKey = await this.encryptService.decryptToBytes( new EncString(encryptedOrgPrivateKey), orgSymKey, ); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 34c7bba7d0c..e074e0dd315 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -10,6 +10,7 @@ import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -30,7 +31,12 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module"; }), safeProvider({ provide: OrganizationAuthRequestService, - deps: [OrganizationAuthRequestApiService, CryptoService, OrganizationUserApiService], + deps: [ + OrganizationAuthRequestApiService, + CryptoService, + EncryptService, + OrganizationUserApiService, + ], }), ] satisfies SafeProvider[], imports: [SharedModule, NoItemsModule, LooseComponentsModule], diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 76094646808..3a2c7b8b644 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -27,7 +27,7 @@ export class WebProviderService { const orgKey = await this.cryptoService.getOrgKey(organizationId); const providerKey = await this.cryptoService.getProviderKey(providerId); - const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey); + const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); const request = new ProviderAddOrganizationRequest(); request.organizationId = organizationId; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 4cdf5be8651..734ae03d59b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -449,6 +449,7 @@ const safeProviders: SafeProvider[] = [ fileUploadService: CipherFileUploadServiceAbstraction, configService: ConfigService, stateProvider: StateProvider, + accountService: AccountServiceAbstraction, ) => new CipherService( cryptoService, @@ -463,6 +464,7 @@ const safeProviders: SafeProvider[] = [ fileUploadService, configService, stateProvider, + accountService, ), deps: [ CryptoServiceAbstraction, @@ -477,6 +479,7 @@ const safeProviders: SafeProvider[] = [ CipherFileUploadServiceAbstraction, ConfigService, StateProvider, + AccountServiceAbstraction, ], }), safeProvider({ @@ -484,6 +487,7 @@ const safeProviders: SafeProvider[] = [ useClass: FolderService, deps: [ CryptoServiceAbstraction, + EncryptService, I18nServiceAbstraction, CipherServiceAbstraction, StateProvider, @@ -527,7 +531,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CollectionServiceAbstraction, useClass: CollectionService, - deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateProvider], + deps: [CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, StateProvider], }), safeProvider({ provide: EnvironmentService, @@ -785,6 +789,7 @@ const safeProviders: SafeProvider[] = [ I18nServiceAbstraction, CollectionServiceAbstraction, CryptoServiceAbstraction, + EncryptService, PinServiceAbstraction, AccountServiceAbstraction, ], @@ -797,8 +802,10 @@ const safeProviders: SafeProvider[] = [ CipherServiceAbstraction, PinServiceAbstraction, CryptoServiceAbstraction, + EncryptService, CryptoFunctionServiceAbstraction, KdfConfigServiceAbstraction, + AccountServiceAbstraction, ], }), safeProvider({ @@ -809,6 +816,7 @@ const safeProviders: SafeProvider[] = [ ApiServiceAbstraction, PinServiceAbstraction, CryptoServiceAbstraction, + EncryptService, CryptoFunctionServiceAbstraction, CollectionServiceAbstraction, KdfConfigServiceAbstraction, diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 4ae68c9ca9d..43c5a0d6d41 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -6,6 +6,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -40,6 +41,7 @@ export class AttachmentsComponent implements OnInit { protected cipherService: CipherService, protected i18nService: I18nService, protected cryptoService: CryptoService, + protected encryptService: EncryptService, protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, protected win: Window, @@ -178,7 +180,7 @@ export class AttachmentsComponent implements OnInit { attachment.key != null ? attachment.key : await this.cryptoService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const decBuf = await this.encryptService.decryptToBytes(encBuf, key); this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, @@ -249,7 +251,7 @@ export class AttachmentsComponent implements OnInit { attachment.key != null ? attachment.key : await this.cryptoService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 0f179ae012c..199feb599ff 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -1,6 +1,9 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Validators, FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -29,6 +32,8 @@ export class FolderAddEditComponent implements OnInit { constructor( protected folderService: FolderService, protected folderApiService: FolderApiServiceAbstraction, + protected accountService: AccountService, + protected cryptoService: CryptoService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, @@ -52,7 +57,9 @@ export class FolderAddEditComponent implements OnInit { } try { - const folder = await this.folderService.encrypt(this.folder); + const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeAccountId.id); + const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder); await this.formPromise; this.platformUtilsService.showToast( diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index a6e96bc542a..ac644acf9e4 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -21,6 +21,7 @@ import { EventType } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -87,6 +88,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected tokenService: TokenService, protected i18nService: I18nService, protected cryptoService: CryptoService, + protected encryptService: EncryptService, protected platformUtilsService: PlatformUtilsService, protected auditService: AuditService, protected win: Window, @@ -442,7 +444,7 @@ export class ViewComponent implements OnDestroy, OnInit { attachment.key != null ? attachment.key : await this.cryptoService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const decBuf = await this.encryptService.decryptToBytes(encBuf, key); this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index b112e5aa2ab..efc6da51d9f 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -12,6 +12,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -37,6 +38,7 @@ describe("AuthRequestLoginStrategy", () => { let cache: AuthRequestLoginStrategyData; let cryptoService: MockProxy; + let encryptService: MockProxy; let apiService: MockProxy; let tokenService: MockProxy; let appIdService: MockProxy; @@ -101,6 +103,7 @@ describe("AuthRequestLoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 665857c1f47..35d62ca76b3 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -22,6 +22,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -104,6 +105,7 @@ describe("LoginStrategy", () => { let loginStrategyService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; let apiService: MockProxy; let tokenService: MockProxy; let appIdService: MockProxy; @@ -128,6 +130,7 @@ describe("LoginStrategy", () => { loginStrategyService = mock(); cryptoService = mock(); + encryptService = mock(); apiService = mock(); tokenService = mock(); appIdService = mock(); @@ -156,6 +159,7 @@ describe("LoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, @@ -467,6 +471,7 @@ describe("LoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index ff6bf07af7e..2e881f978dc 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -26,6 +26,7 @@ import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -66,6 +67,7 @@ export abstract class LoginStrategy { protected accountService: AccountService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, protected cryptoService: CryptoService, + protected encryptService: EncryptService, protected apiService: ApiService, protected tokenService: TokenService, protected appIdService: AppIdService, diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 7ba58e1443a..07cbf2424ab 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -16,6 +16,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -63,6 +64,7 @@ describe("PasswordLoginStrategy", () => { let loginStrategyService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; let apiService: MockProxy; let tokenService: MockProxy; let appIdService: MockProxy; @@ -88,6 +90,7 @@ describe("PasswordLoginStrategy", () => { loginStrategyService = mock(); cryptoService = mock(); + encryptService = mock(); apiService = mock(); tokenService = mock(); appIdService = mock(); @@ -127,6 +130,7 @@ describe("PasswordLoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 8e28a2c0222..f5de10766c0 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -17,6 +17,7 @@ import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -44,6 +45,7 @@ describe("SsoLoginStrategy", () => { let masterPasswordService: FakeMasterPasswordService; let cryptoService: MockProxy; + let encryptService: MockProxy; let apiService: MockProxy; let tokenService: MockProxy; let appIdService: MockProxy; @@ -78,6 +80,7 @@ describe("SsoLoginStrategy", () => { masterPasswordService = new FakeMasterPasswordService(); cryptoService = mock(); + encryptService = mock(); apiService = mock(); tokenService = mock(); appIdService = mock(); @@ -125,6 +128,7 @@ describe("SsoLoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index 16614497964..d299a8e0ced 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -11,6 +11,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Environment, EnvironmentService, @@ -39,6 +40,7 @@ describe("UserApiLoginStrategy", () => { let masterPasswordService: FakeMasterPasswordService; let cryptoService: MockProxy; + let encryptService: MockProxy; let apiService: MockProxy; let tokenService: MockProxy; let appIdService: MockProxy; @@ -99,6 +101,7 @@ describe("UserApiLoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index 0db41c1e64f..b25022d25df 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -14,6 +14,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -37,6 +38,7 @@ describe("WebAuthnLoginStrategy", () => { let masterPasswordService: FakeMasterPasswordService; let cryptoService!: MockProxy; + let encryptService!: MockProxy; let apiService!: MockProxy; let tokenService!: MockProxy; let appIdService!: MockProxy; @@ -79,6 +81,7 @@ describe("WebAuthnLoginStrategy", () => { masterPasswordService = new FakeMasterPasswordService(); cryptoService = mock(); + encryptService = mock(); apiService = mock(); tokenService = mock(); appIdService = mock(); @@ -103,6 +106,7 @@ describe("WebAuthnLoginStrategy", () => { accountService, masterPasswordService, cryptoService, + encryptService, apiService, tokenService, appIdService, @@ -221,7 +225,7 @@ describe("WebAuthnLoginStrategy", () => { const mockUserKeyArray: Uint8Array = randomBytes(32); const mockUserKey = new SymmetricCryptoKey(mockUserKeyArray) as UserKey; - cryptoService.decryptToBytes.mockResolvedValue(mockPrfPrivateKey); + encryptService.decryptToBytes.mockResolvedValue(mockPrfPrivateKey); cryptoService.rsaDecrypt.mockResolvedValue(mockUserKeyArray); // Act @@ -235,8 +239,8 @@ describe("WebAuthnLoginStrategy", () => { userId, ); - expect(cryptoService.decryptToBytes).toHaveBeenCalledTimes(1); - expect(cryptoService.decryptToBytes).toHaveBeenCalledWith( + expect(encryptService.decryptToBytes).toHaveBeenCalledTimes(1); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith( idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedPrivateKey, webAuthnCredentials.prfKey, ); @@ -268,7 +272,7 @@ describe("WebAuthnLoginStrategy", () => { await webAuthnLoginStrategy.logIn(webAuthnCredentials); // Assert - expect(cryptoService.decryptToBytes).not.toHaveBeenCalled(); + expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); expect(cryptoService.rsaDecrypt).not.toHaveBeenCalled(); expect(cryptoService.setUserKey).not.toHaveBeenCalled(); }); @@ -303,7 +307,7 @@ describe("WebAuthnLoginStrategy", () => { apiService.postIdentityToken.mockResolvedValue(idTokenResponse); - cryptoService.decryptToBytes.mockResolvedValue(null); + encryptService.decryptToBytes.mockResolvedValue(null); // Act await webAuthnLoginStrategy.logIn(webAuthnCredentials); diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts index d283d163da1..96f8bc7d633 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts @@ -80,7 +80,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy { } // decrypt prf encrypted private key - const privateKey = await this.cryptoService.decryptToBytes( + const privateKey = await this.encryptService.decryptToBytes( webAuthnPrfOption.encryptedPrivateKey, credentials.prfKey, ); diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 67bcdc3658e..89c2bc01d94 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -317,6 +317,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.accountService, this.masterPasswordService, this.cryptoService, + this.encryptService, this.apiService, this.tokenService, this.appIdService, diff --git a/libs/common/src/platform/abstractions/crypto.service.ts b/libs/common/src/platform/abstractions/crypto.service.ts index 1fe97e023f2..2a8e1ad6476 100644 --- a/libs/common/src/platform/abstractions/crypto.service.ts +++ b/libs/common/src/platform/abstractions/crypto.service.ts @@ -15,7 +15,6 @@ import { UserPublicKey, } from "../../types/key"; import { KeySuffixOptions, HashPurpose } from "../enums"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncString } from "../models/domain/enc-string"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; @@ -373,37 +372,6 @@ export abstract class CryptoService { * @param userId The desired user */ abstract clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: string): Promise; - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.encrypt - */ - abstract encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey): Promise; - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.encryptToBytes - */ - abstract encryptToBytes( - plainValue: Uint8Array, - key?: SymmetricCryptoKey, - ): Promise; - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToBytes - */ - abstract decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise; - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToUtf8 - */ - abstract decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise; - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToBytes - */ - abstract decryptFromBytes( - encBuffer: EncArrayBuffer, - key: SymmetricCryptoKey, - ): Promise; /** * Retrieves all the keys needed for decrypting Ciphers diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index 8ce2b5e1a0c..6a93ac7f3ff 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -48,7 +48,6 @@ import { StateService } from "../abstractions/state.service"; import { KeySuffixOptions, HashPurpose, EncryptionType } from "../enums"; import { convertValues } from "../misc/convert-values"; import { EFFLongWordList } from "../misc/wordlist"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncString, EncryptedString } from "../models/domain/enc-string"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { ActiveUserState, StateProvider } from "../state"; @@ -859,58 +858,6 @@ export class CryptoService implements CryptoServiceAbstraction { } } - // --DEPRECATED METHODS-- - - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.encrypt - */ - async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey): Promise { - key ||= await this.getUserKeyWithLegacySupport(); - return await this.encryptService.encrypt(plainValue, key); - } - - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.encryptToBytes - */ - async encryptToBytes(plainValue: Uint8Array, key?: SymmetricCryptoKey): Promise { - key ||= await this.getUserKeyWithLegacySupport(); - return this.encryptService.encryptToBytes(plainValue, key); - } - - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToBytes - */ - async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise { - key ||= await this.getUserKeyWithLegacySupport(); - return this.encryptService.decryptToBytes(encString, key); - } - - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToUtf8 - */ - async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise { - key ||= await this.getUserKeyWithLegacySupport(); - return await this.encryptService.decryptToUtf8(encString, key); - } - - /** - * @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey) - * and then call encryptService.decryptToBytes - */ - async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise { - if (encBuffer == null) { - throw new Error("No buffer provided for decryption."); - } - - key ||= await this.getUserKeyWithLegacySupport(); - - return this.encryptService.decryptToBytes(encBuffer, key); - } - userKey$(userId: UserId): Observable { return this.stateProvider.getUser(userId, USER_KEY).state$; } diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index bd42ab09cf9..5b1d7e73dae 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,5 +1,8 @@ import { mock } from "jest-mock-extended"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserKey } from "@bitwarden/common/types/key"; + import { makeStaticByteArray, mockEnc } from "../../../../../spec"; import { CryptoService } from "../../../../platform/abstractions/crypto.service"; import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; @@ -89,6 +92,7 @@ describe("Send", () => { it("Decrypt", async () => { const text = mock(); text.decrypt.mockResolvedValue("textView" as any); + const userKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; const send = new Send(); send.id = "id"; @@ -106,13 +110,13 @@ describe("Send", () => { send.disabled = false; send.hideEmail = true; + const encryptService = mock(); const cryptoService = mock(); - cryptoService.decryptToBytes - .calledWith(send.key, null) + encryptService.decryptToBytes + .calledWith(send.key, userKey) .mockResolvedValue(makeStaticByteArray(32)); cryptoService.makeSendKey.mockResolvedValue("cryptoKey" as any); - - const encryptService = mock(); + cryptoService.getUserKey.mockResolvedValue(userKey); (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 610980062b6..41d1fecc10b 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -73,9 +73,11 @@ export class Send extends Domain { const model = new SendView(this); const cryptoService = Utils.getContainerService().getCryptoService(); + const encryptService = Utils.getContainerService().getEncryptService(); try { - model.key = await cryptoService.decryptToBytes(this.key, null); + const sendKeyEncryptionKey = await cryptoService.getUserKey(); + model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await cryptoService.makeSendKey(model.key); } catch (e) { // TODO: error? diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index 3480a8aca03..857915ddb80 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -15,7 +15,7 @@ export abstract class FolderService implements UserKeyRotationDataProvider; clearCache: () => Promise; - encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; + encrypt: (model: FolderView, key: SymmetricCryptoKey) => Promise; get: (id: string) => Promise; getDecrypted$: (id: string) => Observable; getAllFromState: () => Promise; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index b2712dee559..0873fa9d928 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -145,6 +145,7 @@ describe("Cipher Service", () => { cipherFileUploadService, configService, stateProvider, + accountService, ); cipherObj = new Cipher(cipherData); @@ -273,7 +274,7 @@ describe("Cipher Service", () => { cryptoService.makeCipherKey.mockReturnValue( Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey), ); - cryptoService.encrypt.mockImplementation(encryptText); + encryptService.encrypt.mockImplementation(encryptText); jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true); }); @@ -285,6 +286,10 @@ describe("Cipher Service", () => { { uri: "uri", match: UriMatchStrategy.RegularExpression } as LoginUriView, ]; + cryptoService.getOrgKey.mockReturnValue( + Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), + ); + const domain = await cipherService.encrypt(cipherView, userId); expect(domain.login.uris).toEqual([ @@ -301,6 +306,9 @@ describe("Cipher Service", () => { it("is null when feature flag is false", async () => { configService.getFeatureFlag.mockResolvedValue(false); + cryptoService.getOrgKey.mockReturnValue( + Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), + ); const cipher = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeNull(); @@ -322,6 +330,9 @@ describe("Cipher Service", () => { it("is not called when feature flag is false", async () => { configService.getFeatureFlag.mockResolvedValue(false); + cryptoService.getOrgKey.mockReturnValue( + Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), + ); await cipherService.encrypt(cipherView, userId); @@ -330,6 +341,9 @@ describe("Cipher Service", () => { it("is called when feature flag is true", async () => { configService.getFeatureFlag.mockResolvedValue(true); + cryptoService.getOrgKey.mockReturnValue( + Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), + ); await cipherService.encrypt(cipherView, userId); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 70b7c77fc15..9761387284f 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,6 +1,7 @@ import { firstValueFrom, map, Observable, skipWhile, switchMap } from "rxjs"; import { SemVer } from "semver"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; @@ -108,6 +109,7 @@ export class CipherService implements CipherServiceAbstraction { private cipherFileUploadService: CipherFileUploadService, private configService: ConfigService, private stateProvider: StateProvider, + private accountService: AccountService, ) { this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY); this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); @@ -165,7 +167,7 @@ export class CipherService implements CipherServiceAbstraction { async encrypt( model: CipherView, userId: UserId, - keyForEncryption?: SymmetricCryptoKey, + keyForCipherEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher: Cipher = null, ): Promise { @@ -195,26 +197,21 @@ export class CipherService implements CipherServiceAbstraction { const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId); // The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled. // If the caller has provided a key for cipher key encryption, use it. Otherwise, use the user or org key. - keyForEncryption ||= userOrOrgKey; + keyForCipherEncryption ||= userOrOrgKey; // If the caller has provided a key for cipher key decryption, use it. Otherwise, use the user or org key. keyForCipherKeyDecryption ||= userOrOrgKey; return this.encryptCipherWithCipherKey( model, cipher, - keyForEncryption, + keyForCipherEncryption, keyForCipherKeyDecryption, ); } else { - if (keyForEncryption == null && cipher.organizationId != null) { - keyForEncryption = await this.cryptoService.getOrgKey(cipher.organizationId); - if (keyForEncryption == null) { - throw new Error("Cannot encrypt cipher for organization. No key."); - } - } + keyForCipherEncryption ||= await this.getKeyForCipherKeyDecryption(cipher, userId); // We want to ensure that the cipher key is null if cipher key encryption is disabled // so that decryption uses the proper key. cipher.key = null; - return this.encryptCipher(model, cipher, keyForEncryption); + return this.encryptCipher(model, cipher, keyForCipherEncryption); } } @@ -243,7 +240,7 @@ export class CipherService implements CipherServiceAbstraction { key, ).then(async () => { if (model.key != null) { - attachment.key = await this.cryptoService.encrypt(model.key.key, key); + attachment.key = await this.encryptService.encrypt(model.key.key, key); } encAttachments.push(attachment); }); @@ -1348,7 +1345,9 @@ export class CipherService implements CipherServiceAbstraction { } const encBuf = await EncArrayBuffer.fromResponse(attachmentResponse); - const decBuf = await this.cryptoService.decryptFromBytes(encBuf, null); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId.id); + const decBuf = await this.encryptService.decryptToBytes(encBuf, userKey); let encKey: UserKey | OrgKey; encKey = await this.cryptoService.getOrgKey(organizationId); @@ -1412,7 +1411,7 @@ export class CipherService implements CipherServiceAbstraction { .then(() => { const modelProp = (model as any)[map[theProp] || theProp]; if (modelProp && modelProp !== "") { - return self.cryptoService.encrypt(modelProp, key); + return self.encryptService.encrypt(modelProp, key); } return null; }) @@ -1458,7 +1457,7 @@ export class CipherService implements CipherServiceAbstraction { key, ); const uriHash = await this.encryptService.hash(model.login.uris[i].uri, "sha256"); - loginUri.uriChecksum = await this.cryptoService.encrypt(uriHash, key); + loginUri.uriChecksum = await this.encryptService.encrypt(uriHash, key); cipher.login.uris.push(loginUri); } } @@ -1485,8 +1484,8 @@ export class CipherService implements CipherServiceAbstraction { }, key, ); - domainKey.counter = await this.cryptoService.encrypt(String(viewKey.counter), key); - domainKey.discoverable = await this.cryptoService.encrypt( + domainKey.counter = await this.encryptService.encrypt(String(viewKey.counter), key); + domainKey.discoverable = await this.encryptService.encrypt( String(viewKey.discoverable), key, ); @@ -1605,11 +1604,23 @@ export class CipherService implements CipherServiceAbstraction { this.sortedCiphersCache.clear(); } + /** + * Encrypts a cipher object. + * @param model The cipher view model. + * @param cipher The cipher object. + * @param key The encryption key to encrypt with. This can be the org key, user key or cipher key, but must never be null + */ private async encryptCipher( model: CipherView, cipher: Cipher, key: SymmetricCryptoKey, ): Promise { + if (key == null) { + throw new Error( + "Key to encrypt cipher must not be null. Use the org key, user key or cipher key.", + ); + } + await Promise.all([ this.encryptObjProperty( model, diff --git a/libs/common/src/vault/services/collection.service.ts b/libs/common/src/vault/services/collection.service.ts index e9ad09a4831..09d21390aea 100644 --- a/libs/common/src/vault/services/collection.service.ts +++ b/libs/common/src/vault/services/collection.service.ts @@ -1,6 +1,8 @@ import { firstValueFrom, map, Observable } from "rxjs"; import { Jsonify } from "type-fest"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; + import { CryptoService } from "../../platform/abstractions/crypto.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; @@ -61,6 +63,7 @@ export class CollectionService implements CollectionServiceAbstraction { constructor( private cryptoService: CryptoService, + private encryptService: EncryptService, private i18nService: I18nService, protected stateProvider: StateProvider, ) { @@ -101,7 +104,7 @@ export class CollectionService implements CollectionServiceAbstraction { collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; collection.externalId = model.externalId; - collection.name = await this.cryptoService.encrypt(model.name, key); + collection.name = await this.encryptService.encrypt(model.name, key); return collection; } diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index c27ea7646b0..05e1cdebc93 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -49,7 +49,13 @@ describe("Folder Service", () => { ); encryptService.decryptToUtf8.mockResolvedValue("DEC"); - folderService = new FolderService(cryptoService, i18nService, cipherService, stateProvider); + folderService = new FolderService( + cryptoService, + encryptService, + i18nService, + cipherService, + stateProvider, + ); folderState = stateProvider.activeUser.getFake(FOLDER_ENCRYPTED_FOLDERS); @@ -62,9 +68,9 @@ describe("Folder Service", () => { model.id = "2"; model.name = "Test Folder"; - cryptoService.encrypt.mockResolvedValue(new EncString("ENC")); + encryptService.encrypt.mockResolvedValue(new EncString("ENC")); - const result = await folderService.encrypt(model); + const result = await folderService.encrypt(model, null); expect(result).toEqual({ id: "2", @@ -185,7 +191,7 @@ describe("Folder Service", () => { beforeEach(() => { encryptedKey = new EncString("Re-encrypted Folder"); - cryptoService.encrypt.mockResolvedValue(encryptedKey); + encryptService.encrypt.mockResolvedValue(encryptedKey); }); it("returns re-encrypted user folders", async () => { diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 0c17d7178b2..2adbc8c6d0e 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -1,5 +1,7 @@ import { Observable, firstValueFrom, map, shareReplay } from "rxjs"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; + import { CryptoService } from "../../../platform/abstractions/crypto.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { Utils } from "../../../platform/misc/utils"; @@ -25,6 +27,7 @@ export class FolderService implements InternalFolderServiceAbstraction { constructor( private cryptoService: CryptoService, + private encryptService: EncryptService, private i18nService: I18nService, private cipherService: CipherService, private stateProvider: StateProvider, @@ -48,10 +51,10 @@ export class FolderService implements InternalFolderServiceAbstraction { } // TODO: This should be moved to EncryptService or something - async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise { + async encrypt(model: FolderView, key: SymmetricCryptoKey): Promise { const folder = new Folder(); folder.id = model.id; - folder.name = await this.cryptoService.encrypt(model.name, key); + folder.name = await this.encryptService.encrypt(model.name, key); return folder; } diff --git a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts index d36ce8b9a64..e5100e49900 100644 --- a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts @@ -3,6 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -19,6 +20,7 @@ import { emptyUnencryptedExport } from "./test-data/bitwarden-json/unencrypted.j describe("BitwardenPasswordProtectedImporter", () => { let importer: BitwardenPasswordProtectedImporter; let cryptoService: MockProxy; + let encryptService: MockProxy; let i18nService: MockProxy; let cipherService: MockProxy; let pinService: MockProxy; @@ -30,6 +32,7 @@ describe("BitwardenPasswordProtectedImporter", () => { beforeEach(() => { cryptoService = mock(); + encryptService = mock(); i18nService = mock(); cipherService = mock(); pinService = mock(); @@ -37,6 +40,7 @@ describe("BitwardenPasswordProtectedImporter", () => { importer = new BitwardenPasswordProtectedImporter( cryptoService, + encryptService, i18nService, cipherService, pinService, @@ -91,7 +95,7 @@ describe("BitwardenPasswordProtectedImporter", () => { }); it("succeeds with default jdoc", async () => { - cryptoService.decryptToUtf8.mockReturnValue(Promise.resolve(emptyUnencryptedExport)); + encryptService.decryptToUtf8.mockReturnValue(Promise.resolve(emptyUnencryptedExport)); expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(true); }); diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 8ee882734b3..10a3f5a89ab 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -30,6 +30,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -89,6 +90,7 @@ const safeProviders: SafeProvider[] = [ I18nService, CollectionService, CryptoService, + EncryptService, PinServiceAbstraction, AccountService, ], diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 2248606814b..9adc8a97819 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -8,8 +8,10 @@ import { FolderWithIdExport, } from "@bitwarden/common/models/export"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -31,6 +33,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { protected constructor( protected cryptoService: CryptoService, + protected encryptService: EncryptService, protected i18nService: I18nService, protected cipherService: CipherService, protected pinService: PinServiceAbstraction, @@ -60,11 +63,16 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport, ) { if (results.encKeyValidation_DO_NOT_EDIT != null) { - const orgKey = await this.cryptoService.getOrgKey(this.organizationId); + let keyForDecryption: SymmetricCryptoKey = await this.cryptoService.getOrgKey( + this.organizationId, + ); + if (keyForDecryption == null) { + keyForDecryption = await this.cryptoService.getUserKeyWithLegacySupport(); + } const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT); - const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8( + const encKeyValidationDecrypt = await this.encryptService.decryptToUtf8( encKeyValidation, - orgKey, + keyForDecryption, ); if (encKeyValidationDecrypt === null) { this.result.success = false; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index a854346bccb..35a0ec0f22c 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -6,6 +6,7 @@ import { PBKDF2KdfConfig, } from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -23,13 +24,14 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im constructor( cryptoService: CryptoService, + encryptService: EncryptService, i18nService: I18nService, cipherService: CipherService, pinService: PinServiceAbstraction, accountService: AccountService, private promptForPassword_callback: () => Promise, ) { - super(cryptoService, i18nService, cipherService, pinService, accountService); + super(cryptoService, encryptService, i18nService, cipherService, pinService, accountService); } async parse(data: string): Promise { @@ -65,7 +67,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im } const encData = new EncString(parsedData.data); - const clearTextData = await this.cryptoService.decryptToUtf8(encData, this.key); + const clearTextData = await this.encryptService.decryptToUtf8(encData, this.key); return await super.parse(clearTextData); } @@ -86,7 +88,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT); - const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8( + const encKeyValidationDecrypt = await this.encryptService.decryptToUtf8( encKeyValidation, this.key, ); diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index e44c8f6aa98..ef605746e6e 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -3,6 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -27,6 +28,7 @@ describe("ImportService", () => { let i18nService: MockProxy; let collectionService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; let pinService: MockProxy; let accountService: MockProxy; @@ -37,6 +39,7 @@ describe("ImportService", () => { i18nService = mock(); collectionService = mock(); cryptoService = mock(); + encryptService = mock(); pinService = mock(); importService = new ImportService( @@ -46,6 +49,7 @@ describe("ImportService", () => { i18nService, collectionService, cryptoService, + encryptService, pinService, accountService, ); diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 13b77fb5b4e..2295f4f7041 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -7,6 +7,7 @@ import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/reque import { KvpRequest } from "@bitwarden/common/models/request/kvp.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -104,6 +105,7 @@ export class ImportService implements ImportServiceAbstraction { private i18nService: I18nService, private collectionService: CollectionService, private cryptoService: CryptoService, + private encryptService: EncryptService, private pinService: PinServiceAbstraction, private accountService: AccountService, ) {} @@ -207,6 +209,7 @@ export class ImportService implements ImportServiceAbstraction { case "bitwardenpasswordprotected": return new BitwardenPasswordProtectedImporter( this.cryptoService, + this.encryptService, this.i18nService, this.cipherService, this.pinService, @@ -344,9 +347,10 @@ export class ImportService implements ImportServiceAbstraction { const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); request.ciphers.push(new CipherRequest(c)); } + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId); if (importResult.folders != null) { for (let i = 0; i < importResult.folders.length; i++) { - const f = await this.folderService.encrypt(importResult.folders[i]); + const f = await this.folderService.encrypt(importResult.folders[i], userKey); request.folders.push(new FolderWithIdRequest(f)); } } diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts index a494885698e..76b008be620 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts @@ -2,7 +2,7 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -12,7 +12,7 @@ import { BitwardenCsvExportType, BitwardenPasswordProtectedFileFormat } from ".. export class BaseVaultExportService { constructor( protected pinService: PinServiceAbstraction, - protected cryptoService: CryptoService, + protected encryptService: EncryptService, private cryptoFunctionService: CryptoFunctionService, private kdfConfigService: KdfConfigService, ) {} @@ -23,8 +23,8 @@ export class BaseVaultExportService { const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16)); const key = await this.pinService.makePinKey(password, salt, kdfConfig); - const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key); - const encText = await this.cryptoService.encrypt(clearText, key); + const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), key); + const encText = await this.encryptService.encrypt(clearText, key); const jsonDoc: BitwardenPasswordProtectedFileFormat = { encrypted: true, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 44df18116de..1a66fe92256 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { DEFAULT_KDF_CONFIG, @@ -9,9 +11,11 @@ import { import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -149,7 +153,9 @@ describe("VaultExportService", () => { let pinService: MockProxy; let folderService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; let kdfConfigService: MockProxy; + let accountService: MockProxy; beforeEach(() => { cryptoFunctionService = mock(); @@ -157,20 +163,35 @@ describe("VaultExportService", () => { pinService = mock(); folderService = mock(); cryptoService = mock(); + encryptService = mock(); kdfConfigService = mock(); + accountService = mock(); + + cryptoService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); + + const userId = "" as UserId; + const accountInfo: AccountInfo = { + email: "", + emailVerified: true, + name: undefined, + }; + const activeAccount = { id: userId, ...accountInfo }; + accountService.activeAccount$ = new BehaviorSubject(activeAccount); folderService.getAllDecryptedFromState.mockResolvedValue(UserFolderViews); folderService.getAllFromState.mockResolvedValue(UserFolders); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); - cryptoService.encrypt.mockResolvedValue(new EncString("encrypted")); + encryptService.encrypt.mockResolvedValue(new EncString("encrypted")); exportService = new IndividualVaultExportService( folderService, cipherService, pinService, cryptoService, + encryptService, cryptoFunctionService, kdfConfigService, + accountService, ); }); @@ -250,7 +271,7 @@ describe("VaultExportService", () => { }); it("has a mac property", async () => { - cryptoService.encrypt.mockResolvedValue(mac); + encryptService.encrypt.mockResolvedValue(mac); exportString = await exportService.getPasswordProtectedExport(password); exportObject = JSON.parse(exportString); @@ -258,7 +279,7 @@ describe("VaultExportService", () => { }); it("has data property", async () => { - cryptoService.encrypt.mockResolvedValue(data); + encryptService.encrypt.mockResolvedValue(data); exportString = await exportService.getPasswordProtectedExport(password); exportObject = JSON.parse(exportString); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index 3da92ef16b5..d6d37b28ac7 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -1,10 +1,13 @@ import * as papa from "papaparse"; +import { firstValueFrom, map } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -32,11 +35,13 @@ export class IndividualVaultExportService private folderService: FolderService, private cipherService: CipherService, pinService: PinServiceAbstraction, - cryptoService: CryptoService, + private cryptoService: CryptoService, + encryptService: EncryptService, cryptoFunctionService: CryptoFunctionService, kdfConfigService: KdfConfigService, + private accountService: AccountService, ) { - super(pinService, cryptoService, cryptoFunctionService, kdfConfigService); + super(pinService, encryptService, cryptoFunctionService, kdfConfigService); } async getExport(format: ExportFormat = "csv"): Promise { @@ -96,7 +101,11 @@ export class IndividualVaultExportService await Promise.all(promises); - const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid()); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId); + const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey); const jsonDoc: BitwardenEncryptedIndividualJsonExport = { encrypted: true, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 0c3e94178f6..9fc1f20b832 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -8,6 +8,7 @@ import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -39,13 +40,14 @@ export class OrganizationVaultExportService private cipherService: CipherService, private apiService: ApiService, pinService: PinServiceAbstraction, - cryptoService: CryptoService, + private cryptoService: CryptoService, + encryptService: EncryptService, cryptoFunctionService: CryptoFunctionService, private collectionService: CollectionService, kdfConfigService: KdfConfigService, private accountService: AccountService, ) { - super(pinService, cryptoService, cryptoFunctionService, kdfConfigService); + super(pinService, encryptService, cryptoFunctionService, kdfConfigService); } async getPasswordProtectedExport( @@ -242,7 +244,7 @@ export class OrganizationVaultExportService ciphers: Cipher[], ): Promise { const orgKey = await this.cryptoService.getOrgKey(organizationId); - const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey); + const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), orgKey); const jsonDoc: BitwardenEncryptedOrgJsonExport = { encrypted: true, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts index 44df18116de..7e93c78fc51 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { DEFAULT_KDF_CONFIG, @@ -9,9 +11,11 @@ import { import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -149,6 +153,8 @@ describe("VaultExportService", () => { let pinService: MockProxy; let folderService: MockProxy; let cryptoService: MockProxy; + let encryptService: MockProxy; + let accountService: MockProxy; let kdfConfigService: MockProxy; beforeEach(() => { @@ -157,20 +163,34 @@ describe("VaultExportService", () => { pinService = mock(); folderService = mock(); cryptoService = mock(); + encryptService = mock(); + accountService = mock(); + kdfConfigService = mock(); folderService.getAllDecryptedFromState.mockResolvedValue(UserFolderViews); folderService.getAllFromState.mockResolvedValue(UserFolders); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); - cryptoService.encrypt.mockResolvedValue(new EncString("encrypted")); + encryptService.encrypt.mockResolvedValue(new EncString("encrypted")); + cryptoService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); + const userId = "" as UserId; + const accountInfo: AccountInfo = { + email: "", + emailVerified: true, + name: undefined, + }; + const activeAccount = { id: userId, ...accountInfo }; + accountService.activeAccount$ = new BehaviorSubject(activeAccount); exportService = new IndividualVaultExportService( folderService, cipherService, pinService, cryptoService, + encryptService, cryptoFunctionService, kdfConfigService, + accountService, ); }); @@ -250,7 +270,7 @@ describe("VaultExportService", () => { }); it("has a mac property", async () => { - cryptoService.encrypt.mockResolvedValue(mac); + encryptService.encrypt.mockResolvedValue(mac); exportString = await exportService.getPasswordProtectedExport(password); exportObject = JSON.parse(exportString); @@ -258,7 +278,7 @@ describe("VaultExportService", () => { }); it("has data property", async () => { - cryptoService.encrypt.mockResolvedValue(data); + encryptService.encrypt.mockResolvedValue(data); exportString = await exportService.getPasswordProtectedExport(password); exportObject = JSON.parse(exportString); From 8507097fe7e50a80288a0a55b4e2039e23adce63 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:45:08 -0700 Subject: [PATCH 10/23] fix send password input (#11208) --- .../send-form/components/options/send-options.component.ts | 2 +- .../send-ui/src/send-form/components/send-form.component.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index ab4ffaa9dd0..89ab9d19ba2 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -102,7 +102,7 @@ export class SendOptionsComponent implements OnInit { this.sendOptionsForm.patchValue({ maxAccessCount: this.sendFormContainer.originalSendView.maxAccessCount, accessCount: this.sendFormContainer.originalSendView.accessCount, - password: this.sendFormContainer.originalSendView.password, + password: null, hideEmail: this.sendFormContainer.originalSendView.hideEmail, notes: this.sendFormContainer.originalSendView.notes, }); diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts index b265b644df4..1d93804e11f 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -16,6 +16,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { @@ -199,6 +200,10 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send return; } + if (Utils.isNullOrWhitespace(this.updatedSendView.password)) { + this.updatedSendView.password = null; + } + await this.addEditFormService.saveSend(this.updatedSendView, this.file, this.config); this.toastService.showToast({ From c8084cc4e3d5bb2a97b9f8424a00d130e36a07dd Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:26:39 -0400 Subject: [PATCH 11/23] Fixed free organization upgrade after stripe sources deprecation (#11205) --- .../organization-plans.component.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 924b128a50a..9ee56b0bcea 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -23,8 +23,11 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; @@ -33,7 +36,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -153,15 +155,15 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private syncService: SyncService, private policyService: PolicyService, private organizationService: OrganizationService, - private logService: LogService, private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, private providerApiService: ProviderApiServiceAbstraction, private toastService: ToastService, private configService: ConfigService, + private billingApiService: BillingApiServiceAbstraction, ) { - this.selfHosted = platformUtilsService.isSelfHost(); + this.selfHosted = this.platformUtilsService.isSelfHost(); } async ngOnInit() { @@ -660,21 +662,26 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.buildSecretsManagerRequest(request); if (this.upgradeRequiresPaymentMethod) { - let type: PaymentMethodType; - let token: string; - if (this.deprecateStripeSourcesAPI) { - ({ type, token } = await this.paymentV2Component.tokenize()); + const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); + updatePaymentMethodRequest.paymentSource = await this.paymentV2Component.tokenize(); + const expandedTaxInfoUpdateRequest = new ExpandedTaxInfoUpdateRequest(); + expandedTaxInfoUpdateRequest.country = this.taxComponent.country; + expandedTaxInfoUpdateRequest.postalCode = this.taxComponent.postalCode; + updatePaymentMethodRequest.taxInformation = expandedTaxInfoUpdateRequest; + await this.billingApiService.updateOrganizationPaymentMethod( + this.organizationId, + updatePaymentMethodRequest, + ); } else { - [token, type] = await this.paymentComponent.createPaymentToken(); + const [paymentToken, paymentMethodType] = await this.paymentComponent.createPaymentToken(); + const paymentRequest = new PaymentRequest(); + paymentRequest.paymentToken = paymentToken; + paymentRequest.paymentMethodType = paymentMethodType; + paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; + paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; + await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } - - const paymentRequest = new PaymentRequest(); - paymentRequest.paymentToken = token; - paymentRequest.paymentMethodType = type; - paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; - paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; - await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } // Backfill pub/priv key if necessary From e88e231d48ef3f0040833206308c8a739dbe9f4d Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Tue, 24 Sep 2024 10:36:44 -0400 Subject: [PATCH 12/23] [PM-11588] Bugfix - parse user input value for combined expiry date when creating/adding a card cipher (#11103) * simplify logic and fix some pattern-matching bugs * add first pass at parsing combined expiry year and month from user input * clean up code * fix broken three-digit parsing case * fix case where splitCombinedDateValues returns empty strings when the input is only a delimiter * fix incorrect expectation of falsy negative integers * clean up code * split out logic from parseYearMonthExpiry * move utils from vault to autofill --- .../autofill/background/overlay.background.ts | 15 +- .../autofill/services/autofill-constants.ts | 2 - .../src/autofill/services/autofill.service.ts | 15 +- .../components/vault/add-edit.component.ts | 2 +- .../individual-vault/add-edit.component.ts | 2 +- .../vault/components/add-edit.component.ts | 2 +- libs/common/src/autofill/constants/index.ts | 2 + .../src/autofill/constants/match-patterns.ts | 26 ++ libs/common/src/autofill/utils.spec.ts | 284 ++++++++++++++++ libs/common/src/autofill/utils.ts | 307 ++++++++++++++++++ .../common/src/vault/models/view/card.view.ts | 2 +- libs/common/src/vault/utils.spec.ts | 122 ------- libs/common/src/vault/utils.ts | 83 ----- libs/importer/src/importers/base-importer.ts | 2 +- .../card-details-section.component.ts | 2 +- .../src/cipher-view/cipher-view.component.ts | 2 +- 16 files changed, 648 insertions(+), 222 deletions(-) create mode 100644 libs/common/src/autofill/constants/match-patterns.ts create mode 100644 libs/common/src/autofill/utils.spec.ts create mode 100644 libs/common/src/autofill/utils.ts delete mode 100644 libs/common/src/vault/utils.spec.ts delete mode 100644 libs/common/src/vault/utils.ts diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 0047d1de28e..c8d250df509 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -20,6 +20,7 @@ import { import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { parseYearMonthExpiry } from "@bitwarden/common/autofill/utils"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { @@ -1898,11 +1899,21 @@ export class OverlayBackground implements OverlayBackgroundInterface { const cardView = new CardView(); cardView.cardholderName = card.cardholderName || ""; cardView.number = card.number || ""; - cardView.expMonth = card.expirationMonth || ""; - cardView.expYear = card.expirationYear || ""; cardView.code = card.cvv || ""; cardView.brand = card.number ? CardView.getCardBrandByPatterns(card.number) : ""; + // If there's a combined expiration date value and no individual month or year values, + // try to parse them from the combined value + if (card.expirationDate && !card.expirationMonth && !card.expirationYear) { + const [parsedYear, parsedMonth] = parseYearMonthExpiry(card.expirationDate); + + cardView.expMonth = parsedMonth || ""; + cardView.expYear = parsedYear || ""; + } else { + cardView.expMonth = card.expirationMonth || ""; + cardView.expYear = card.expirationYear || ""; + } + const cipherView = new CipherView(); cipherView.name = ""; cipherView.folderId = null; diff --git a/apps/browser/src/autofill/services/autofill-constants.ts b/apps/browser/src/autofill/services/autofill-constants.ts index 9cf2b6848c6..c379daaf2d8 100644 --- a/apps/browser/src/autofill/services/autofill-constants.ts +++ b/apps/browser/src/autofill/services/autofill-constants.ts @@ -300,8 +300,6 @@ export class CreditCardAutoFillConstants { "cb-type", ]; - static readonly CardExpiryDateDelimiters: string[] = ["/", "-", ".", " "]; - // Note, these are expressions of user-guidance for the expected expiry date format to be used static readonly CardExpiryDateFormats: CardExpiryDateFormat[] = [ // English diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 49d00624f34..5d9bfa9f9d4 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -6,11 +6,15 @@ import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; +import { + AutofillOverlayVisibility, + CardExpiryDateDelimiters, +} from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -30,7 +34,6 @@ import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { BrowserApi } from "../../platform/browser/browser-api"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; @@ -1397,8 +1400,7 @@ export default class AutofillService implements AutofillServiceInterface { if (expectedExpiryDateFormat) { const { Month, MonthShort, Year } = expiryDateFormatPatterns; - const expiryDateDelimitersPattern = - "\\" + CreditCardAutoFillConstants.CardExpiryDateDelimiters.join("\\"); + const expiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\"); // assign the delimiter from the expected format string delimiter = @@ -1450,8 +1452,7 @@ export default class AutofillService implements AutofillServiceInterface { let expectedDateFormat = null; let dateFormatPatterns = null; - const expiryDateDelimitersPattern = - "\\" + CreditCardAutoFillConstants.CardExpiryDateDelimiters.join("\\"); + const expiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\"); CreditCardAutoFillConstants.CardExpiryDateFormats.find((dateFormat) => { dateFormatPatterns = dateFormat; @@ -1489,6 +1490,8 @@ export default class AutofillService implements AutofillServiceInterface { return false; }); }); + // @TODO if expectedDateFormat is still null, and there is a `pattern` attribute, cycle + // through generated formatted values, checking against the provided regex pattern return [expectedDateFormat, dateFormatPatterns]; } diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 02654f37efe..de8e5615e2f 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -12,6 +12,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -23,7 +24,6 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index d1b51b611f5..9826d9f2f5a 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -8,6 +8,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { isCardExpired } from "@bitwarden/common/autofill/utils"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { EventType } from "@bitwarden/common/enums"; @@ -24,7 +25,6 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; -import { isCardExpired } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 255d553a3ec..21a7b35ac51 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -12,6 +12,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { EventType } from "@bitwarden/common/enums"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -36,7 +37,6 @@ import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view" import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index 15005691d29..4ccec81a447 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -109,3 +109,5 @@ export type ExtensionCommandType = (typeof ExtensionCommand)[keyof typeof Extens export const CLEAR_NOTIFICATION_LOGIN_DATA_DURATION = 60 * 1000; // 1 minute export const MAX_DEEP_QUERY_RECURSION_DEPTH = 4; + +export * from "./match-patterns"; diff --git a/libs/common/src/autofill/constants/match-patterns.ts b/libs/common/src/autofill/constants/match-patterns.ts new file mode 100644 index 00000000000..f756537d28d --- /dev/null +++ b/libs/common/src/autofill/constants/match-patterns.ts @@ -0,0 +1,26 @@ +export const CardExpiryDateDelimiters: string[] = ["/", "-", ".", " "]; + +// `CardExpiryDateDelimiters` is not intended solely for regex consumption, +// so we need to format it here +export const ExpiryDateDelimitersPattern = + "\\" + + CardExpiryDateDelimiters.join("\\") + // replace space character with the regex whitespace character class + .replace(" ", "s"); + +export const MonthPattern = "(([1]{1}[0-2]{1})|(0?[1-9]{1}))"; + +// Because we're dealing with expiry dates, we assume the year will be in current or next century (as of 2024) +export const ExpiryFullYearPattern = "2[0-1]{1}\\d{2}"; + +export const DelimiterPatternExpression = new RegExp(`[${ExpiryDateDelimitersPattern}]`, "g"); + +export const IrrelevantExpiryCharactersPatternExpression = new RegExp( + // "nor digits" to ensure numbers are removed from guidance pattern, which aren't covered by ^\w + `[^\\d${ExpiryDateDelimitersPattern}]`, + "g", +); + +export const MonthPatternExpression = new RegExp(`^${MonthPattern}$`); + +export const ExpiryFullYearPatternExpression = new RegExp(`^${ExpiryFullYearPattern}$`); diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts new file mode 100644 index 00000000000..b09dc723b8e --- /dev/null +++ b/libs/common/src/autofill/utils.spec.ts @@ -0,0 +1,284 @@ +import { + normalizeExpiryYearFormat, + isCardExpired, + parseYearMonthExpiry, +} from "@bitwarden/common/autofill/utils"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; + +function getExpiryYearValueFormats(currentCentury: string) { + return [ + [-12, `${currentCentury}12`], + [0, `${currentCentury}00`], + [2043, "2043"], // valid year with a length of four should be taken directly + [24, `${currentCentury}24`], + [3054, "3054"], // valid year with a length of four should be taken directly + [31423524543, `${currentCentury}43`], + [4, `${currentCentury}04`], + [null, null], + [undefined, null], + ["-12", `${currentCentury}12`], + ["", null], + ["0", `${currentCentury}00`], + ["00", `${currentCentury}00`], + ["000", `${currentCentury}00`], + ["0000", `${currentCentury}00`], + ["00000", `${currentCentury}00`], + ["0234234", `${currentCentury}34`], + ["04", `${currentCentury}04`], + ["2043", "2043"], // valid year with a length of four should be taken directly + ["24", `${currentCentury}24`], + ["3054", "3054"], // valid year with a length of four should be taken directly + ["31423524543", `${currentCentury}43`], + ["4", `${currentCentury}04`], + ["aaaa", null], + ["adgshsfhjsdrtyhsrth", null], + ["agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", `${currentCentury}45`], + ]; +} + +describe("normalizeExpiryYearFormat", () => { + const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); + + const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); + + expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { + it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { + const formattedValue = normalizeExpiryYearFormat(inputValue); + + expect(formattedValue).toEqual(expectedValue); + }); + }); + + describe("in the year 3107", () => { + const theDistantFuture = new Date(Date.UTC(3107, 1, 1)); + jest.spyOn(Date, "now").mockReturnValue(theDistantFuture.valueOf()); + + beforeAll(() => { + jest.useFakeTimers({ advanceTimers: true }); + jest.setSystemTime(theDistantFuture); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + const currentCentury = `${new Date(Date.now()).getFullYear()}`.slice(0, 2); + expect(currentCentury).toBe("31"); + + const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); + + expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { + it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { + const formattedValue = normalizeExpiryYearFormat(inputValue); + + expect(formattedValue).toEqual(expectedValue); + }); + }); + jest.clearAllTimers(); + }); +}); + +function getCardExpiryDateValues() { + const currentDate = new Date(); + + const currentYear = currentDate.getFullYear(); + + // `Date` months are zero-indexed, our expiry date month inputs are one-indexed + const currentMonth = currentDate.getMonth() + 1; + + return [ + [null, null, false], // no month, no year + [undefined, undefined, false], // no month, no year, invalid values + ["", "", false], // no month, no year, invalid values + ["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values + ["0", `${currentYear}`, true], // invalid month + ["0", `${currentYear - 1}`, true], // invalid 0 month + ["00", `${currentYear + 1}`, false], // invalid 0 month + [`${currentMonth}`, "0000", true], // current month, in the year 2000 + [null, `${currentYear}`.slice(-2), false], // no month, this year + [null, `${currentYear - 1}`.slice(-2), true], // no month, last year + ["1", null, false], // no year, January + ["1", `${currentYear - 1}`, true], // January last year + ["13", `${currentYear}`, false], // 12 + 1 is Feb. in the next year (Date is zero-indexed) + [`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired + [`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over) + [`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over) + [`${currentMonth - 1}`, `${currentYear}`, true], // last month + [`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now + ]; +} + +describe("isCardExpired", () => { + const expiryYearValueFormats = getCardExpiryDateValues(); + + expiryYearValueFormats.forEach( + ([inputMonth, inputYear, expectedValue]: [string | null, string | null, boolean]) => { + it(`should return ${expectedValue} when the card expiry month is ${inputMonth} and the card expiry year is ${inputYear}`, () => { + const testCardView = new CardView(); + testCardView.expMonth = inputMonth; + testCardView.expYear = inputYear; + + const cardIsExpired = isCardExpired(testCardView); + + expect(cardIsExpired).toBe(expectedValue); + }); + }, + ); +}); + +const combinedDateTestValues = [ + " 2024 / 05 ", + "05 2024", + "05 2024", // Tab whitespace character + "05 2024", // Em Quad + "05 2024", // Em Space + "05 2024", // En Quad + "05 2024", // En Space + "05 2024", // Figure Space + "05 2024", // Four-Per-Em Space + "05 2024", // Hair Space + "05 2024", // Ideographic Space + "05 2024", // Medium Mathematical Space + "05 2024", // No-Break Space + "05 2024", // ogham space mark + "05 2024", // Punctuation Space + "05 2024", // Six-Per-Em Space + "05 2024", // Thin Space + "05 2024", // Three-Per-Em Space + "05 24", + "05-2024", + "05-24", + "05.2024", + "05.24", + "05/2024", + "05/24", + "052024", + "0524", + "2024 05", + "2024 5", + "2024-05", + "2024-5", + "2024.05", + "2024.5", + "2024/05", + "2024/5", + "202405", + "20245", + "24 05", + "24 5", + "24-05", + "24-5", + "24.05", + "24.5", + "24/05", + "24/5", + "2405", + "5 2024", + "5 24", + "5-2024", + "5-24", + "5.2024", + "5.24", + "5/2024", + "5/24", + "52024", +]; +const expectedParsedValue = ["2024", "5"]; +describe("parseYearMonthExpiry", () => { + it('returns "null" expiration year and month values when a value of "" is passed', () => { + expect(parseYearMonthExpiry("")).toStrictEqual([null, null]); + }); + + it('returns "null" expiration year and month values when a value of "/" is passed', () => { + expect(parseYearMonthExpiry("/")).toStrictEqual([null, null]); + }); + + combinedDateTestValues.forEach((combinedDate) => { + it(`returns an expiration year value of "${expectedParsedValue[0]}" and month value of "${expectedParsedValue[1]}" when a value of "${combinedDate}" is passed`, () => { + expect(parseYearMonthExpiry(combinedDate)).toStrictEqual(expectedParsedValue); + }); + }); + + it('returns an expiration year value of "2002" and month value of "2" when a value of "022" is passed', () => { + expect(parseYearMonthExpiry("022")).toStrictEqual(["2002", "2"]); + }); + + it('returns an expiration year value of "2002" and month value of "2" when a value of "202" is passed', () => { + expect(parseYearMonthExpiry("202")).toStrictEqual(["2002", "2"]); + }); + + it('returns an expiration year value of "2002" and month value of "1" when a value of "1/2/3/4" is passed', () => { + expect(parseYearMonthExpiry("1/2/3/4")).toStrictEqual(["2002", "1"]); + }); + + it('returns valid expiration year and month values when a value of "198" is passed', () => { + // This static value will cause the test to fail in 2098 + const testValue = "198"; + const parsedValue = parseYearMonthExpiry(testValue); + + expect(parsedValue[0]).toHaveLength(4); + expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/); + + expect(parsedValue).toStrictEqual(["2098", "1"]); + }); + + // Ambiguous input cases: we use try/catch for these cases as a workaround to accept either + // outcome (both are valid interpretations) in the event of any future code changes. + describe("ambiguous input cases", () => { + it('returns valid expiration year and month values when a value of "111" is passed', () => { + const testValue = "111"; + const parsedValue = parseYearMonthExpiry(testValue); + + expect(parsedValue[0]).toHaveLength(4); + expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/); + + try { + expect(parsedValue).toStrictEqual(["2011", "1"]); + } catch { + expect(parsedValue).toStrictEqual(["2001", "11"]); + } + }); + + it('returns valid expiration year and month values when a value of "212" is passed', () => { + const testValue = "212"; + const parsedValue = parseYearMonthExpiry(testValue); + + expect(parsedValue[0]).toHaveLength(4); + expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/); + + try { + expect(parsedValue).toStrictEqual(["2012", "2"]); + } catch { + expect(parsedValue).toStrictEqual(["2021", "2"]); + } + }); + + it('returns valid expiration year and month values when a value of "245" is passed', () => { + const testValue = "245"; + const parsedValue = parseYearMonthExpiry(testValue); + + expect(parsedValue[0]).toHaveLength(4); + expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/); + + try { + expect(parsedValue).toStrictEqual(["2045", "2"]); + } catch { + expect(parsedValue).toStrictEqual(["2024", "5"]); + } + }); + + it('returns valid expiration year and month values when a value of "524" is passed', () => { + const testValue = "524"; + const parsedValue = parseYearMonthExpiry(testValue); + + expect(parsedValue[0]).toHaveLength(4); + expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/); + + try { + expect(parsedValue).toStrictEqual(["2024", "5"]); + } catch { + expect(parsedValue).toStrictEqual(["2052", "4"]); + } + }); + }); +}); diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts new file mode 100644 index 00000000000..86411691ea2 --- /dev/null +++ b/libs/common/src/autofill/utils.ts @@ -0,0 +1,307 @@ +import { + DelimiterPatternExpression, + ExpiryFullYearPattern, + ExpiryFullYearPatternExpression, + IrrelevantExpiryCharactersPatternExpression, + MonthPatternExpression, +} from "@bitwarden/common/autofill/constants"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; + +type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; +type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; + +/** + * Takes a string or number value and returns a string value formatted as a valid 4-digit year + * + * @param {(string | number)} yearInput + * @return {*} {(Year | null)} + */ +export function normalizeExpiryYearFormat(yearInput: string | number): Year | null { + // The input[type="number"] is returning a number, convert it to a string + // An empty field returns null, avoid casting `"null"` to a string + const yearInputIsEmpty = yearInput == null || yearInput === ""; + let expirationYear = yearInputIsEmpty ? null : `${yearInput}`; + + // Exit early if year is already formatted correctly or empty + if (yearInputIsEmpty || /^[1-9]{1}\d{3}$/.test(expirationYear)) { + return expirationYear as Year; + } + + expirationYear = expirationYear + // For safety, because even input[type="number"] will allow decimals + .replace(/[^\d]/g, "") + // remove any leading zero padding (leave the last leading zero if it ends the string) + .replace(/^[0]+(?=.)/, ""); + + if (expirationYear === "") { + expirationYear = null; + } + + // given the context of payment card expiry, a year character length of 3, or over 4 + // is more likely to be a mistake than an intentional value for the far past or far future. + if (expirationYear && expirationYear.length !== 4) { + const paddedYear = ("00" + expirationYear).slice(-2); + const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); + + expirationYear = currentCentury + paddedYear; + } + + return expirationYear as Year | null; +} + +/** + * Takes a cipher card view and returns "true" if the month and year affirmativey indicate + * the card is expired. + * + * @param {CardView} cipherCard + * @return {*} {boolean} + */ +export function isCardExpired(cipherCard: CardView): boolean { + if (cipherCard) { + const { expMonth = null, expYear = null } = cipherCard; + + const now = new Date(); + const normalizedYear = normalizeExpiryYearFormat(expYear); + + // If the card year is before the current year, don't bother checking the month + if (normalizedYear && parseInt(normalizedYear, 10) < now.getFullYear()) { + return true; + } + + if (normalizedYear && expMonth) { + const parsedMonthInteger = parseInt(expMonth, 10); + + const parsedMonth = isNaN(parsedMonthInteger) + ? 0 + : // Add a month floor of 0 to protect against an invalid low month value of "0" or negative integers + Math.max( + // `Date` months are zero-indexed + parsedMonthInteger - 1, + 0, + ); + + const parsedYear = parseInt(normalizedYear, 10); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + + return cardExpiry < now; + } + } + + return false; +} + +/** + * Attempt to split a string into date segments on the basis of expected formats and delimiter symbols. + * + * @param {string} combinedExpiryValue + * @return {*} {string[]} + */ +function splitCombinedDateValues(combinedExpiryValue: string): string[] { + let sanitizedValue = combinedExpiryValue + .replace(IrrelevantExpiryCharactersPatternExpression, "") + .trim(); + + // Do this after initial value replace to avoid identifying leading whitespace as delimiter + const parsedDelimiter = sanitizedValue.match(DelimiterPatternExpression)?.[0] || null; + + let dateParts = [sanitizedValue]; + + if (parsedDelimiter?.length) { + // If the parsed delimiter is a whitespace character, assign 's' (character class) instead + const delimiterPattern = /\s/.test(parsedDelimiter) ? "\\s" : "\\" + parsedDelimiter; + + sanitizedValue = sanitizedValue + // Remove all other delimiter characters not identified as the delimiter + .replace(new RegExp(`[^\\d${delimiterPattern}]`, "g"), "") + // Also de-dupe the delimiter character + .replace(new RegExp(`[${delimiterPattern}]{2,}`, "g"), parsedDelimiter); + + dateParts = sanitizedValue.split(parsedDelimiter); + } + + return ( + dateParts + // remove values that have no length + .filter((splitValue) => splitValue?.length) + ); +} + +/** + * Given an array of split card expiry date parts, + * returns an array of those values ordered by year then month + * + * @param {string[]} splitDateInput + * @return {*} {([string | null, string | null])} + */ +function parseDelimitedYearMonthExpiry([firstPart, secondPart]: string[]): [string, string] { + // Conditionals here are structured to avoid unnecessary evaluations and are ordered + // from more authoritative checks to checks yielding increasingly inferred conclusions + + // If a 4-digit value is found (when there are multiple parts), it can't be month + if (ExpiryFullYearPatternExpression.test(firstPart)) { + return [firstPart, secondPart]; + } + + // If a 4-digit value is found (when there are multiple parts), it can't be month + if (ExpiryFullYearPatternExpression.test(secondPart)) { + return [secondPart, firstPart]; + } + + // If it's a two digit value that doesn't match against month pattern, assume it's a year + if (/\d{2}/.test(firstPart) && !MonthPatternExpression.test(firstPart)) { + return [firstPart, secondPart]; + } + + // If it's a two digit value that doesn't match against month pattern, assume it's a year + if (/\d{2}/.test(secondPart) && !MonthPatternExpression.test(secondPart)) { + return [secondPart, firstPart]; + } + + // Values are too ambiguous (e.g. "12/09"). For the most part, + // a month-looking value likely is, at the time of writing (year 2024). + let parsedYear = firstPart; + let parsedMonth = secondPart; + + if (MonthPatternExpression.test(firstPart)) { + parsedYear = secondPart; + parsedMonth = firstPart; + } + + return [parsedYear, parsedMonth]; +} + +/** + * Given a single string of integers, attempts to identify card expiry date portions within + * and return values ordered by year then month + * + * @param {string} dateInput + * @return {*} {([string | null, string | null])} + */ +function parseNonDelimitedYearMonthExpiry(dateInput: string): [string | null, string | null] { + if (dateInput.length > 4) { + // e.g. + // "052024" + // "202405" + // "20245" + // "52024" + + // If the value is over 5-characters long, it likely has a full year format in it + const [parsedYear, parsedMonth] = dateInput + .split(new RegExp(`(?=${ExpiryFullYearPattern})|(?<=${ExpiryFullYearPattern})`, "g")) + .sort((current: string, next: string) => (current.length > next.length ? -1 : 1)); + + return [parsedYear, parsedMonth]; + } + + if (dateInput.length === 4) { + // e.g. + // "0524" + // "2405" + + // If the `sanitizedFirstPart` value is a length of 4, it must be split in half, since + // neither a year or month will be represented with three characters + const splitFirstPartFirstHalf = dateInput.slice(0, 2); + const splitFirstPartSecondHalf = dateInput.slice(-2); + + let parsedYear = splitFirstPartSecondHalf; + let parsedMonth = splitFirstPartFirstHalf; + + // If the first part doesn't match a month pattern, assume it's a year + if (!MonthPatternExpression.test(splitFirstPartFirstHalf)) { + parsedYear = splitFirstPartFirstHalf; + parsedMonth = splitFirstPartSecondHalf; + } + + return [parsedYear, parsedMonth]; + } + + // e.g. + // "245" + // "202" + // "212" + // "022" + // "111" + + // A valid year representation here must be two characters so try to find it first. + + let parsedYear = null; + let parsedMonth = null; + + // Split if there is a digit with a leading zero + const splitFirstPartOnLeadingZero = dateInput.split(/(?<=0[1-9]{1})|(?=0[1-9]{1})/); + + // Assume a leading zero indicates a month in ambiguous cases (e.g. "202"), since we're + // dealing with expiry dates and the next two-digit year with a leading zero will be 2100 + if (splitFirstPartOnLeadingZero.length > 1) { + parsedYear = splitFirstPartOnLeadingZero[0]; + parsedMonth = splitFirstPartOnLeadingZero[1]; + + if (splitFirstPartOnLeadingZero[0].startsWith("0")) { + parsedMonth = splitFirstPartOnLeadingZero[0]; + parsedYear = splitFirstPartOnLeadingZero[1]; + } + } else { + // Here, a year has to be two-digits, and a month can't be more than one, so assume the first two digits that are greater than the current year is the year representation. + parsedYear = dateInput.slice(0, 2); + parsedMonth = dateInput.slice(-1); + + const currentYear = new Date().getFullYear(); + const normalizedParsedYear = parseInt(normalizeExpiryYearFormat(parsedYear), 10); + const normalizedParsedYearAlternative = parseInt( + normalizeExpiryYearFormat(dateInput.slice(-2)), + 10, + ); + + if (normalizedParsedYear < currentYear && normalizedParsedYearAlternative >= currentYear) { + parsedYear = dateInput.slice(-2); + parsedMonth = dateInput.slice(0, 1); + } + } + + return [parsedYear, parsedMonth]; +} + +/** + * Attempt to parse year and month parts of a combined expiry date value. + * + * @param {string} combinedExpiryValue + * @return {*} {([string | null, string | null])} + */ +export function parseYearMonthExpiry(combinedExpiryValue: string): [Year | null, string | null] { + let parsedYear = null; + let parsedMonth = null; + + const dateParts = splitCombinedDateValues(combinedExpiryValue); + + if (dateParts.length < 1) { + return [null, null]; + } + + const sanitizedFirstPart = + dateParts[0]?.replace(IrrelevantExpiryCharactersPatternExpression, "") || ""; + const sanitizedSecondPart = + dateParts[1]?.replace(IrrelevantExpiryCharactersPatternExpression, "") || ""; + + // If there is only one date part, no delimiter was found in the passed value + if (dateParts.length === 1) { + [parsedYear, parsedMonth] = parseNonDelimitedYearMonthExpiry(sanitizedFirstPart); + } + // There are multiple date parts + else { + [parsedYear, parsedMonth] = parseDelimitedYearMonthExpiry([ + sanitizedFirstPart, + sanitizedSecondPart, + ]); + } + + const normalizedParsedYear = normalizeExpiryYearFormat(parsedYear); + const normalizedParsedMonth = parsedMonth?.replace(/^0+/, "").slice(0, 2); + + // Set "empty" values to null + parsedYear = normalizedParsedYear?.length ? normalizedParsedYear : null; + parsedMonth = normalizedParsedMonth?.length ? normalizedParsedMonth : null; + + return [parsedYear, parsedMonth]; +} diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index f3bf4e1fab2..fad10851e6a 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -1,8 +1,8 @@ import { Jsonify } from "type-fest"; +import { normalizeExpiryYearFormat } from "../../../autofill/utils"; import { CardLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; -import { normalizeExpiryYearFormat } from "../../utils"; import { ItemView } from "./item.view"; diff --git a/libs/common/src/vault/utils.spec.ts b/libs/common/src/vault/utils.spec.ts deleted file mode 100644 index 54ec66984e2..00000000000 --- a/libs/common/src/vault/utils.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; -import { normalizeExpiryYearFormat, isCardExpired } from "@bitwarden/common/vault/utils"; - -function getExpiryYearValueFormats(currentCentury: string) { - return [ - [-12, `${currentCentury}12`], - [0, `${currentCentury}00`], - [2043, "2043"], // valid year with a length of four should be taken directly - [24, `${currentCentury}24`], - [3054, "3054"], // valid year with a length of four should be taken directly - [31423524543, `${currentCentury}43`], - [4, `${currentCentury}04`], - [null, null], - [undefined, null], - ["-12", `${currentCentury}12`], - ["", null], - ["0", `${currentCentury}00`], - ["00", `${currentCentury}00`], - ["000", `${currentCentury}00`], - ["0000", `${currentCentury}00`], - ["00000", `${currentCentury}00`], - ["0234234", `${currentCentury}34`], - ["04", `${currentCentury}04`], - ["2043", "2043"], // valid year with a length of four should be taken directly - ["24", `${currentCentury}24`], - ["3054", "3054"], // valid year with a length of four should be taken directly - ["31423524543", `${currentCentury}43`], - ["4", `${currentCentury}04`], - ["aaaa", null], - ["adgshsfhjsdrtyhsrth", null], - ["agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", `${currentCentury}45`], - ]; -} - -describe("normalizeExpiryYearFormat", () => { - const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); - - const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); - - expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { - it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { - const formattedValue = normalizeExpiryYearFormat(inputValue); - - expect(formattedValue).toEqual(expectedValue); - }); - }); - - describe("in the year 3107", () => { - const theDistantFuture = new Date(Date.UTC(3107, 1, 1)); - jest.spyOn(Date, "now").mockReturnValue(theDistantFuture.valueOf()); - - beforeAll(() => { - jest.useFakeTimers({ advanceTimers: true }); - jest.setSystemTime(theDistantFuture); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - - const currentCentury = `${new Date(Date.now()).getFullYear()}`.slice(0, 2); - expect(currentCentury).toBe("31"); - - const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); - - expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { - it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { - const formattedValue = normalizeExpiryYearFormat(inputValue); - - expect(formattedValue).toEqual(expectedValue); - }); - }); - jest.clearAllTimers(); - }); -}); - -function getCardExpiryDateValues() { - const currentDate = new Date(); - - const currentYear = currentDate.getFullYear(); - - // `Date` months are zero-indexed, our expiry date month inputs are one-indexed - const currentMonth = currentDate.getMonth() + 1; - - return [ - [null, null, false], // no month, no year - [undefined, undefined, false], // no month, no year, invalid values - ["", "", false], // no month, no year, invalid values - ["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values - ["0", `${currentYear - 1}`, true], // invalid 0 month - ["00", `${currentYear + 1}`, false], // invalid 0 month - [`${currentMonth}`, "0000", true], // current month, in the year 2000 - [null, `${currentYear}`.slice(-2), false], // no month, this year - [null, `${currentYear - 1}`.slice(-2), true], // no month, last year - ["1", null, false], // no year, January - ["1", `${currentYear - 1}`, true], // January last year - ["13", `${currentYear}`, false], // 12 + 1 is Feb. in the next year (Date is zero-indexed) - [`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired - [`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over) - [`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over) - [`${currentMonth - 1}`, `${currentYear}`, true], // last month - [`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now - ]; -} - -describe("isCardExpired", () => { - const expiryYearValueFormats = getCardExpiryDateValues(); - - expiryYearValueFormats.forEach( - ([inputMonth, inputYear, expectedValue]: [string | null, string | null, boolean]) => { - it(`should return ${expectedValue} when the card expiry month is ${inputMonth} and the card expiry year is ${inputYear}`, () => { - const testCardView = new CardView(); - testCardView.expMonth = inputMonth; - testCardView.expYear = inputYear; - - const cardIsExpired = isCardExpired(testCardView); - - expect(cardIsExpired).toBe(expectedValue); - }); - }, - ); -}); diff --git a/libs/common/src/vault/utils.ts b/libs/common/src/vault/utils.ts deleted file mode 100644 index 7d8784eda78..00000000000 --- a/libs/common/src/vault/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; - -type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; -type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; - -/** - * Takes a string or number value and returns a string value formatted as a valid 4-digit year - * - * @export - * @param {(string | number)} yearInput - * @return {*} {(Year | null)} - */ -export function normalizeExpiryYearFormat(yearInput: string | number): Year | null { - // The input[type="number"] is returning a number, convert it to a string - // An empty field returns null, avoid casting `"null"` to a string - const yearInputIsEmpty = yearInput == null || yearInput === ""; - let expirationYear = yearInputIsEmpty ? null : `${yearInput}`; - - // Exit early if year is already formatted correctly or empty - if (yearInputIsEmpty || /^[1-9]{1}\d{3}$/.test(expirationYear)) { - return expirationYear as Year; - } - - expirationYear = expirationYear - // For safety, because even input[type="number"] will allow decimals - .replace(/[^\d]/g, "") - // remove any leading zero padding (leave the last leading zero if it ends the string) - .replace(/^[0]+(?=.)/, ""); - - if (expirationYear === "") { - expirationYear = null; - } - - // given the context of payment card expiry, a year character length of 3, or over 4 - // is more likely to be a mistake than an intentional value for the far past or far future. - if (expirationYear && expirationYear.length !== 4) { - const paddedYear = ("00" + expirationYear).slice(-2); - const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); - - expirationYear = currentCentury + paddedYear; - } - - return expirationYear as Year | null; -} - -/** - * Takes a cipher card view and returns "true" if the month and year affirmativey indicate - * the card is expired. - * - * @export - * @param {CardView} cipherCard - * @return {*} {boolean} - */ -export function isCardExpired(cipherCard: CardView): boolean { - if (cipherCard) { - const { expMonth = null, expYear = null } = cipherCard; - - const now = new Date(); - const normalizedYear = normalizeExpiryYearFormat(expYear); - - // If the card year is before the current year, don't bother checking the month - if (normalizedYear && parseInt(normalizedYear) < now.getFullYear()) { - return true; - } - - if (normalizedYear && expMonth) { - // `Date` months are zero-indexed - const parsedMonth = - parseInt(expMonth) - 1 || - // Add a month floor of 0 to protect against an invalid low month value of "0" - 0; - - const parsedYear = parseInt(normalizedYear); - - // First day of the next month minus one, to get last day of the card month - const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); - - return cardExpiry < now; - } - } - - return false; -} diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 215210eda14..9cba62c5faf 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -1,5 +1,6 @@ import * as papa from "papaparse"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; @@ -11,7 +12,6 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { ImportResult } from "../models/import-result"; diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index e1ef3dc0f37..bc4ff608805 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -5,11 +5,11 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { CardComponent, FormFieldModule, diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index cadf388e76d..e28f7f2a2bb 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -5,13 +5,13 @@ import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { isCardExpired } from "@bitwarden/common/autofill/utils"; import { CollectionId } from "@bitwarden/common/types/guid"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { isCardExpired } from "@bitwarden/common/vault/utils"; import { CalloutModule, SearchModule } from "@bitwarden/components"; import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; From 0089ae0886c3556b4e9f181763badaa15b181cbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:35:01 -0400 Subject: [PATCH 13/23] [deps] DevOps: Update gh minor (#11064) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-browser.yml | 4 ++-- .github/workflows/build-cli.yml | 4 ++-- .github/workflows/build-desktop.yml | 14 +++++++------- .github/workflows/build-web.yml | 2 +- .github/workflows/chromatic.yml | 4 ++-- .github/workflows/lint.yml | 2 +- .github/workflows/release-desktop-beta.yml | 10 +++++----- .github/workflows/scan.yml | 4 ++-- .github/workflows/test.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 610769859fe..562567ffe77 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -114,7 +114,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -257,7 +257,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 76d86b45500..bccfc6d57ce 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -93,7 +93,7 @@ jobs: awk '{print tolower($0)}')" >> $GITHUB_ENV - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -171,7 +171,7 @@ jobs: choco install nasm --no-progress - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index ddb87320839..05039c3982b 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -143,7 +143,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -252,7 +252,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -458,7 +458,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -622,7 +622,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -839,7 +839,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -1045,7 +1045,7 @@ jobs: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop') - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 with: channel-id: C074F5UESQ0 payload: | @@ -1084,7 +1084,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index d875078757c..54e993edb33 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -94,7 +94,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index c8dd3e77838..272b68139ac 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -38,7 +38,7 @@ jobs: echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: ${{ steps.retrieve-node-version.outputs.node_version }} @@ -57,7 +57,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@b984808b772126a9f44b2b7737b131b68a2ede32 # v11.7.1 + uses: chromaui/action@c883154c39671e194613be2f09ff4d17bb95f1e6 # v11.10.3 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bb495a5a26d..3f72d62214b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -48,7 +48,7 @@ jobs: echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 5a6a3d52361..80b1ae092c6 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -130,7 +130,7 @@ jobs: ref: ${{ needs.setup.outputs.branch-name }} - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -220,7 +220,7 @@ jobs: ref: ${{ needs.setup.outputs.branch-name }} - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -409,7 +409,7 @@ jobs: ref: ${{ needs.setup.outputs.branch-name }} - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -543,7 +543,7 @@ jobs: ref: ${{ needs.setup.outputs.branch-name }} - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' @@ -756,7 +756,7 @@ jobs: ref: ${{ needs.setup.outputs.branch-name }} - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 076bfb46e80..2320bae72f3 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -32,7 +32,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@1fe318de2993222574e6249750ba9000a4e2a6cd # 2.0.33 + uses: checkmarx/ast-github-action@9fda5a4a2c297608117a5a56af424502a9192e57 # 2.0.34 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -47,7 +47,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/upload-sarif@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 with: sarif_file: cx_result.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d4067c1167..fbe0c0a798b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,7 @@ jobs: echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT - name: Set up Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 71e7d3c10aa..fea68161bab 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -447,7 +447,7 @@ jobs: echo "EOF" >> $GITHUB_ENV - name: Generate GH App token - uses: actions/create-github-app-token@3378cda945da322a8db4b193e19d46352ebe2de5 # v1.10.4 + uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} From e3c75b3c1b4ec36c13b5fe2387ab1941eb6e56fb Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:07:29 +0100 Subject: [PATCH 14/23] Resolve the payment display (#11219) --- .../organizations/change-plan-dialog.component.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 63ac3afb930..42a987664f0 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -403,11 +403,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get upgradeRequiresPaymentMethod() { - return ( - this.organization?.productTierType === ProductTierType.Free && - !this.showFree && - !this.billing?.paymentSource - ); + const isFreeTier = this.organization?.productTierType === ProductTierType.Free; + const shouldHideFree = !this.showFree; + const hasNoPaymentSource = this.deprecateStripeSourcesAPI + ? !this.paymentSource + : !this.billing?.paymentSource; + + return isFreeTier && shouldHideFree && hasNoPaymentSource; } get selectedSecretsManagerPlan() { From 3646214a0f8ea069fd01a982a0fdd86035c38646 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 24 Sep 2024 14:13:27 -0400 Subject: [PATCH 15/23] Made allCiphers$ depend on the refresh subject$ (#11225) --- apps/web/src/app/vault/org-vault/vault.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 0a8e8769715..f378e171e78 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -318,8 +318,8 @@ export class VaultComponent implements OnInit, OnDestroy { shareReplay({ refCount: true, bufferSize: 1 }), ); - const allCiphers$ = organization$.pipe( - concatMap(async (organization) => { + const allCiphers$ = combineLatest([organization$, this.refresh$]).pipe( + switchMap(async ([organization]) => { // If user swaps organization reset the addAccessToggle if (!this.showAddAccessToggle || organization) { this.addAccessToggle(0); From bdcf920e624981612d39945e283beb66c2c1c14d Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:47:10 -0400 Subject: [PATCH 16/23] Remove references to device-trust-logging feature flag (#11183) --- .../src/auth/services/device-trust.service.implementation.ts | 4 ---- libs/common/src/enums/feature-flag.enum.ts | 2 -- 2 files changed, 6 deletions(-) diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index fe0f2cff0ab..c1cf871e257 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -2,7 +2,6 @@ import { firstValueFrom, map, Observable } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; -import { FeatureFlag } from "../../enums/feature-flag.enum"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; @@ -334,9 +333,6 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { } async recordDeviceTrustLoss(): Promise { - if (!(await this.configService.getFeatureFlag(FeatureFlag.DeviceTrustLogging))) { - return; - } const deviceIdentifier = await this.appIdService.getAppId(); await this.devicesApiService.postDeviceTrustLoss(deviceIdentifier); } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 3d426aee3b7..aeefa98b5dc 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -24,7 +24,6 @@ export enum FeatureFlag { VaultBulkManagementAction = "vault-bulk-management-action", AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page", IdpAutoSubmitLogin = "idp-auto-submit-login", - DeviceTrustLogging = "pm-8285-device-trust-logging", AuthenticatorTwoFactorToken = "authenticator-2fa-token", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub", @@ -69,7 +68,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.AC2828_ProviderPortalMembersPage]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, - [FeatureFlag.DeviceTrustLogging]: FALSE, [FeatureFlag.AuthenticatorTwoFactorToken]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, [FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE, From 85b97d930481f2c35c4de14feefb1041277fa2a3 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:02:39 -0400 Subject: [PATCH 17/23] Remove authenticator-token feature flag (#11182) --- .../auth/settings/two-factor-authenticator.component.ts | 8 -------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 2 files changed, 10 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts index fdd595b7fcd..da5378f4790 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts @@ -11,7 +11,6 @@ import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/mod import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -127,13 +126,6 @@ export class TwoFactorAuthenticatorComponent } protected override async disableMethod() { - const twoFactorAuthenticatorTokenFeatureFlag = await this.configService.getFeatureFlag( - FeatureFlag.AuthenticatorTwoFactorToken, - ); - if (twoFactorAuthenticatorTokenFeatureFlag === false) { - return super.disableMethod(); - } - const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "disable" }, content: { key: "twoStepDisableDesc" }, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index aeefa98b5dc..7ac473ff6ab 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -24,7 +24,6 @@ export enum FeatureFlag { VaultBulkManagementAction = "vault-bulk-management-action", AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page", IdpAutoSubmitLogin = "idp-auto-submit-login", - AuthenticatorTwoFactorToken = "authenticator-2fa-token", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub", GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", @@ -68,7 +67,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.AC2828_ProviderPortalMembersPage]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, - [FeatureFlag.AuthenticatorTwoFactorToken]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, [FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE, [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, From 4269489548d739dcff7829b21bba1baf119fc6c6 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Tue, 24 Sep 2024 17:07:17 -0400 Subject: [PATCH 18/23] pin regedit dependency (#11228) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2058de0dc8..57b3aa028fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,7 +164,7 @@ "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.6", "process": "0.11.10", - "regedit": "^3.0.3", + "regedit": "3.0.3", "remark-gfm": "4.0.0", "rimraf": "6.0.1", "sass": "1.74.1", diff --git a/package.json b/package.json index aafc92bbd3d..b073cd27d87 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.6", "process": "0.11.10", - "regedit": "^3.0.3", + "regedit": "3.0.3", "remark-gfm": "4.0.0", "rimraf": "6.0.1", "sass": "1.74.1", From e6c3de9f47b3ffe3cb8a5fa9e24df04683e9eee4 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 24 Sep 2024 14:30:06 -0700 Subject: [PATCH 19/23] [PM-12609] Use shareReplay for allCiphers$ observable (#11229) --- apps/web/src/app/vault/org-vault/vault.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index f378e171e78..31f981b4898 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -343,6 +343,7 @@ export class VaultComponent implements OnInit, OnDestroy { await this.searchService.indexCiphers(ciphers, organization.id); return ciphers; }), + shareReplay({ refCount: true, bufferSize: 1 }), ); const allCipherMap$ = allCiphers$.pipe( From d587be1831601ec6482a00ad6647579ab90a31ad Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 25 Sep 2024 04:07:01 -0700 Subject: [PATCH 20/23] [PM-12403] - Implement Remove Send policy on Add/edit screen (#11178) * disable edit send if policy requires * remove unused var * don't display free bitwarden families button * Revert "don't display free bitwarden families button" This reverts commit 832564d705e082efcd6c7938cc7c1d9c6b23def4. * use config instead of policy service * Revert "don't display free bitwarden families button" This reverts commit 832564d705e082efcd6c7938cc7c1d9c6b23def4. * remove unnecessary code * Use short when transforming deletionDate instead of fixed format --------- Co-authored-by: Daniel James Smith --- .../options/send-options.component.ts | 4 + .../base-send-details.component.ts | 107 ------------------ .../send-details/send-details.component.ts | 105 ++++++++++++++--- .../send-file-details.component.ts | 4 + .../send-text-details.component.ts | 4 + 5 files changed, 104 insertions(+), 120 deletions(-) delete mode 100644 libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 89ab9d19ba2..a73a3a6ad88 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -97,6 +97,7 @@ export class SendOptionsComponent implements OnInit { }); }); } + ngOnInit() { if (this.sendFormContainer.originalSendView) { this.sendOptionsForm.patchValue({ @@ -107,5 +108,8 @@ export class SendOptionsComponent implements OnInit { notes: this.sendFormContainer.originalSendView.notes, }); } + if (!this.config.areSendsAllowed) { + this.sendOptionsForm.disable(); + } } } diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts deleted file mode 100644 index b5cf8ee0c76..00000000000 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { DatePipe } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormBuilder, FormControl, Validators } from "@angular/forms"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; - -import { SendFormConfig } from "../../abstractions/send-form-config.service"; -import { SendFormContainer } from "../../send-form-container"; - -// Value = hours -export enum DatePreset { - OneHour = 1, - OneDay = 24, - TwoDays = 48, - ThreeDays = 72, - SevenDays = 168, - FourteenDays = 336, - ThirtyDays = 720, -} - -export interface DatePresetSelectOption { - name: string; - value: DatePreset | string; -} - -@Component({ - selector: "base-send-details-behavior", - template: "", -}) -export class BaseSendDetailsComponent implements OnInit { - @Input() config: SendFormConfig; - @Input() originalSendView?: SendView; - - customDeletionDateOption: DatePresetSelectOption | null = null; - datePresetOptions: DatePresetSelectOption[] = []; - - sendDetailsForm = this.formBuilder.group({ - name: new FormControl("", Validators.required), - selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required), - }); - - constructor( - protected sendFormContainer: SendFormContainer, - protected formBuilder: FormBuilder, - protected i18nService: I18nService, - protected datePipe: DatePipe, - ) { - this.sendDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { - this.sendFormContainer.patchSend((send) => { - return Object.assign(send, { - name: value.name, - deletionDate: new Date(this.formattedDeletionDate), - expirationDate: new Date(this.formattedDeletionDate), - } as SendView); - }); - }); - - this.sendFormContainer.registerChildForm("sendDetailsForm", this.sendDetailsForm); - } - - async ngOnInit() { - this.setupDeletionDatePresets(); - - if (this.originalSendView) { - this.sendDetailsForm.patchValue({ - name: this.originalSendView.name, - selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(), - }); - - if (this.originalSendView.deletionDate) { - this.customDeletionDateOption = { - name: this.datePipe.transform(this.originalSendView.deletionDate, "MM/dd/yyyy, hh:mm a"), - value: this.originalSendView.deletionDate.toString(), - }; - this.datePresetOptions.unshift(this.customDeletionDateOption); - } - } - } - - setupDeletionDatePresets() { - const defaultSelections: DatePresetSelectOption[] = [ - { name: this.i18nService.t("oneHour"), value: DatePreset.OneHour }, - { name: this.i18nService.t("oneDay"), value: DatePreset.OneDay }, - { name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays }, - { name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays }, - { name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays }, - { name: this.i18nService.t("days", "14"), value: DatePreset.FourteenDays }, - { name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays }, - ]; - - this.datePresetOptions = defaultSelections; - } - - get formattedDeletionDate(): string { - const now = new Date(); - const selectedValue = this.sendDetailsForm.controls.selectedDeletionDatePreset.value; - - if (typeof selectedValue === "string") { - return selectedValue; - } - - const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000); - return new Date(milliseconds).toString(); - } -} diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts index 0b287205be4..68a5e40a575 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts @@ -1,12 +1,14 @@ import { CommonModule, DatePipe } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { Component, OnInit, Input } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SectionComponent, SectionHeaderComponent, @@ -18,13 +20,29 @@ import { SelectModule, } from "@bitwarden/components"; +import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; import { SendOptionsComponent } from "../options/send-options.component"; -import { BaseSendDetailsComponent } from "./base-send-details.component"; import { SendFileDetailsComponent } from "./send-file-details.component"; import { SendTextDetailsComponent } from "./send-text-details.component"; +// Value = hours +export enum DatePreset { + OneHour = 1, + OneDay = 24, + TwoDays = 48, + ThreeDays = 72, + SevenDays = 168, + FourteenDays = 336, + ThirtyDays = 720, +} + +export interface DatePresetSelectOption { + name: string; + value: DatePreset | string; +} + @Component({ selector: "tools-send-details", templateUrl: "./send-details.component.html", @@ -46,10 +64,20 @@ import { SendTextDetailsComponent } from "./send-text-details.component"; SelectModule, ], }) -export class SendDetailsComponent extends BaseSendDetailsComponent implements OnInit { +export class SendDetailsComponent implements OnInit { + @Input() config: SendFormConfig; + @Input() originalSendView?: SendView; + FileSendType = SendType.File; TextSendType = SendType.Text; sendLink: string | null = null; + customDeletionDateOption: DatePresetSelectOption | null = null; + datePresetOptions: DatePresetSelectOption[] = []; + + sendDetailsForm = this.formBuilder.group({ + name: new FormControl("", Validators.required), + selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required), + }); constructor( protected sendFormContainer: SendFormContainer, @@ -58,18 +86,69 @@ export class SendDetailsComponent extends BaseSendDetailsComponent implements On protected datePipe: DatePipe, protected environmentService: EnvironmentService, ) { - super(sendFormContainer, formBuilder, i18nService, datePipe); - } + this.sendDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { + this.sendFormContainer.patchSend((send) => { + return Object.assign(send, { + name: value.name, + deletionDate: new Date(this.formattedDeletionDate), + expirationDate: new Date(this.formattedDeletionDate), + } as SendView); + }); + }); - async getSendLink() {} + this.sendFormContainer.registerChildForm("sendDetailsForm", this.sendDetailsForm); + } async ngOnInit() { - await super.ngOnInit(); - if (!this.originalSendView) { - return; + this.setupDeletionDatePresets(); + + if (this.originalSendView) { + this.sendDetailsForm.patchValue({ + name: this.originalSendView.name, + selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(), + }); + + if (this.originalSendView.deletionDate) { + this.customDeletionDateOption = { + name: this.datePipe.transform(this.originalSendView.deletionDate, "short"), + value: this.originalSendView.deletionDate.toString(), + }; + this.datePresetOptions.unshift(this.customDeletionDateOption); + } + + const env = await firstValueFrom(this.environmentService.environment$); + this.sendLink = + env.getSendUrl() + this.originalSendView.accessId + "/" + this.originalSendView.urlB64Key; } - const env = await firstValueFrom(this.environmentService.environment$); - this.sendLink = - env.getSendUrl() + this.originalSendView.accessId + "/" + this.originalSendView.urlB64Key; + + if (!this.config.areSendsAllowed) { + this.sendDetailsForm.disable(); + } + } + + setupDeletionDatePresets() { + const defaultSelections: DatePresetSelectOption[] = [ + { name: this.i18nService.t("oneHour"), value: DatePreset.OneHour }, + { name: this.i18nService.t("oneDay"), value: DatePreset.OneDay }, + { name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays }, + { name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays }, + { name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays }, + { name: this.i18nService.t("days", "14"), value: DatePreset.FourteenDays }, + { name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays }, + ]; + + this.datePresetOptions = defaultSelections; + } + + get formattedDeletionDate(): string { + const now = new Date(); + const selectedValue = this.sendDetailsForm.controls.selectedDeletionDatePreset.value; + + if (typeof selectedValue === "string") { + return selectedValue; + } + + const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000); + return new Date(milliseconds).toString(); } } diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts index 7739ca26528..447256cacdf 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts @@ -73,5 +73,9 @@ export class SendFileDetailsComponent implements OnInit { file: this.originalSendView.file, }); } + + if (!this.config.areSendsAllowed) { + this.sendFileDetailsForm.disable(); + } } } diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts index 85fc324f2f7..873f85c9e38 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts @@ -57,5 +57,9 @@ export class SendTextDetailsComponent implements OnInit { hidden: this.originalSendView.text?.hidden || false, }); } + + if (!this.config.areSendsAllowed) { + this.sendTextDetailsForm.disable(); + } } } From cd9045483bc31768698b197d9452b5b01da68c99 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 25 Sep 2024 05:03:42 -0700 Subject: [PATCH 21/23] [PM-12561] - add data attrs for send form (#11209) * add data attrs for send form * Add data-testid for toggle view password * Revert "Add data-testid for toggle view password" This reverts commit bd6fcc8c1bf6d4d61888ace8480d34b3098d0770. * move dataid to component --------- Co-authored-by: Daniel James Smith --- .../components/options/send-options.component.html | 9 ++++++++- .../components/send-details/send-details.component.html | 2 +- .../send-details/send-file-details.component.html | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html index 53483065b73..01b96e3bc5a 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html @@ -15,12 +15,19 @@ {{ "password" | i18n }} {{ "newPassword" | i18n }} - + {{ "sendPasswordDescV2" | i18n }} diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html index 98f399760be..d4c253303bd 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html @@ -23,7 +23,7 @@ {{ "sendLink" | i18n }} - + + + + + +
+

{{ "noPasswordsInList" | i18n }}

+
+ + + + + diff --git a/apps/web/src/app/vault/individual-vault/password-history.component.ts b/apps/web/src/app/vault/individual-vault/password-history.component.ts new file mode 100644 index 00000000000..21a1b01e583 --- /dev/null +++ b/apps/web/src/app/vault/individual-vault/password-history.component.ts @@ -0,0 +1,131 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { OnInit, Inject, Component } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view"; +import { + AsyncActionsModule, + DialogModule, + DialogService, + ToastService, + ItemModule, +} from "@bitwarden/components"; + +import { SharedModule } from "../../shared/shared.module"; + +/** + * The parameters for the password history dialog. + */ +export interface ViewPasswordHistoryDialogParams { + cipherId: CipherId; +} + +/** + * A dialog component that displays the password history for a cipher. + */ +@Component({ + selector: "app-vault-password-history", + templateUrl: "password-history.component.html", + standalone: true, + imports: [CommonModule, AsyncActionsModule, DialogModule, ItemModule, SharedModule], +}) +export class PasswordHistoryComponent implements OnInit { + /** + * The ID of the cipher to display the password history for. + */ + cipherId: CipherId; + + /** + * The password history for the cipher. + */ + history: PasswordHistoryView[] = []; + + /** + * The constructor for the password history dialog component. + * @param params The parameters passed to the password history dialog. + * @param cipherService The cipher service - used to get the cipher to display the password history for. + * @param platformUtilsService The platform utils service - used to copy passwords to the clipboard. + * @param i18nService The i18n service - used to translate strings. + * @param accountService The account service - used to get the active account to decrypt the cipher. + * @param win The window object - used to copy passwords to the clipboard. + * @param toastService The toast service - used to display feedback to the user when a password is copied. + * @param dialogRef The dialog reference - used to close the dialog. + **/ + constructor( + @Inject(DIALOG_DATA) public params: ViewPasswordHistoryDialogParams, + protected cipherService: CipherService, + protected platformUtilsService: PlatformUtilsService, + protected i18nService: I18nService, + protected accountService: AccountService, + @Inject(WINDOW) private win: Window, + protected toastService: ToastService, + private dialogRef: DialogRef, + ) { + /** + * Set the cipher ID from the parameters. + */ + this.cipherId = params.cipherId; + } + + async ngOnInit() { + await this.init(); + } + + /** + * Copies a password to the clipboard. + * @param password The password to copy. + */ + copy(password: string) { + const copyOptions = this.win != null ? { window: this.win } : undefined; + this.platformUtilsService.copyToClipboard(password, copyOptions); + this.toastService.showToast({ + variant: "info", + title: "", + message: this.i18nService.t("valueCopied", this.i18nService.t("password")), + }); + } + + /** + * Initializes the password history dialog component. + */ + protected async init() { + const cipher = await this.cipherService.get(this.cipherId); + const activeAccount = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)), + ); + + if (!activeAccount || !activeAccount.id) { + throw new Error("Active account is not available."); + } + + const activeUserId = activeAccount.id as UserId; + const decCipher = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; + } + + /** + * Closes the password history dialog. + */ + close() { + this.dialogRef.close(); + } +} + +/** + * Strongly typed wrapper around the dialog service to open the password history dialog. + */ +export function openPasswordHistoryDialog( + dialogService: DialogService, + config: DialogConfig, +) { + return dialogService.open(PasswordHistoryComponent, config); +} diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index 67a0223c738..ead52d805a8 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -8,6 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -22,6 +23,7 @@ import { PremiumUpgradePromptService } from "../../../../../../libs/common/src/v import { CipherViewComponent } from "../../../../../../libs/vault/src/cipher-view/cipher-view.component"; import { SharedModule } from "../../shared/shared.module"; import { WebVaultPremiumUpgradePromptService } from "../services/web-premium-upgrade-prompt.service"; +import { WebViewPasswordHistoryService } from "../services/web-view-password-history.service"; export interface ViewCipherDialogParams { cipher: CipherView; @@ -57,6 +59,7 @@ export interface ViewCipherDialogCloseResult { standalone: true, imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule], providers: [ + { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, ], }) diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts new file mode 100644 index 00000000000..2c5fb82c53c --- /dev/null +++ b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts @@ -0,0 +1,45 @@ +import { Overlay } from "@angular/cdk/overlay"; +import { TestBed } from "@angular/core/testing"; + +import { CipherId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; + +import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; + +import { WebViewPasswordHistoryService } from "./web-view-password-history.service"; + +jest.mock("../individual-vault/password-history.component", () => ({ + openPasswordHistoryDialog: jest.fn(), +})); + +describe("WebViewPasswordHistoryService", () => { + let service: WebViewPasswordHistoryService; + let dialogService: DialogService; + + beforeEach(async () => { + const mockDialogService = { + open: jest.fn(), + }; + + await TestBed.configureTestingModule({ + providers: [ + WebViewPasswordHistoryService, + { provide: DialogService, useValue: mockDialogService }, + Overlay, + ], + }).compileComponents(); + + service = TestBed.inject(WebViewPasswordHistoryService); + dialogService = TestBed.inject(DialogService); + }); + + describe("viewPasswordHistory", () => { + it("calls openPasswordHistoryDialog with the correct parameters", async () => { + const mockCipherId = "cipher-id" as CipherId; + await service.viewPasswordHistory(mockCipherId); + expect(openPasswordHistoryDialog).toHaveBeenCalledWith(dialogService, { + data: { cipherId: mockCipherId }, + }); + }); + }); +}); diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.ts b/apps/web/src/app/vault/services/web-view-password-history.service.ts new file mode 100644 index 00000000000..cbdc3928e60 --- /dev/null +++ b/apps/web/src/app/vault/services/web-view-password-history.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; + +import { CipherId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; + +import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service"; +import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; + +/** + * This service is used to display the password history dialog in the web vault. + */ +@Injectable() +export class WebViewPasswordHistoryService implements ViewPasswordHistoryService { + constructor(private dialogService: DialogService) {} + + /** + * Opens the password history dialog for the given cipher ID. + * @param cipherId The ID of the cipher to view the password history for. + */ + async viewPasswordHistory(cipherId: CipherId) { + openPasswordHistoryDialog(this.dialogService, { data: { cipherId } }); + } +} diff --git a/libs/common/src/vault/abstractions/view-password-history.service.ts b/libs/common/src/vault/abstractions/view-password-history.service.ts new file mode 100644 index 00000000000..d9b1306eacb --- /dev/null +++ b/libs/common/src/vault/abstractions/view-password-history.service.ts @@ -0,0 +1,8 @@ +import { CipherId } from "../../types/guid"; + +/** + * The ViewPasswordHistoryService is responsible for displaying the password history for a cipher. + */ +export abstract class ViewPasswordHistoryService { + abstract viewPasswordHistory(cipherId?: CipherId): Promise; +} diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index a03639dee61..d48666ad83a 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -27,10 +27,8 @@

{{ "passwordHistory" | i18n }} diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts index 55c8b90da15..4a37c9a491b 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts @@ -3,6 +3,8 @@ import { Component, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CipherId } from "@bitwarden/common/types/guid"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -31,7 +33,16 @@ import { export class ItemHistoryV2Component { @Input() cipher: CipherView; + constructor(private viewPasswordHistoryService: ViewPasswordHistoryService) {} + get isLogin() { return this.cipher.type === CipherType.Login; } + + /** + * View the password history for the cipher. + */ + async viewPasswordHistory() { + await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher?.id as CipherId); + } }