From ecd6863abef92453fd3a2885ddabe15cbdfe1c92 Mon Sep 17 00:00:00 2001 From: dgoodman-bw <109169446+dgoodman-bw@users.noreply.github.com> Date: Tue, 9 Aug 2022 09:23:57 -0700 Subject: [PATCH 1/7] PS-725 - add empty state image to Vault and Send pages in web and to the Vault page in the bowser extension (#3263) --- .../src/popup/vault/ciphers.component.html | 1 + apps/web/src/app/send/send.component.html | 1 + apps/web/src/app/vault/ciphers.component.html | 1 + apps/web/src/images/search-web-dark.svg | 34 +++++++++++++++++++ apps/web/src/images/search-web-light.svg | 34 +++++++++++++++++++ apps/web/src/scss/base.scss | 15 ++++++++ apps/web/src/scss/variables.scss | 2 ++ 7 files changed, 88 insertions(+) create mode 100644 apps/web/src/images/search-web-dark.svg create mode 100644 apps/web/src/images/search-web-light.svg diff --git a/apps/browser/src/popup/vault/ciphers.component.html b/apps/browser/src/popup/vault/ciphers.component.html index 340e7db8dd4..d4a535885e7 100644 --- a/apps/browser/src/popup/vault/ciphers.component.html +++ b/apps/browser/src/popup/vault/ciphers.component.html @@ -84,6 +84,7 @@
+

{{ "noItemsInList" | i18n }}

diff --git a/apps/web/src/app/organizations/manage/reset-password.component.html b/apps/web/src/app/organizations/manage/reset-password.component.html index ea6e75fee8e..675a17e4b08 100644 --- a/apps/web/src/app/organizations/manage/reset-password.component.html +++ b/apps/web/src/app/organizations/manage/reset-password.component.html @@ -77,7 +77,12 @@ - + diff --git a/apps/web/src/app/organizations/manage/reset-password.component.ts b/apps/web/src/app/organizations/manage/reset-password.component.ts index 84c9a5c143e..93fb6ff75f1 100644 --- a/apps/web/src/app/organizations/manage/reset-password.component.ts +++ b/apps/web/src/app/organizations/manage/reset-password.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core"; +import zxcvbn from "zxcvbn"; import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -28,7 +29,7 @@ export class ResetPasswordComponent implements OnInit { enforcedPolicyOptions: MasterPasswordPolicyOptions; newPassword: string = null; showPassword = false; - masterPasswordScore: number; + passwordStrengthResult: zxcvbn.ZXCVBNResult; formPromise: Promise; constructor( @@ -97,7 +98,7 @@ export class ResetPasswordComponent implements OnInit { if ( this.enforcedPolicyOptions != null && !this.policyService.evaluateMasterPassword( - this.masterPasswordScore, + this.passwordStrengthResult.score, this.newPassword, this.enforcedPolicyOptions ) @@ -110,7 +111,7 @@ export class ResetPasswordComponent implements OnInit { return; } - if (this.masterPasswordScore < 3) { + if (this.passwordStrengthResult.score < 3) { const result = await this.platformUtilsService.showDialog( this.i18nService.t("weakMasterPasswordDesc"), this.i18nService.t("weakMasterPassword"), @@ -184,4 +185,8 @@ export class ResetPasswordComponent implements OnInit { this.logService.error(e); } } + + getStrengthResult(result: zxcvbn.ZXCVBNResult) { + this.passwordStrengthResult = result; + } } From c4f9c2cca6dab48acecf0afe72377920b40c5e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Tue, 9 Aug 2022 19:03:02 +0100 Subject: [PATCH 3/7] [SG-416] Updates to Bitwarden Authenticator (#3045) * [SG-416] Changed UI for TOTP codes on free plan and added link to get Premium. On browser, changed back action of premium.component in order to reuse on cipher details. * [SSG-416] PR Fix * [SSG-416] fix formatting * [SSG-416] Updated desktop free plan OTP UI * [SSG-416] noticed a bad div tag making file changes erratic * [SG-416] fixed label * [SSG-416] Fix formatting * [SSG-416] Changed bootstrap classes to tailwind * [SSG-416] Added premium and upgrade badge back. Muted placeholder totp code colors and button. * [SSG-416] Change learn more to upgrade label on get premium modal. Fixed navigation for premium. * [SSG-416] Removed unused image file. * [SG-416] Changed browser "Premium subscription required" text to be all hyperlink. * [SG-416] Fixed missing resource on browser * [SG-416] Code format with lint --- apps/browser/src/_locales/en/messages.json | 3 + .../src/popup/settings/premium.component.html | 2 +- .../src/popup/settings/premium.component.ts | 7 +- .../src/popup/vault/view.component.html | 16 ++++- .../desktop/src/app/vault/view.component.html | 15 +++- apps/desktop/src/app/vault/view.component.ts | 6 ++ apps/desktop/src/locales/en/messages.json | 3 + apps/web/src/app/app.component.ts | 2 +- .../web/src/app/vault/add-edit.component.html | 64 +++++++++++++----- apps/web/src/app/vault/add-edit.component.ts | 11 +++ apps/web/src/images/totp-countdown.png | Bin 950 -> 0 bytes apps/web/src/locales/en/messages.json | 3 + apps/web/src/scss/modals.scss | 19 ++++++ 13 files changed, 130 insertions(+), 21 deletions(-) delete mode 100644 apps/web/src/images/totp-countdown.png diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b38a984dd9a..ac25f4cf293 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1967,6 +1967,9 @@ }, "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" }, "organizationIsDisabled": { "message": "Organization is disabled." diff --git a/apps/browser/src/popup/settings/premium.component.html b/apps/browser/src/popup/settings/premium.component.html index fad784da6b4..01ac9258133 100644 --- a/apps/browser/src/popup/settings/premium.component.html +++ b/apps/browser/src/popup/settings/premium.component.html @@ -1,6 +1,6 @@
- diff --git a/apps/browser/src/popup/settings/premium.component.ts b/apps/browser/src/popup/settings/premium.component.ts index b8213919245..d44fdb09a70 100644 --- a/apps/browser/src/popup/settings/premium.component.ts +++ b/apps/browser/src/popup/settings/premium.component.ts @@ -1,4 +1,4 @@ -import { CurrencyPipe } from "@angular/common"; +import { CurrencyPipe, Location } from "@angular/common"; import { Component } from "@angular/core"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/components/premium.component"; @@ -21,6 +21,7 @@ export class PremiumComponent extends BasePremiumComponent { apiService: ApiService, stateService: StateService, logService: LogService, + private location: Location, private currencyPipe: CurrencyPipe ) { super(i18nService, platformUtilsService, apiService, logService, stateService); @@ -32,4 +33,8 @@ export class PremiumComponent extends BasePremiumComponent { this.priceString = this.priceString.replace("%price%", thePrice); } } + + goBack() { + this.location.back(); + } } diff --git a/apps/browser/src/popup/vault/view.component.html b/apps/browser/src/popup/vault/view.component.html index feb17606669..e168879dd06 100644 --- a/apps/browser/src/popup/vault/view.component.html +++ b/apps/browser/src/popup/vault/view.component.html @@ -139,7 +139,7 @@
+ +
+
+ {{ "verificationCodeTotp" | i18n }} + + + {{ "premiumSubcriptionRequired" | i18n }} + + +
+
diff --git a/apps/desktop/src/app/vault/view.component.html b/apps/desktop/src/app/vault/view.component.html index a033cdd8272..5038e4b984b 100644 --- a/apps/desktop/src/app/vault/view.component.html +++ b/apps/desktop/src/app/vault/view.component.html @@ -100,7 +100,7 @@
+
+
+ {{ "verificationCodeTotp" | i18n }} + + {{ "premiumSubcriptionRequired" | i18n }} + + +
+
diff --git a/apps/desktop/src/app/vault/view.component.ts b/apps/desktop/src/app/vault/view.component.ts index eb58df0d1e3..a3125c02e7d 100644 --- a/apps/desktop/src/app/vault/view.component.ts +++ b/apps/desktop/src/app/vault/view.component.ts @@ -115,4 +115,10 @@ export class ViewComponent extends BaseViewComponent implements OnChanges { }); } } + + showGetPremium() { + if (!this.canAccessPremium) { + this.messagingService.send("premiumRequired"); + } + } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 170ddb72145..e399281c061 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 381fd551124..ef80dc5e736 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -147,7 +147,7 @@ export class AppComponent implements OnDestroy, OnInit { const premiumConfirmed = await this.platformUtilsService.showDialog( this.i18nService.t("premiumRequiredDesc"), this.i18nService.t("premiumRequired"), - this.i18nService.t("learnMore"), + this.i18nService.t("upgrade"), this.i18nService.t("cancel") ); if (premiumConfirmed) { diff --git a/apps/web/src/app/vault/add-edit.component.html b/apps/web/src/app/vault/add-edit.component.html index 54b707c9f6e..d7aa44f8772 100644 --- a/apps/web/src/app/vault/add-edit.component.html +++ b/apps/web/src/app/vault/add-edit.component.html @@ -166,8 +166,8 @@
-
-
+
+
-
-
- +
+ + 15 + + + + + + + + + >--- --- + +
+
-
- +
+ {{ totpSec }} @@ -225,16 +255,18 @@ - {{ - totpCodeFormatted - }} + {{ totpCodeFormatted }}
diff --git a/apps/web/src/app/vault/add-edit.component.ts b/apps/web/src/app/vault/add-edit.component.ts index 4b89162803e..0b7642eb97e 100644 --- a/apps/web/src/app/vault/add-edit.component.ts +++ b/apps/web/src/app/vault/add-edit.component.ts @@ -152,6 +152,17 @@ export class AddEditComponent extends BaseAddEditComponent { }); } + showGetPremium() { + if (this.canAccessPremium) { + return; + } + if (this.cipher.organizationUseTotp) { + this.upgradeOrganization(); + } else { + this.premiumRequired(); + } + } + viewHistory() { this.viewingPasswordHistory = !this.viewingPasswordHistory; } diff --git a/apps/web/src/images/totp-countdown.png b/apps/web/src/images/totp-countdown.png deleted file mode 100644 index ef07c49b98b91be2047b4f52dd308c520253d477..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 950 zcmeAS@N?(olHy`uVBq!ia0vp^Z9pu?!3-qtO`6sVq;>`Pgt-3y|DPdkCXiqNVqY*L zVLg=lzwiGP2Hq(Q43WziLRT>)EoX>W{(mn6!`}Z4QA-&@7c<1JUTJH*$fN| z|1-?~4^jz|TB^h_L5ZRGP9+0FC6KTH(hO2fKn??g1w+JQkebLv3=vB}>LV93go7Bz z3m6!RkIeUAU?@1w5Vf5lY6VDH_)-Rs`2`FGKuHe<51=Ga8pLE^U|d!(0Z7@E1o;Is zuySy4@d*eC2?+~}h)PMx%gd{1YU+W2v9YPCH3Zn&IXQcHc=`sR0oHX^4h#%Txt=bL zAr-fdPQP0<%R$7IKkMQ)MJ-V&&nVA&k?eM_&08~--v5s(z0>+LVaeIv4S5bm4|Tp- zHgr9-ic)jf)U$4>@tm1XowF=2aqeTf)@UxkaDy>RM^=DQ+uwviL&)>>9j1LEJKmdJ zxZ`!&C1BmvMX|Rle=z9sG@CMPv6Am=%9p<)cWUA{jtxSy8xQd{ZfS42enCqkv)Axi z(KOz&tuJ5Xezxg%RrBBZ%s`a0|Mv5V5@J6)Yv-?iZX;DR@!?hXhCff_7a9Hfu*Kx+ z)EhbqtD;h(6!o~|rng6_rgr{1Wg;9Yv+Pe!_KQzx+S(OP-G{GF^qArK{_@NE*acRm zlkW$ISs2#zM0V+kR#B>zbnc79mT#-ONj_VK-G&yyP$-~6m`=5dw8QI8F+QP-{|2vt3Q z*3;&f@@Z*)_J==}VQ;3zZjU(=5BD;&)_qj)BGew&wO*zEk9u9X`IO=Gf}9 zDUMscKA-yWEasPf??vgi2L+e9rJC)z)nU|duk8MFyVM)Z);v*1Vs5QDI9I;*-Uc`2 zJKw8sTfR>zK0Iq-ir}e&xU*MhS3dY zGtn;0ZF;6jwu#-DSk@I)mw$?+N~}3~ddr~;o>Tj8iD$AzoU)wrUc6@OO{toTw||J& zUEI9qW*=j?j*YS8^xl@0H{zKx7YcZuzQb6zS9! .align-items-center { From cfc8858ef98b4dd957910b206da1f2cb8c0e45ab Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Tue, 9 Aug 2022 21:11:51 +0200 Subject: [PATCH 4/7] Fix active account and searchBar observables/subscriptions (#3268) * Change subscription to rely on observables and not on BehaviourSubject * Ensure OnDestroy is added to AppComponent * Fix check for no active accounts to redirect to the login page instead of lock * Change subscription handling on SearchBarService * Fix naming convention: Observables should have a $ suffix * Remove obsolete linter hint * Fix activeAccountUnlocked getting exposed as Observable but is instantiated as BehaviourSubject --- apps/browser/src/popup/app.component.ts | 4 +-- .../src/app/accounts/lock.component.ts | 5 ++-- apps/desktop/src/app/app.component.ts | 5 ++-- .../app/layout/search/search-bar.service.ts | 11 ++++---- .../src/app/layout/search/search.component.ts | 9 ++++--- apps/desktop/src/app/send/send.component.ts | 2 +- .../src/app/vault/ciphers.component.ts | 2 +- libs/angular/src/components/lock.component.ts | 13 ++++++--- .../spec/services/folder.service.spec.ts | 2 +- libs/common/src/abstractions/state.service.ts | 5 ++-- .../src/services/environment.service.ts | 2 +- .../src/services/folder/folder.service.ts | 2 +- libs/common/src/services/state.service.ts | 27 ++++++++++--------- 13 files changed, 52 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index bc383715ea7..7524aec22d9 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -56,7 +56,7 @@ export class AppComponent implements OnInit, OnDestroy { // Clear them aggressively to make sure this doesn't occur await this.clearComponentStates(); - this.stateService.activeAccount.pipe(takeUntil(this.destroy$)).subscribe((userId) => { + this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => { this.activeUserId = userId; }); @@ -84,7 +84,7 @@ export class AppComponent implements OnInit, OnDestroy { }); } - if (this.stateService.activeAccount.getValue() == null) { + if (this.activeUserId === null) { this.router.navigate(["home"]); } }); diff --git a/apps/desktop/src/app/accounts/lock.component.ts b/apps/desktop/src/app/accounts/lock.component.ts index c7224113f98..e7e5c33715d 100644 --- a/apps/desktop/src/app/accounts/lock.component.ts +++ b/apps/desktop/src/app/accounts/lock.component.ts @@ -1,4 +1,4 @@ -import { Component, NgZone, OnDestroy } from "@angular/core"; +import { Component, NgZone } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { ipcRenderer } from "electron"; @@ -22,7 +22,7 @@ const BroadcasterSubscriptionId = "LockComponent"; selector: "app-lock", templateUrl: "lock.component.html", }) -export class LockComponent extends BaseLockComponent implements OnDestroy { +export class LockComponent extends BaseLockComponent { private deferFocus: boolean = null; authenicatedUrl = "vault"; unAuthenicatedUrl = "update-temp-password"; @@ -103,6 +103,7 @@ export class LockComponent extends BaseLockComponent implements OnDestroy { } ngOnDestroy() { + super.ngOnDestroy(); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index dc089e20e45..f2400b31fb9 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -1,6 +1,7 @@ import { Component, NgZone, + OnDestroy, OnInit, SecurityContext, Type, @@ -77,7 +78,7 @@ const systemTimeoutOptions = {
`, }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, OnDestroy { @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @ViewChild("premium", { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef; @ViewChild("passwordHistory", { read: ViewContainerRef, static: true }) @@ -129,7 +130,7 @@ export class AppComponent implements OnInit { ) {} ngOnInit() { - this.stateService.activeAccount.pipe(takeUntil(this.destroy$)).subscribe((userId) => { + this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => { this.activeUserId = userId; }); diff --git a/apps/desktop/src/app/layout/search/search-bar.service.ts b/apps/desktop/src/app/layout/search/search-bar.service.ts index 340ee3a6986..24f3d0513f7 100644 --- a/apps/desktop/src/app/layout/search/search-bar.service.ts +++ b/apps/desktop/src/app/layout/search/search-bar.service.ts @@ -8,15 +8,16 @@ export type SearchBarState = { @Injectable() export class SearchBarService { - searchText = new BehaviorSubject(null); + private searchTextSubject = new BehaviorSubject(null); + searchText$ = this.searchTextSubject.asObservable(); private _state = { enabled: false, placeholderText: "", }; - // tslint:disable-next-line:member-ordering - state = new BehaviorSubject(this._state); + private stateSubject = new BehaviorSubject(this._state); + state$ = this.stateSubject.asObservable(); setEnabled(enabled: boolean) { this._state.enabled = enabled; @@ -29,10 +30,10 @@ export class SearchBarService { } setSearchText(value: string) { - this.searchText.next(value); + this.searchTextSubject.next(value); } private updateState() { - this.state.next(this._state); + this.stateSubject.next(this._state); } } diff --git a/apps/desktop/src/app/layout/search/search.component.ts b/apps/desktop/src/app/layout/search/search.component.ts index e836ebd91a3..6f5f7a69fda 100644 --- a/apps/desktop/src/app/layout/search/search.component.ts +++ b/apps/desktop/src/app/layout/search/search.component.ts @@ -1,5 +1,6 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { UntypedFormControl } from "@angular/forms"; +import { Subscription } from "rxjs"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -13,8 +14,10 @@ export class SearchComponent implements OnInit, OnDestroy { state: SearchBarState; searchText: UntypedFormControl = new UntypedFormControl(null); + private activeAccountSubscription: Subscription; + constructor(private searchBarService: SearchBarService, private stateService: StateService) { - this.searchBarService.state.subscribe((state) => { + this.searchBarService.state$.subscribe((state) => { this.state = state; }); @@ -24,13 +27,13 @@ export class SearchComponent implements OnInit, OnDestroy { } ngOnInit() { - this.stateService.activeAccount.subscribe((value) => { + this.activeAccountSubscription = this.stateService.activeAccount$.subscribe((value) => { this.searchBarService.setSearchText(""); this.searchText.patchValue(""); }); } ngOnDestroy() { - this.stateService.activeAccount.unsubscribe(); + this.activeAccountSubscription.unsubscribe(); } } diff --git a/apps/desktop/src/app/send/send.component.ts b/apps/desktop/src/app/send/send.component.ts index 5f9b3cde7cb..d8b68d842ca 100644 --- a/apps/desktop/src/app/send/send.component.ts +++ b/apps/desktop/src/app/send/send.component.ts @@ -56,7 +56,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro policyService, logService ); - this.searchBarService.searchText.subscribe((searchText) => { + this.searchBarService.searchText$.subscribe((searchText) => { this.searchText = searchText; this.searchTextChanged(); }); diff --git a/apps/desktop/src/app/vault/ciphers.component.ts b/apps/desktop/src/app/vault/ciphers.component.ts index 7505206f501..30fcbd270b5 100644 --- a/apps/desktop/src/app/vault/ciphers.component.ts +++ b/apps/desktop/src/app/vault/ciphers.component.ts @@ -14,7 +14,7 @@ export class CiphersComponent extends BaseCiphersComponent { constructor(searchService: SearchService, searchBarService: SearchBarService) { super(searchService); - searchBarService.searchText.subscribe((searchText) => { + searchBarService.searchText$.subscribe((searchText) => { this.searchText = searchText; this.search(200); }); diff --git a/libs/angular/src/components/lock.component.ts b/libs/angular/src/components/lock.component.ts index b16717ea7f7..83d5744dfa7 100644 --- a/libs/angular/src/components/lock.component.ts +++ b/libs/angular/src/components/lock.component.ts @@ -1,5 +1,6 @@ -import { Directive, NgZone, OnInit } from "@angular/core"; +import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; import { Router } from "@angular/router"; +import { Subscription } from "rxjs"; import { take } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -20,7 +21,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCry import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; @Directive() -export class LockComponent implements OnInit { +export class LockComponent implements OnInit, OnDestroy { masterPassword = ""; pin = ""; showPassword = false; @@ -39,6 +40,8 @@ export class LockComponent implements OnInit { private invalidPinAttempts = 0; private pinSet: [boolean, boolean]; + private activeAccountSubscription: Subscription; + constructor( protected router: Router, protected i18nService: I18nService, @@ -57,11 +60,15 @@ export class LockComponent implements OnInit { async ngOnInit() { // Load the first and observe updates await this.load(); - this.stateService.activeAccount.subscribe(async () => { + this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => { await this.load(); }); } + ngOnDestroy() { + this.activeAccountSubscription.unsubscribe(); + } + async submit() { if (this.pinLock && (this.pin == null || this.pin === "")) { this.platformUtilsService.showToast( diff --git a/libs/common/spec/services/folder.service.spec.ts b/libs/common/spec/services/folder.service.spec.ts index 697ae491f5e..660d4a953c8 100644 --- a/libs/common/spec/services/folder.service.spec.ts +++ b/libs/common/spec/services/folder.service.spec.ts @@ -32,7 +32,7 @@ describe("Folder Service", () => { stateService.getEncryptedFolders().resolves({ "1": folderData("1", "test"), }); - stateService.activeAccount.returns(activeAccount); + stateService.activeAccount$.returns(activeAccount); stateService.activeAccountUnlocked.returns(activeAccountUnlocked); (window as any).bitwardenContainerService = new ContainerService(cryptoService); diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index ae31a222cca..b99d8aef86e 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -26,9 +26,8 @@ import { SendView } from "../models/view/sendView"; export abstract class StateService { accounts: BehaviorSubject<{ [userId: string]: T }>; - activeAccount: BehaviorSubject; - - activeAccountUnlocked: Observable; + activeAccount$: Observable; + activeAccountUnlocked$: Observable; addAccount: (account: T) => Promise; setActiveUser: (userId: string) => Promise; diff --git a/libs/common/src/services/environment.service.ts b/libs/common/src/services/environment.service.ts index 271fb87c45d..1d62d471ad5 100644 --- a/libs/common/src/services/environment.service.ts +++ b/libs/common/src/services/environment.service.ts @@ -22,7 +22,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { private scimUrl: string = null; constructor(private stateService: StateService) { - this.stateService.activeAccount.subscribe(async () => { + this.stateService.activeAccount$.subscribe(async () => { await this.setUrlsFromStorage(); }); } diff --git a/libs/common/src/services/folder/folder.service.ts b/libs/common/src/services/folder/folder.service.ts index 222ea44fb35..4b3530cb817 100644 --- a/libs/common/src/services/folder/folder.service.ts +++ b/libs/common/src/services/folder/folder.service.ts @@ -25,7 +25,7 @@ export class FolderService implements InternalFolderServiceAbstraction { private cipherService: CipherService, private stateService: StateService ) { - this.stateService.activeAccountUnlocked.subscribe(async (unlocked) => { + this.stateService.activeAccountUnlocked$.subscribe(async (unlocked) => { if ((Utils.global as any).bitwardenContainerService == null) { return; } diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index bc54c76ffab..39f34886496 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -55,8 +55,11 @@ export class StateService< > implements StateServiceAbstraction { accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); - activeAccount = new BehaviorSubject(null); - activeAccountUnlocked = new BehaviorSubject(false); + private activeAccountSubject = new BehaviorSubject(null); + activeAccount$ = this.activeAccountSubject.asObservable(); + + private activeAccountUnlockedSubject = new BehaviorSubject(false); + activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable(); private hasBeenInited = false; private isRecoveredSession = false; @@ -73,17 +76,17 @@ export class StateService< protected useAccountCache: boolean = true ) { // If the account gets changed, verify the new account is unlocked - this.activeAccount.subscribe(async (userId) => { - if (userId == null && this.activeAccountUnlocked.getValue() == false) { + this.activeAccountSubject.subscribe(async (userId) => { + if (userId == null && this.activeAccountUnlockedSubject.getValue() == false) { return; } else if (userId == null) { - this.activeAccountUnlocked.next(false); + this.activeAccountUnlockedSubject.next(false); } // FIXME: This should be refactored into AuthService or a similar service, // as checking for the existance of the crypto key is a low level // implementation detail. - this.activeAccountUnlocked.next((await this.getCryptoMasterKey()) != null); + this.activeAccountUnlockedSubject.next((await this.getCryptoMasterKey()) != null); }); } @@ -125,7 +128,7 @@ export class StateService< state.activeUserId = storedActiveUser; } await this.pushAccounts(); - this.activeAccount.next(state.activeUserId); + this.activeAccountSubject.next(state.activeUserId); return state; }); @@ -154,7 +157,7 @@ export class StateService< await this.scaffoldNewAccountStorage(account); await this.setLastActive(new Date().getTime(), { userId: account.profile.userId }); await this.setActiveUser(account.profile.userId); - this.activeAccount.next(account.profile.userId); + this.activeAccountSubject.next(account.profile.userId); } async setActiveUser(userId: string): Promise { @@ -162,7 +165,7 @@ export class StateService< await this.updateState(async (state) => { state.activeUserId = userId; await this.storageService.save(keys.activeUserId, userId); - this.activeAccount.next(state.activeUserId); + this.activeAccountSubject.next(state.activeUserId); return state; }); @@ -497,12 +500,12 @@ export class StateService< this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); - if (options.userId == this.activeAccount.getValue()) { + if (options.userId == this.activeAccountSubject.getValue()) { const nextValue = value != null; // Avoid emitting if we are already unlocked - if (this.activeAccountUnlocked.getValue() != nextValue) { - this.activeAccountUnlocked.next(nextValue); + if (this.activeAccountUnlockedSubject.getValue() != nextValue) { + this.activeAccountUnlockedSubject.next(nextValue); } } } From 43d428b3dfa21d29e1aec0a722e30a8f714232c4 Mon Sep 17 00:00:00 2001 From: Justin Baur Date: Tue, 9 Aug 2022 21:30:26 -0400 Subject: [PATCH 5/7] [PS-816] Add Autofill Shortcut to MV3 Extension (#3131) * Work on background service worker. * Work on shortcuts * Work on supporting service worker * Put new background behind version check * Fix build * Use new storage service * create commands from crypto service (#2995) * Work on service worker autofill * Got basic autofill working * Final touches * Work on tests * Revert some changes * Add modifications * Remove unused ciphers for now * Cleanup * Address PR feedback * Update lock file * Update noop service * Add chrome type * Handle "/" in branch names Updates web workflow to handle the `/` in branch names when it's a PR. * Remove any Co-authored-by: Jake Fink Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> --- .storybook/tsconfig.json | 2 +- apps/browser/src/background.ts | 15 +- .../src/background/contextMenus.background.ts | 4 + .../src/background/webRequest.background.ts | 5 +- .../src/commands/autoFillActiveTabCommand.ts | 44 ++++ apps/browser/src/content/autofill.js | 6 + .../src/listeners/onCommandListener.ts | 140 ++++++++++ apps/browser/src/manifest.v3.json | 3 +- apps/browser/src/models/autofillField.ts | 1 + .../abstractChromeStorageApi.service.ts | 2 +- .../services/abstractions/autofill.service.ts | 42 ++- apps/browser/src/services/autofill.service.ts | 244 +++++++++++------- .../services/browserLocalStorage.service.ts | 2 +- .../services/browserMemoryStorage.service.ts | 2 +- apps/browser/webpack.config.js | 19 +- .../app/settings/verify-email.component.ts | 2 +- .../spec/services/consoleLog.service.spec.ts | 21 -- libs/common/src/abstractions/log.service.ts | 2 - libs/common/src/misc/utils.ts | 34 ++- libs/common/src/models/domain/attachment.ts | 2 +- libs/common/src/models/domain/encString.ts | 2 +- libs/common/src/services/cipher.service.ts | 2 +- .../common/src/services/consoleLog.service.ts | 15 -- libs/common/src/services/noopEvent.service.ts | 24 ++ libs/common/src/services/search.service.ts | 14 +- libs/common/src/services/state.service.ts | 2 +- .../src/services/webCryptoFunction.service.ts | 2 +- package-lock.json | 15 +- package.json | 3 +- 29 files changed, 488 insertions(+), 183 deletions(-) create mode 100644 apps/browser/src/commands/autoFillActiveTabCommand.ts create mode 100644 apps/browser/src/listeners/onCommandListener.ts create mode 100644 libs/common/src/services/noopEvent.service.ts diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json index e411a7a39cd..397be6b000c 100644 --- a/.storybook/tsconfig.json +++ b/.storybook/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig", "compilerOptions": { - "types": ["node", "jest"], + "types": ["node", "jest", "chrome"], "allowSyntheticDefaultImports": true }, "exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"], diff --git a/apps/browser/src/background.ts b/apps/browser/src/background.ts index 939a481bd52..ea24defb8cb 100644 --- a/apps/browser/src/background.ts +++ b/apps/browser/src/background.ts @@ -1,6 +1,13 @@ import MainBackground from "./background/main.background"; +import { onCommandListener } from "./listeners/onCommandListener"; -const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); -bitwardenMain.bootstrap().then(() => { - // Finished bootstrapping -}); +const manifest = chrome.runtime.getManifest(); + +if (manifest.manifest_version === 3) { + chrome.commands.onCommand.addListener(onCommandListener); +} else { + const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); + bitwardenMain.bootstrap().then(() => { + // Finished bootstrapping + }); +} diff --git a/apps/browser/src/background/contextMenus.background.ts b/apps/browser/src/background/contextMenus.background.ts index b14ef79cac9..22d9d56bbff 100644 --- a/apps/browser/src/background/contextMenus.background.ts +++ b/apps/browser/src/background/contextMenus.background.ts @@ -81,6 +81,10 @@ export default class ContextMenusBackground { } private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { + if (typeof info.menuItemId !== "string") { + return; + } + const id = info.menuItemId.split("_")[1]; if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { diff --git a/apps/browser/src/background/webRequest.background.ts b/apps/browser/src/background/webRequest.background.ts index 0afbfc41124..26dd86a9081 100644 --- a/apps/browser/src/background/webRequest.background.ts +++ b/apps/browser/src/background/webRequest.background.ts @@ -14,7 +14,10 @@ export default class WebRequestBackground { private cipherService: CipherService, private authService: AuthService ) { - this.webRequest = (window as any).chrome.webRequest; + const manifest = chrome.runtime.getManifest(); + if (manifest.manifest_version === 2) { + this.webRequest = (window as any).chrome.webRequest; + } this.isFirefox = platformUtilsService.isFirefox(); } diff --git a/apps/browser/src/commands/autoFillActiveTabCommand.ts b/apps/browser/src/commands/autoFillActiveTabCommand.ts new file mode 100644 index 00000000000..74cdad55d73 --- /dev/null +++ b/apps/browser/src/commands/autoFillActiveTabCommand.ts @@ -0,0 +1,44 @@ +import AutofillPageDetails from "../models/autofillPageDetails"; +import { AutofillService } from "../services/abstractions/autofill.service"; + +export class AutoFillActiveTabCommand { + constructor(private autofillService: AutofillService) {} + + async doAutoFillActiveTabCommand(tab: chrome.tabs.Tab) { + if (!tab.id) { + throw new Error("Tab does not have an id, cannot complete autofill."); + } + + const details = await this.collectPageDetails(tab.id); + await this.autofillService.doAutoFillOnTab( + [ + { + frameId: 0, + tab: tab, + details: details, + }, + ], + tab, + true + ); + } + + private async collectPageDetails(tabId: number): Promise { + return new Promise((resolve, reject) => { + chrome.tabs.sendMessage( + tabId, + { + command: "collectPageDetailsImmediately", + }, + (response: AutofillPageDetails) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + + resolve(response); + } + ); + }); + } +} diff --git a/apps/browser/src/content/autofill.js b/apps/browser/src/content/autofill.js index 7b285d4cf4f..d4c05f7e8ce 100644 --- a/apps/browser/src/content/autofill.js +++ b/apps/browser/src/content/autofill.js @@ -39,6 +39,7 @@ 6. Rename com.agilebits.* stuff to com.bitwarden.* 7. Remove "some useful globals" on window 8. Add ability to autofill span[data-bwautofill] elements + 9. Add new handler, for new command that responds with page details in response callback */ function collect(document, undefined) { @@ -1037,6 +1038,11 @@ fill(document, msg.fillScript); sendResponse(); return true; + } else if (msg.command === 'collectPageDetailsImmediately') { + var pageDetails = collect(document); + var pageDetailsObj = JSON.parse(pageDetails); + sendResponse(pageDetailsObj); + return true; } }); })(); diff --git a/apps/browser/src/listeners/onCommandListener.ts b/apps/browser/src/listeners/onCommandListener.ts new file mode 100644 index 00000000000..0e64f8b77e8 --- /dev/null +++ b/apps/browser/src/listeners/onCommandListener.ts @@ -0,0 +1,140 @@ +import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; +import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { GlobalState } from "@bitwarden/common/models/domain/globalState"; +import { AuthService } from "@bitwarden/common/services/auth.service"; +import { CipherService } from "@bitwarden/common/services/cipher.service"; +import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; +import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import NoOpEventService from "@bitwarden/common/services/noOpEvent.service"; +import { SearchService } from "@bitwarden/common/services/search.service"; +import { SettingsService } from "@bitwarden/common/services/settings.service"; +import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; +import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; + +import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand"; +import { Account } from "../models/account"; +import { StateService as AbstractStateService } from "../services/abstractions/state.service"; +import AutofillService from "../services/autofill.service"; +import { BrowserCryptoService } from "../services/browserCrypto.service"; +import BrowserLocalStorageService from "../services/browserLocalStorage.service"; +import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; +import I18nService from "../services/i18n.service"; +import { KeyGenerationService } from "../services/keyGeneration.service"; +import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service"; +import { StateService } from "../services/state.service"; + +export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => { + switch (command) { + case "autofill_login": + await doAutoFillLogin(tab); + break; + } +}; + +const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise => { + const logService = new ConsoleLogService(false); + + const cryptoFunctionService = new WebCryptoFunctionService(self); + + const storageService = new BrowserLocalStorageService(); + + const secureStorageService = new BrowserLocalStorageService(); + + const memoryStorageService = new LocalBackedSessionStorageService( + new EncryptService(cryptoFunctionService, logService, false), + new KeyGenerationService(cryptoFunctionService) + ); + + const stateFactory = new StateFactory(GlobalState, Account); + + const stateMigrationService = new StateMigrationService( + storageService, + secureStorageService, + stateFactory + ); + + const stateService: AbstractStateService = new StateService( + storageService, + secureStorageService, + memoryStorageService, // AbstractStorageService + logService, + stateMigrationService, + stateFactory + ); + + await stateService.init(); + + const platformUtils = new BrowserPlatformUtilsService( + null, // MessagingService + stateService, + null, // clipboardWriteCallback + null // biometricCallback + ); + + const cryptoService = new BrowserCryptoService( + cryptoFunctionService, + null, // AbstractEncryptService + platformUtils, + logService, + stateService + ); + + const settingsService = new SettingsService(stateService); + + const i18nService = new I18nService(chrome.i18n.getUILanguage()); + + await i18nService.init(); + + // Don't love this pt.1 + let searchService: SearchService = null; + + const cipherService = new CipherService( + cryptoService, + settingsService, + null, // ApiService + null, // FileUploadService, + i18nService, + () => searchService, // Don't love this pt.2 + logService, + stateService + ); + + // Don't love this pt.3 + searchService = new SearchService(cipherService, logService, i18nService); + + // TODO: Remove this before we encourage anyone to start using this + const eventService = new NoOpEventService(); + + const autofillService = new AutofillService( + cipherService, + stateService, + null, // TotpService + eventService, + logService + ); + + const authService = new AuthService( + cryptoService, // CryptoService + null, // ApiService + null, // TokenService + null, // AppIdService + platformUtils, + null, // MessagingService + logService, + null, // KeyConnectorService + null, // EnvironmentService + stateService, + null, // TwoFactorService + i18nService + ); + + const authStatus = await authService.getAuthStatus(); + if (authStatus < AuthenticationStatus.Unlocked) { + // TODO: Add back in unlock on autofill + logService.info("Currently not unlocked, MV3 does not support unlock on autofill currently."); + return; + } + + const command = new AutoFillActiveTabCommand(autofillService); + await command.doAutoFillActiveTabCommand(tab); +}; diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 0acb6ccea15..5c04b6847be 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -47,7 +47,8 @@ } ], "background": { - "service_worker": "background.js" + "service_worker": "background.js", + "type": "module" }, "action": { "default_icon": { diff --git a/apps/browser/src/models/autofillField.ts b/apps/browser/src/models/autofillField.ts index fa08602a32d..96da54f6770 100644 --- a/apps/browser/src/models/autofillField.ts +++ b/apps/browser/src/models/autofillField.ts @@ -22,4 +22,5 @@ export default class AutofillField { selectInfo: any; maxLength: number; tagName: string; + [key: string]: any; } diff --git a/apps/browser/src/services/abstractChromeStorageApi.service.ts b/apps/browser/src/services/abstractChromeStorageApi.service.ts index e6784570af1..4d83e369727 100644 --- a/apps/browser/src/services/abstractChromeStorageApi.service.ts +++ b/apps/browser/src/services/abstractChromeStorageApi.service.ts @@ -1,7 +1,7 @@ import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; export default abstract class AbstractChromeStorageService implements AbstractStorageService { - protected abstract chromeStorageApi: any; + protected abstract chromeStorageApi: chrome.storage.StorageArea; async get(key: string): Promise { return new Promise((resolve) => { diff --git a/apps/browser/src/services/abstractions/autofill.service.ts b/apps/browser/src/services/abstractions/autofill.service.ts index 68fb1b8dba3..ceab1d589f8 100644 --- a/apps/browser/src/services/abstractions/autofill.service.ts +++ b/apps/browser/src/services/abstractions/autofill.service.ts @@ -1,7 +1,41 @@ +import { CipherView } from "@bitwarden/common/models/view/cipherView"; + +import AutofillField from "../../models/autofillField"; +import AutofillForm from "../../models/autofillForm"; import AutofillPageDetails from "../../models/autofillPageDetails"; -export abstract class AutofillService { - getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[]; - doAutoFill: (options: any) => Promise; - doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise; +export interface PageDetail { + frameId: number; + tab: chrome.tabs.Tab; + details: AutofillPageDetails; +} + +export interface AutoFillOptions { + cipher: CipherView; + pageDetails: PageDetail[]; + doc?: typeof window.document; + tab: chrome.tabs.Tab; + skipUsernameOnlyFill?: boolean; + onlyEmptyFields?: boolean; + onlyVisibleFields?: boolean; + fillNewPassword?: boolean; + skipLastUsed?: boolean; +} + +export interface FormData { + form: AutofillForm; + password: AutofillField; + username: AutofillField; + passwords: AutofillField[]; +} + +export abstract class AutofillService { + getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => FormData[]; + doAutoFill: (options: AutoFillOptions) => Promise; + doAutoFillOnTab: ( + pageDetails: PageDetail[], + tab: chrome.tabs.Tab, + fromCommand: boolean + ) => Promise; + doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise; } diff --git a/apps/browser/src/services/autofill.service.ts b/apps/browser/src/services/autofill.service.ts index 341eb723d19..ff8eca93475 100644 --- a/apps/browser/src/services/autofill.service.ts +++ b/apps/browser/src/services/autofill.service.ts @@ -15,13 +15,26 @@ import AutofillPageDetails from "../models/autofillPageDetails"; import AutofillScript from "../models/autofillScript"; import { StateService } from "../services/abstractions/state.service"; -import { AutofillService as AutofillServiceInterface } from "./abstractions/autofill.service"; +import { + AutoFillOptions, + AutofillService as AutofillServiceInterface, + PageDetail, + FormData, +} from "./abstractions/autofill.service"; import { AutoFillConstants, CreditCardAutoFillConstants, IdentityAutoFillConstants, } from "./autofillConstants"; +export interface GenerateFillScriptOptions { + skipUsernameOnlyFill: boolean; + onlyEmptyFields: boolean; + onlyVisibleFields: boolean; + fillNewPassword: boolean; + cipher: CipherView; +} + export default class AutofillService implements AutofillServiceInterface { constructor( private cipherService: CipherService, @@ -31,10 +44,16 @@ export default class AutofillService implements AutofillServiceInterface { private logService: LogService ) {} - getFormsWithPasswordFields(pageDetails: AutofillPageDetails): any[] { - const formData: any[] = []; + getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] { + const formData: FormData[] = []; - const passwordFields = this.loadPasswordFields(pageDetails, true, true, false, false); + const passwordFields = AutofillService.loadPasswordFields( + pageDetails, + true, + true, + false, + false + ); if (passwordFields.length === 0) { return formData; } @@ -64,16 +83,17 @@ export default class AutofillService implements AutofillServiceInterface { return formData; } - async doAutoFill(options: any) { - let totpPromise: Promise = null; + async doAutoFill(options: AutoFillOptions) { const tab = options.tab; if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) { throw new Error("Nothing to auto-fill."); } + let totpPromise: Promise = null; + const canAccessPremium = await this.stateService.getCanAccessPremium(); let didAutofill = false; - options.pageDetails.forEach((pd: any) => { + options.pageDetails.forEach((pd) => { // make sure we're still on correct tab if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) { return; @@ -138,12 +158,7 @@ export default class AutofillService implements AutofillServiceInterface { } } - async doAutoFillActiveTab(pageDetails: any, fromCommand: boolean) { - const tab = await this.getActiveTab(); - if (!tab || !tab.url) { - return; - } - + async doAutoFillOnTab(pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean) { let cipher: CipherView; if (fromCommand) { cipher = await this.cipherService.getNextCipherForUrl(tab.url); @@ -186,9 +201,18 @@ export default class AutofillService implements AutofillServiceInterface { return totpCode; } + async doAutoFillActiveTab(pageDetails: PageDetail[], fromCommand: boolean) { + const tab = await this.getActiveTab(); + if (!tab || !tab.url) { + return; + } + + return await this.doAutoFillOnTab(pageDetails, tab, fromCommand); + } + // Helpers - private async getActiveTab(): Promise { + private async getActiveTab(): Promise { const tab = await BrowserApi.getTabFromCurrentWindow(); if (!tab) { throw new Error("No tab found."); @@ -197,7 +221,10 @@ export default class AutofillService implements AutofillServiceInterface { return tab; } - private generateFillScript(pageDetails: AutofillPageDetails, options: any): AutofillScript { + private generateFillScript( + pageDetails: AutofillPageDetails, + options: GenerateFillScriptOptions + ): AutofillScript { if (!pageDetails || !options.cipher) { return null; } @@ -209,13 +236,13 @@ export default class AutofillService implements AutofillServiceInterface { if (fields && fields.length) { const fieldNames: string[] = []; - fields.forEach((f: any) => { - if (this.hasValue(f.name)) { + fields.forEach((f) => { + if (AutofillService.hasValue(f.name)) { fieldNames.push(f.name.toLowerCase()); } }); - pageDetails.fields.forEach((field: any) => { + pageDetails.fields.forEach((field) => { // eslint-disable-next-line if (filledFields.hasOwnProperty(field.opid)) { return; @@ -228,10 +255,10 @@ export default class AutofillService implements AutofillServiceInterface { const matchingIndex = this.findMatchingFieldIndex(field, fieldNames); if (matchingIndex > -1) { const matchingField: FieldView = fields[matchingIndex]; - let val; + let val: string; if (matchingField.type === FieldType.Linked) { // Assumption: Linked Field is not being used to autofill a boolean value - val = options.cipher.linkedFieldValue(matchingField.linkedId); + val = options.cipher.linkedFieldValue(matchingField.linkedId) as string; } else { val = matchingField.value; if (val == null && matchingField.type === FieldType.Boolean) { @@ -240,7 +267,7 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[field.opid] = field; - this.fillByOpid(fillScript, field, val); + AutofillService.fillByOpid(fillScript, field, val); } }); } @@ -269,9 +296,9 @@ export default class AutofillService implements AutofillServiceInterface { private generateLoginFillScript( fillScript: AutofillScript, - pageDetails: any, + pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, - options: any + options: GenerateFillScriptOptions ): AutofillScript { if (!options.cipher.login) { return null; @@ -285,11 +312,11 @@ export default class AutofillService implements AutofillServiceInterface { if (!login.password || login.password === "") { // No password for this login. Maybe they just wanted to auto-fill some custom fields? - fillScript = this.setFillScriptForFocus(filledFields, fillScript); + fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript); return fillScript; } - let passwordFields = this.loadPasswordFields( + let passwordFields = AutofillService.loadPasswordFields( pageDetails, false, false, @@ -298,7 +325,7 @@ export default class AutofillService implements AutofillServiceInterface { ); if (!passwordFields.length && !options.onlyVisibleFields) { // not able to find any viewable password fields. maybe there are some "hidden" ones? - passwordFields = this.loadPasswordFields( + passwordFields = AutofillService.loadPasswordFields( pageDetails, true, true, @@ -362,11 +389,11 @@ export default class AutofillService implements AutofillServiceInterface { if (!passwordFields.length && !options.skipUsernameOnlyFill) { // No password fields on this page. Let's try to just fuzzy fill the username. - pageDetails.fields.forEach((f: any) => { + pageDetails.fields.forEach((f) => { if ( f.viewable && (f.type === "text" || f.type === "email" || f.type === "tel") && - this.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames) + AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames) ) { usernames.push(f); } @@ -380,7 +407,7 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[u.opid] = u; - this.fillByOpid(fillScript, u, login.username); + AutofillService.fillByOpid(fillScript, u, login.username); }); passwords.forEach((p) => { @@ -390,18 +417,18 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[p.opid] = p; - this.fillByOpid(fillScript, p, login.password); + AutofillService.fillByOpid(fillScript, p, login.password); }); - fillScript = this.setFillScriptForFocus(filledFields, fillScript); + fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript); return fillScript; } private generateCardFillScript( fillScript: AutofillScript, - pageDetails: any, + pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, - options: any + options: GenerateFillScriptOptions ): AutofillScript { if (!options.cipher.card) { return null; @@ -409,8 +436,8 @@ export default class AutofillService implements AutofillServiceInterface { const fillFields: { [id: string]: AutofillField } = {}; - pageDetails.fields.forEach((f: any) => { - if (this.forCustomFieldsOnly(f)) { + pageDetails.fields.forEach((f) => { + if (AutofillService.forCustomFieldsOnly(f)) { return; } @@ -429,7 +456,7 @@ export default class AutofillService implements AutofillServiceInterface { // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ if ( !fillFields.cardholderName && - this.isFieldMatch( + AutofillService.isFieldMatch( f[attr], CreditCardAutoFillConstants.CardHolderFieldNames, CreditCardAutoFillConstants.CardHolderFieldNameValues @@ -439,7 +466,7 @@ export default class AutofillService implements AutofillServiceInterface { break; } else if ( !fillFields.number && - this.isFieldMatch( + AutofillService.isFieldMatch( f[attr], CreditCardAutoFillConstants.CardNumberFieldNames, CreditCardAutoFillConstants.CardNumberFieldNameValues @@ -449,7 +476,7 @@ export default class AutofillService implements AutofillServiceInterface { break; } else if ( !fillFields.exp && - this.isFieldMatch( + AutofillService.isFieldMatch( f[attr], CreditCardAutoFillConstants.CardExpiryFieldNames, CreditCardAutoFillConstants.CardExpiryFieldNameValues @@ -459,25 +486,25 @@ export default class AutofillService implements AutofillServiceInterface { break; } else if ( !fillFields.expMonth && - this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames) + AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames) ) { fillFields.expMonth = f; break; } else if ( !fillFields.expYear && - this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames) + AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames) ) { fillFields.expYear = f; break; } else if ( !fillFields.code && - this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames) + AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames) ) { fillFields.code = f; break; } else if ( !fillFields.brand && - this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames) + AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames) ) { fillFields.brand = f; break; @@ -491,7 +518,7 @@ export default class AutofillService implements AutofillServiceInterface { this.makeScriptAction(fillScript, card, fillFields, filledFields, "code"); this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand"); - if (fillFields.expMonth && this.hasValue(card.expMonth)) { + if (fillFields.expMonth && AutofillService.hasValue(card.expMonth)) { let expMonth: string = card.expMonth; if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) { @@ -526,10 +553,10 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[fillFields.expMonth.opid] = fillFields.expMonth; - this.fillByOpid(fillScript, fillFields.expMonth, expMonth); + AutofillService.fillByOpid(fillScript, fillFields.expMonth, expMonth); } - if (fillFields.expYear && this.hasValue(card.expYear)) { + if (fillFields.expYear && AutofillService.hasValue(card.expYear)) { let expYear: string = card.expYear; if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) { for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) { @@ -572,10 +599,14 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[fillFields.expYear.opid] = fillFields.expYear; - this.fillByOpid(fillScript, fillFields.expYear, expYear); + AutofillService.fillByOpid(fillScript, fillFields.expYear, expYear); } - if (fillFields.exp && this.hasValue(card.expMonth) && this.hasValue(card.expYear)) { + if ( + fillFields.exp && + AutofillService.hasValue(card.expMonth) && + AutofillService.hasValue(card.expYear) + ) { const fullMonth = ("0" + card.expMonth).slice(-2); let fullYear: string = card.expYear; @@ -712,7 +743,7 @@ export default class AutofillService implements AutofillServiceInterface { return fillScript; } - private fieldAttrsContain(field: any, containsVal: string) { + private fieldAttrsContain(field: AutofillField, containsVal: string) { if (!field) { return false; } @@ -734,9 +765,9 @@ export default class AutofillService implements AutofillServiceInterface { private generateIdentityFillScript( fillScript: AutofillScript, - pageDetails: any, + pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, - options: any + options: GenerateFillScriptOptions ): AutofillScript { if (!options.cipher.identity) { return null; @@ -744,8 +775,8 @@ export default class AutofillService implements AutofillServiceInterface { const fillFields: { [id: string]: AutofillField } = {}; - pageDetails.fields.forEach((f: any) => { - if (this.forCustomFieldsOnly(f)) { + pageDetails.fields.forEach((f) => { + if (AutofillService.forCustomFieldsOnly(f)) { return; } @@ -764,7 +795,7 @@ export default class AutofillService implements AutofillServiceInterface { // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ if ( !fillFields.name && - this.isFieldMatch( + AutofillService.isFieldMatch( f[attr], IdentityAutoFillConstants.FullNameFieldNames, IdentityAutoFillConstants.FullNameFieldNameValues @@ -774,37 +805,37 @@ export default class AutofillService implements AutofillServiceInterface { break; } else if ( !fillFields.firstName && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) ) { fillFields.firstName = f; break; } else if ( !fillFields.middleName && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) ) { fillFields.middleName = f; break; } else if ( !fillFields.lastName && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) ) { fillFields.lastName = f; break; } else if ( !fillFields.title && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) ) { fillFields.title = f; break; } else if ( !fillFields.email && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) ) { fillFields.email = f; break; } else if ( !fillFields.address && - this.isFieldMatch( + AutofillService.isFieldMatch( f[attr], IdentityAutoFillConstants.AddressFieldNames, IdentityAutoFillConstants.AddressFieldNameValues @@ -814,61 +845,61 @@ export default class AutofillService implements AutofillServiceInterface { break; } else if ( !fillFields.address1 && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) ) { fillFields.address1 = f; break; } else if ( !fillFields.address2 && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) ) { fillFields.address2 = f; break; } else if ( !fillFields.address3 && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) ) { fillFields.address3 = f; break; } else if ( !fillFields.postalCode && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) ) { fillFields.postalCode = f; break; } else if ( !fillFields.city && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) ) { fillFields.city = f; break; } else if ( !fillFields.state && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) ) { fillFields.state = f; break; } else if ( !fillFields.country && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) ) { fillFields.country = f; break; } else if ( !fillFields.phone && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) ) { fillFields.phone = f; break; } else if ( !fillFields.username && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) ) { fillFields.username = f; break; } else if ( !fillFields.company && - this.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) + AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) ) { fillFields.company = f; break; @@ -923,16 +954,16 @@ export default class AutofillService implements AutofillServiceInterface { if (fillFields.name && (identity.firstName || identity.lastName)) { let fullName = ""; - if (this.hasValue(identity.firstName)) { + if (AutofillService.hasValue(identity.firstName)) { fullName = identity.firstName; } - if (this.hasValue(identity.middleName)) { + if (AutofillService.hasValue(identity.middleName)) { if (fullName !== "") { fullName += " "; } fullName += identity.middleName; } - if (this.hasValue(identity.lastName)) { + if (AutofillService.hasValue(identity.lastName)) { if (fullName !== "") { fullName += " "; } @@ -942,18 +973,18 @@ export default class AutofillService implements AutofillServiceInterface { this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); } - if (fillFields.address && this.hasValue(identity.address1)) { + if (fillFields.address && AutofillService.hasValue(identity.address1)) { let address = ""; - if (this.hasValue(identity.address1)) { + if (AutofillService.hasValue(identity.address1)) { address = identity.address1; } - if (this.hasValue(identity.address2)) { + if (AutofillService.hasValue(identity.address2)) { if (address !== "") { address += ", "; } address += identity.address2; } - if (this.hasValue(identity.address3)) { + if (AutofillService.hasValue(identity.address3)) { if (address !== "") { address += ", "; } @@ -970,7 +1001,11 @@ export default class AutofillService implements AutofillServiceInterface { return excludedTypes.indexOf(type) > -1; } - private isFieldMatch(value: string, options: string[], containsOptions?: string[]): boolean { + private static isFieldMatch( + value: string, + options: string[], + containsOptions?: string[] + ): boolean { value = value .trim() .toLowerCase() @@ -1011,12 +1046,15 @@ export default class AutofillService implements AutofillServiceInterface { filledFields: { [id: string]: AutofillField } ) { let doFill = false; - if (this.hasValue(dataValue) && field) { + if (AutofillService.hasValue(dataValue) && field) { if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) { for (let i = 0; i < field.selectInfo.options.length; i++) { const option = field.selectInfo.options[i]; for (let j = 0; j < option.length; j++) { - if (this.hasValue(option[j]) && option[j].toLowerCase() === dataValue.toLowerCase()) { + if ( + AutofillService.hasValue(option[j]) && + option[j].toLowerCase() === dataValue.toLowerCase() + ) { doFill = true; if (option.length > 1) { dataValue = option[1]; @@ -1036,11 +1074,11 @@ export default class AutofillService implements AutofillServiceInterface { if (doFill) { filledFields[field.opid] = field; - this.fillByOpid(fillScript, field, dataValue); + AutofillService.fillByOpid(fillScript, field, dataValue); } } - private loadPasswordFields( + static loadPasswordFields( pageDetails: AutofillPageDetails, canBeHidden: boolean, canBeReadOnly: boolean, @@ -1049,7 +1087,7 @@ export default class AutofillService implements AutofillServiceInterface { ) { const arr: AutofillField[] = []; pageDetails.fields.forEach((f) => { - if (this.forCustomFieldsOnly(f)) { + if (AutofillService.forCustomFieldsOnly(f)) { return; } @@ -1111,7 +1149,7 @@ export default class AutofillService implements AutofillServiceInterface { let usernameField: AutofillField = null; for (let i = 0; i < pageDetails.fields.length; i++) { const f = pageDetails.fields[i]; - if (this.forCustomFieldsOnly(f)) { + if (AutofillService.forCustomFieldsOnly(f)) { continue; } @@ -1195,7 +1233,7 @@ export default class AutofillService implements AutofillServiceInterface { private fieldPropertyIsMatch(field: any, property: string, name: string): boolean { let fieldVal = field[property] as string; - if (!this.hasValue(fieldVal)) { + if (!AutofillService.hasValue(fieldVal)) { return false; } @@ -1227,33 +1265,45 @@ export default class AutofillService implements AutofillServiceInterface { return fieldVal.toLowerCase() === name; } - private fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean { - if (this.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) { + static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean { + if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) { return true; } - if (this.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) { + if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) { return true; } - if (this.hasValue(field["label-tag"]) && this.fuzzyMatch(names, field["label-tag"])) { + if ( + AutofillService.hasValue(field["label-tag"]) && + this.fuzzyMatch(names, field["label-tag"]) + ) { return true; } - if (this.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) { + if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) { return true; } - if (this.hasValue(field["label-left"]) && this.fuzzyMatch(names, field["label-left"])) { + if ( + AutofillService.hasValue(field["label-left"]) && + this.fuzzyMatch(names, field["label-left"]) + ) { return true; } - if (this.hasValue(field["label-top"]) && this.fuzzyMatch(names, field["label-top"])) { + if ( + AutofillService.hasValue(field["label-top"]) && + this.fuzzyMatch(names, field["label-top"]) + ) { return true; } - if (this.hasValue(field["label-aria"]) && this.fuzzyMatch(names, field["label-aria"])) { + if ( + AutofillService.hasValue(field["label-aria"]) && + this.fuzzyMatch(names, field["label-aria"]) + ) { return true; } return false; } - private fuzzyMatch(options: string[], value: string): boolean { + private static fuzzyMatch(options: string[], value: string): boolean { if (options == null || options.length === 0 || value == null || value === "") { return false; } @@ -1272,11 +1322,11 @@ export default class AutofillService implements AutofillServiceInterface { return false; } - private hasValue(str: string): boolean { + static hasValue(str: string): boolean { return str && str !== ""; } - private setFillScriptForFocus( + static setFillScriptForFocus( filledFields: { [id: string]: AutofillField }, fillScript: AutofillScript ): AutofillScript { @@ -1304,7 +1354,7 @@ export default class AutofillService implements AutofillServiceInterface { return fillScript; } - private fillByOpid(fillScript: AutofillScript, field: AutofillField, value: string): void { + static fillByOpid(fillScript: AutofillScript, field: AutofillField, value: string): void { if (field.maxLength && value && value.length > field.maxLength) { value = value.substr(0, value.length); } @@ -1315,7 +1365,7 @@ export default class AutofillService implements AutofillServiceInterface { fillScript.script.push(["fill_by_opid", field.opid, value]); } - private forCustomFieldsOnly(field: AutofillField): boolean { + static forCustomFieldsOnly(field: AutofillField): boolean { return field.tagName === "span"; } } diff --git a/apps/browser/src/services/browserLocalStorage.service.ts b/apps/browser/src/services/browserLocalStorage.service.ts index 9556a6e4e27..2c93920df0c 100644 --- a/apps/browser/src/services/browserLocalStorage.service.ts +++ b/apps/browser/src/services/browserLocalStorage.service.ts @@ -1,5 +1,5 @@ import AbstractChromeStorageService from "./abstractChromeStorageApi.service"; export default class BrowserLocalStorageService extends AbstractChromeStorageService { - protected chromeStorageApi: any = chrome.storage.local; + protected chromeStorageApi = chrome.storage.local; } diff --git a/apps/browser/src/services/browserMemoryStorage.service.ts b/apps/browser/src/services/browserMemoryStorage.service.ts index a1195d1a44c..993ae8a16ef 100644 --- a/apps/browser/src/services/browserMemoryStorage.service.ts +++ b/apps/browser/src/services/browserMemoryStorage.service.ts @@ -1,5 +1,5 @@ import AbstractChromeStorageService from "./abstractChromeStorageApi.service"; export default class BrowserMemoryStorageService extends AbstractChromeStorageService { - protected chromeStorageApi: any = (chrome.storage as any).session; + protected chromeStorageApi = chrome.storage.session; } diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index e4cc10060a5..3f16579e44b 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -176,13 +176,6 @@ const config = { return chunk.name === "popup/main"; }, }, - commons2: { - test: /[\\/]node_modules[\\/]/, - name: "vendor", - chunks: (chunk) => { - return chunk.name === "background"; - }, - }, }, }, }, @@ -209,4 +202,16 @@ const config = { plugins: plugins, }; +if (manifestVersion == 2) { + // We can't use this in manifest v3 + // Ideally we understand why this breaks it and we don't have to do this + config.optimization.splitChunks.cacheGroups.commons2 = { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + chunks: (chunk) => { + return chunk.name === "background"; + }, + }; +} + module.exports = config; diff --git a/apps/web/src/app/settings/verify-email.component.ts b/apps/web/src/app/settings/verify-email.component.ts index bd594416891..665bb9975a7 100644 --- a/apps/web/src/app/settings/verify-email.component.ts +++ b/apps/web/src/app/settings/verify-email.component.ts @@ -10,7 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti templateUrl: "verify-email.component.html", }) export class VerifyEmailComponent { - actionPromise: Promise; + actionPromise: Promise; constructor( private apiService: ApiService, diff --git a/libs/common/spec/services/consoleLog.service.spec.ts b/libs/common/spec/services/consoleLog.service.spec.ts index fc487ce7040..5f8e9ead013 100644 --- a/libs/common/spec/services/consoleLog.service.spec.ts +++ b/libs/common/spec/services/consoleLog.service.spec.ts @@ -78,25 +78,4 @@ describe("ConsoleLogService", () => { error: { 0: "this is an error message" }, }); }); - - it("times with output to info", async () => { - logService.time(); - await new Promise((r) => setTimeout(r, 250)); - const duration = logService.timeEnd(); - expect(duration[0]).toBe(0); - expect(duration[1]).toBeGreaterThan(0); - expect(duration[1]).toBeLessThan(500 * 10e6); - - expect(caughtMessage).toEqual(expect.arrayContaining([])); - expect(caughtMessage.log.length).toBe(1); - expect(caughtMessage.log[0]).toEqual(expect.stringMatching(/^default: \d+\.?\d*ms$/)); - }); - - it("filters time output", async () => { - logService = new ConsoleLogService(true, () => true); - logService.time(); - logService.timeEnd(); - - expect(caughtMessage).toEqual({}); - }); }); diff --git a/libs/common/src/abstractions/log.service.ts b/libs/common/src/abstractions/log.service.ts index 9997e480d60..f5ba7fab3c0 100644 --- a/libs/common/src/abstractions/log.service.ts +++ b/libs/common/src/abstractions/log.service.ts @@ -6,6 +6,4 @@ export abstract class LogService { warning: (message: string) => void; error: (message: string) => void; write: (level: LogLevelType, message: string) => void; - time: (label: string) => void; - timeEnd: (label: string) => [number, number]; } diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts index 1d96e08287f..524c4384703 100644 --- a/libs/common/src/misc/utils.ts +++ b/libs/common/src/misc/utils.ts @@ -1,17 +1,28 @@ /* eslint-disable no-useless-escape */ import * as tldjs from "tldjs"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; + import { I18nService } from "../abstractions/i18n.service"; const nodeURL = typeof window === "undefined" ? require("url") : null; +declare global { + /* eslint-disable-next-line no-var */ + var bitwardenContainerService: BitwardenContainerService; +} + +interface BitwardenContainerService { + getCryptoService: () => CryptoService; +} + export class Utils { static inited = false; static isNode = false; static isBrowser = true; static isMobileBrowser = false; static isAppleMobileBrowser = false; - static global: any = null; + static global: typeof global = null; static tldEndingRegex = /.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/; // Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers. @@ -29,16 +40,25 @@ export class Utils { (process as any).release != null && (process as any).release.name === "node"; Utils.isBrowser = typeof window !== "undefined"; + Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window); - Utils.global = Utils.isNode && !Utils.isBrowser ? global : window; + + if (Utils.isNode) { + Utils.global = global; + } else if (Utils.isBrowser) { + Utils.global = window; + } else { + // If it's not browser or node then it must be a service worker + Utils.global = self; + } } static fromB64ToArray(str: string): Uint8Array { if (Utils.isNode) { return new Uint8Array(Buffer.from(str, "base64")); } else { - const binaryString = window.atob(str); + const binaryString = Utils.global.atob(str); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); @@ -93,7 +113,7 @@ export class Utils { for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } - return window.btoa(binary); + return Utils.global.btoa(binary); } } @@ -157,7 +177,7 @@ export class Utils { if (Utils.isNode) { return Buffer.from(utfStr, "utf8").toString("base64"); } else { - return decodeURIComponent(escape(window.btoa(utfStr))); + return decodeURIComponent(escape(Utils.global.btoa(utfStr))); } } @@ -169,7 +189,7 @@ export class Utils { if (Utils.isNode) { return Buffer.from(b64Str, "base64").toString("utf8"); } else { - return decodeURIComponent(escape(window.atob(b64Str))); + return decodeURIComponent(escape(Utils.global.atob(b64Str))); } } @@ -384,7 +404,7 @@ export class Utils { return new nodeURL.URL(uriString); } else if (typeof URL === "function") { return new URL(uriString); - } else if (window != null) { + } else if (typeof window !== "undefined") { const hasProtocol = uriString.indexOf("://") > -1; if (!hasProtocol && uriString.indexOf(".") > -1) { uriString = "http://" + uriString; diff --git a/libs/common/src/models/domain/attachment.ts b/libs/common/src/models/domain/attachment.ts index cbfffdcf687..053438390da 100644 --- a/libs/common/src/models/domain/attachment.ts +++ b/libs/common/src/models/domain/attachment.ts @@ -48,7 +48,7 @@ export class Attachment extends Domain { if (this.key != null) { let cryptoService: CryptoService; - const containerService = (Utils.global as any).bitwardenContainerService; + const containerService = Utils.global.bitwardenContainerService; if (containerService) { cryptoService = containerService.getCryptoService(); } else { diff --git a/libs/common/src/models/domain/encString.ts b/libs/common/src/models/domain/encString.ts index 9c237a485b6..a11ba3a58c6 100644 --- a/libs/common/src/models/domain/encString.ts +++ b/libs/common/src/models/domain/encString.ts @@ -104,7 +104,7 @@ export class EncString implements IEncrypted { } let cryptoService: CryptoService; - const containerService = (Utils.global as any).bitwardenContainerService; + const containerService = Utils.global.bitwardenContainerService; if (containerService) { cryptoService = containerService.getCryptoService(); } else { diff --git a/libs/common/src/services/cipher.service.ts b/libs/common/src/services/cipher.service.ts index c8e588616d7..ebc18b21845 100644 --- a/libs/common/src/services/cipher.service.ts +++ b/libs/common/src/services/cipher.service.ts @@ -341,7 +341,7 @@ export class CipherService implements CipherServiceAbstraction { throw new Error("No key."); } - const promises: any[] = []; + const promises: Promise[] = []; const ciphers = await this.getAll(); ciphers.forEach(async (cipher) => { promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); diff --git a/libs/common/src/services/consoleLog.service.ts b/libs/common/src/services/consoleLog.service.ts index 9959ef6229f..8d48013bfac 100644 --- a/libs/common/src/services/consoleLog.service.ts +++ b/libs/common/src/services/consoleLog.service.ts @@ -1,5 +1,3 @@ -import * as hrtime from "browser-hrtime"; - import { LogService as LogServiceAbstraction } from "../abstractions/log.service"; import { LogLevelType } from "../enums/logLevelType"; @@ -56,17 +54,4 @@ export class ConsoleLogService implements LogServiceAbstraction { break; } } - - time(label = "default") { - if (!this.timersMap.has(label)) { - this.timersMap.set(label, hrtime()); - } - } - - timeEnd(label = "default"): [number, number] { - const elapsed = hrtime(this.timersMap.get(label)); - this.timersMap.delete(label); - this.write(LogLevelType.Info, `${label}: ${elapsed[0] * 1000 + elapsed[1] / 10e6}ms`); - return elapsed; - } } diff --git a/libs/common/src/services/noopEvent.service.ts b/libs/common/src/services/noopEvent.service.ts new file mode 100644 index 00000000000..63d045524ec --- /dev/null +++ b/libs/common/src/services/noopEvent.service.ts @@ -0,0 +1,24 @@ +import { EventService } from "@bitwarden/common/abstractions/event.service"; +import { EventType } from "@bitwarden/common/enums/eventType"; + +/** + * If you want to use this, don't. + * If you think you should use that after the warning, don't. + */ +export default class NoOpEventService implements EventService { + constructor() { + if (chrome.runtime.getManifest().manifest_version !== 3) { + throw new Error("You are not allowed to use this when not in manifest_version 3"); + } + } + + collect(eventType: EventType, cipherId?: string, uploadImmediately?: boolean) { + return Promise.resolve(); + } + uploadEvents(userId?: string) { + return Promise.resolve(); + } + clearEvents(userId?: string) { + return Promise.resolve(); + } +} diff --git a/libs/common/src/services/search.service.ts b/libs/common/src/services/search.service.ts index fe2c87c33d8..919a58941ee 100644 --- a/libs/common/src/services/search.service.ts +++ b/libs/common/src/services/search.service.ts @@ -11,6 +11,8 @@ import { CipherView } from "../models/view/cipherView"; import { SendView } from "../models/view/sendView"; export class SearchService implements SearchServiceAbstraction { + private static registeredPipeline = false; + indexedEntityId?: string = null; private indexing = false; private index: lunr.Index = null; @@ -31,8 +33,13 @@ export class SearchService implements SearchServiceAbstraction { } }); - //register lunr pipeline function - lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents"); + // Currently have to ensure this is only done a single time. Lunr allows you to register a function + // multiple times but they will add a warning message to the console. The way they do that breaks when ran on a service worker. + if (!SearchService.registeredPipeline) { + SearchService.registeredPipeline = true; + //register lunr pipeline function + lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents"); + } } clearIndex(): void { @@ -54,7 +61,6 @@ export class SearchService implements SearchServiceAbstraction { return; } - this.logService.time("search indexing"); this.indexing = true; this.indexedEntityId = indexedEntityId; this.index = null; @@ -95,7 +101,7 @@ export class SearchService implements SearchServiceAbstraction { this.indexing = false; - this.logService.timeEnd("search indexing"); + this.logService.info("Finished search indexing"); } async searchCiphers( diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index 39f34886496..24a2e630e33 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -610,7 +610,7 @@ export class StateService< ); } - @withPrototypeForArrayMembers(CipherView) + @withPrototypeForArrayMembers(CipherView, CipherView.fromJSON) async getDecryptedCiphers(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) diff --git a/libs/common/src/services/webCryptoFunction.service.ts b/libs/common/src/services/webCryptoFunction.service.ts index b863f2267ca..2e0b920f510 100644 --- a/libs/common/src/services/webCryptoFunction.service.ts +++ b/libs/common/src/services/webCryptoFunction.service.ts @@ -9,7 +9,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { private crypto: Crypto; private subtle: SubtleCrypto; - constructor(win: Window) { + constructor(win: Window | typeof global) { this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null; this.subtle = !!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null; diff --git a/package-lock.json b/package-lock.json index 49fa99c43ca..d1aa4525845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "big-integer": "^1.6.51", "bootstrap": "4.6.0", "braintree-web-drop-in": "^1.33.1", - "browser-hrtime": "^1.1.8", "bufferutil": "^4.0.6", "chalk": "^4.1.0", "commander": "^7.2.0", @@ -83,7 +82,7 @@ "@storybook/angular": "^6.5.7", "@storybook/builder-webpack5": "^6.5.7", "@storybook/manager-webpack5": "^6.5.7", - "@types/chrome": "^0.0.139", + "@types/chrome": "^0.0.190", "@types/duo_web_sdk": "^2.7.1", "@types/firefox-webext-browser": "^82.0.0", "@types/inquirer": "^8.2.1", @@ -12504,9 +12503,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.0.139", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz", - "integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==", + "version": "0.0.190", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz", + "integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==", "dev": true, "dependencies": { "@types/filesystem": "*", @@ -52084,9 +52083,9 @@ } }, "@types/chrome": { - "version": "0.0.139", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz", - "integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==", + "version": "0.0.190", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz", + "integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==", "dev": true, "requires": { "@types/filesystem": "*", diff --git a/package.json b/package.json index 82e67080c1b..8675da7ba00 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@storybook/angular": "^6.5.7", "@storybook/builder-webpack5": "^6.5.7", "@storybook/manager-webpack5": "^6.5.7", - "@types/chrome": "^0.0.139", + "@types/chrome": "^0.0.190", "@types/duo_web_sdk": "^2.7.1", "@types/firefox-webext-browser": "^82.0.0", "@types/inquirer": "^8.2.1", @@ -155,7 +155,6 @@ "big-integer": "^1.6.51", "bootstrap": "4.6.0", "braintree-web-drop-in": "^1.33.1", - "browser-hrtime": "^1.1.8", "bufferutil": "^4.0.6", "chalk": "^4.1.0", "commander": "^7.2.0", From 1ba76dec1683a1ad04b606ae5c613d1905f971c0 Mon Sep 17 00:00:00 2001 From: Justin Baur Date: Tue, 9 Aug 2022 21:31:02 -0400 Subject: [PATCH 6/7] [PS-1107] User Verification Service Refactor (#3219) * UserVerificationService refactor * Remove temp change * move import order * Address PR feedback --- .../browser/src/background/main.background.ts | 11 ++++-- .../src/popup/services/services.module.ts | 2 +- .../src/popup/settings/export.component.ts | 2 +- apps/cli/src/bw.ts | 9 ++++- .../desktop/src/app/vault/export.component.ts | 2 +- .../app/accounts/update-password.component.ts | 2 +- .../billing-sync-api-key.component.ts | 2 +- .../settings/delete-organization.component.ts | 6 +-- .../import-export/org-export.component.ts | 4 +- .../enroll-master-password-reset.component.ts | 4 +- .../web/src/app/settings/api-key.component.ts | 2 +- .../deauthorize-sessions.component.ts | 4 +- .../src/app/settings/purge-vault.component.ts | 4 +- .../two-factor-authenticator.component.ts | 23 ++++++++++-- .../app/settings/two-factor-base.component.ts | 7 ++-- .../app/settings/two-factor-duo.component.ts | 7 ++-- .../settings/two-factor-email.component.ts | 9 +++-- .../settings/two-factor-verify.component.ts | 20 ++-------- .../settings/two-factor-webauthn.component.ts | 21 ++++++++--- .../settings/two-factor-yubikey.component.ts | 18 ++++++--- .../tools/import-export/export.component.ts | 2 +- .../src/components/export.component.ts | 4 +- .../components/update-password.component.ts | 4 +- .../components/user-verification.component.ts | 6 +-- .../src/services/jslib-services.module.ts | 17 +++++++-- libs/common/src/abstractions/api.service.ts | 3 -- ...serVerification-api.service.abstraction.ts | 6 +++ .../userVerification.service.abstraction.ts} | 4 +- .../src/services/account/account.service.ts | 4 +- libs/common/src/services/api.service.ts | 9 ----- .../userVerification-api.service.ts | 14 +++++++ .../userVerification.service.ts | 22 +++++------ libs/common/src/types/authResponse.ts | 12 ++++++ libs/common/src/types/twoFactorResponse.ts | 14 +++++++ .../src/services/electronStorage.service.ts | 37 ++++++++++++++++--- 35 files changed, 209 insertions(+), 108 deletions(-) create mode 100644 libs/common/src/abstractions/userVerification/userVerification-api.service.abstraction.ts rename libs/common/src/abstractions/{userVerification.service.ts => userVerification/userVerification.service.abstraction.ts} (67%) create mode 100644 libs/common/src/services/userVerification/userVerification-api.service.ts rename libs/common/src/services/{ => userVerification}/userVerification.service.ts (74%) create mode 100644 libs/common/src/types/authResponse.ts create mode 100644 libs/common/src/types/twoFactorResponse.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 101b2e564b1..3b0f92e8218 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -31,7 +31,8 @@ import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abs import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.service"; -import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction"; +import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; @@ -71,7 +72,8 @@ import { SystemService } from "@bitwarden/common/services/system.service"; import { TokenService } from "@bitwarden/common/services/token.service"; import { TotpService } from "@bitwarden/common/services/totp.service"; import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service"; -import { UserVerificationService } from "@bitwarden/common/services/userVerification.service"; +import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service"; +import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service"; import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; @@ -152,6 +154,7 @@ export default class MainBackground { encryptService: EncryptService; folderApiService: FolderApiServiceAbstraction; policyApiService: PolicyApiServiceAbstraction; + userVerificationApiService: UserVerificationApiServiceAbstraction; // Passed to the popup for Safari to workaround issues with theming, downloading, etc. backgroundWindow = window; @@ -422,10 +425,12 @@ export default class MainBackground { ); this.popupUtilsService = new PopupUtilsService(isPrivateMode); + this.userVerificationApiService = new UserVerificationApiService(this.apiService); + this.userVerificationService = new UserVerificationService( this.cryptoService, this.i18nService, - this.apiService + this.userVerificationApiService ); const systemUtilsServiceReloadCallback = () => { diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 7ed102b1688..265c765cbb6 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -45,7 +45,7 @@ import { SyncService } from "@bitwarden/common/abstractions/sync.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { AuthService } from "@bitwarden/common/services/auth.service"; diff --git a/apps/browser/src/popup/settings/export.component.ts b/apps/browser/src/popup/settings/export.component.ts index 8dc123566db..47301b5fdee 100644 --- a/apps/browser/src/popup/settings/export.component.ts +++ b/apps/browser/src/popup/settings/export.component.ts @@ -11,7 +11,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; @Component({ selector: "app-export", diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 6739eb5d975..0bdc1e0047a 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -42,7 +42,8 @@ import { SyncService } from "@bitwarden/common/services/sync.service"; import { TokenService } from "@bitwarden/common/services/token.service"; import { TotpService } from "@bitwarden/common/services/totp.service"; import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service"; -import { UserVerificationService } from "@bitwarden/common/services/userVerification.service"; +import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service"; +import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service"; import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; @@ -106,6 +107,7 @@ export class Main { twoFactorService: TwoFactorService; broadcasterService: BroadcasterService; folderApiService: FolderApiService; + userVerificationApiService: UserVerificationApiService; constructor() { let p = null; @@ -330,10 +332,13 @@ export class Main { this.program = new Program(this); this.vaultProgram = new VaultProgram(this); this.sendProgram = new SendProgram(this); + + this.userVerificationApiService = new UserVerificationApiService(this.apiService); + this.userVerificationService = new UserVerificationService( this.cryptoService, this.i18nService, - this.apiService + this.userVerificationApiService ); } diff --git a/apps/desktop/src/app/vault/export.component.ts b/apps/desktop/src/app/vault/export.component.ts index e21d8e94677..4793a70b755 100644 --- a/apps/desktop/src/app/vault/export.component.ts +++ b/apps/desktop/src/app/vault/export.component.ts @@ -13,7 +13,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; const BroadcasterSubscriptionId = "ExportComponent"; diff --git a/apps/web/src/app/accounts/update-password.component.ts b/apps/web/src/app/accounts/update-password.component.ts index 0d5da3ca3f3..dfa5d82c0d0 100644 --- a/apps/web/src/app/accounts/update-password.component.ts +++ b/apps/web/src/app/accounts/update-password.component.ts @@ -11,7 +11,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; @Component({ selector: "app-update-password", diff --git a/apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts b/apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts index dfe87ebbf7e..5b36a69e172 100644 --- a/apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts +++ b/apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts @@ -3,7 +3,7 @@ import { Component } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { OrganizationApiKeyType } from "@bitwarden/common/enums/organizationApiKeyType"; import { OrganizationApiKeyRequest } from "@bitwarden/common/models/request/organizationApiKeyRequest"; import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse"; diff --git a/apps/web/src/app/organizations/settings/delete-organization.component.ts b/apps/web/src/app/organizations/settings/delete-organization.component.ts index a008dab06af..19b82ebcaab 100644 --- a/apps/web/src/app/organizations/settings/delete-organization.component.ts +++ b/apps/web/src/app/organizations/settings/delete-organization.component.ts @@ -6,7 +6,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { Utils } from "@bitwarden/common/misc/utils"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; @@ -53,10 +53,10 @@ export class DeleteOrganizationComponent implements OnInit { deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete"; organizationName: string; organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary(); - @Output() onSuccess: EventEmitter = new EventEmitter(); + @Output() onSuccess: EventEmitter = new EventEmitter(); masterPassword: Verification; - formPromise: Promise; + formPromise: Promise; constructor( private apiService: ApiService, diff --git a/apps/web/src/app/organizations/tools/import-export/org-export.component.ts b/apps/web/src/app/organizations/tools/import-export/org-export.component.ts index 277446a36a5..79f92542317 100644 --- a/apps/web/src/app/organizations/tools/import-export/org-export.component.ts +++ b/apps/web/src/app/organizations/tools/import-export/org-export.component.ts @@ -10,7 +10,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { EventType } from "@bitwarden/common/enums/eventType"; import { ExportComponent } from "../../../tools/import-export/export.component"; @@ -66,7 +66,7 @@ export class OrganizationExportComponent extends ExportComponent { return super.getFileName("org"); } - async collectEvent(): Promise { + async collectEvent(): Promise { await this.eventService.collect( EventType.Organization_ClientExportedVault, null, diff --git a/apps/web/src/app/organizations/users/enroll-master-password-reset.component.ts b/apps/web/src/app/organizations/users/enroll-master-password-reset.component.ts index 9252c92feb0..2a8e5f21880 100644 --- a/apps/web/src/app/organizations/users/enroll-master-password-reset.component.ts +++ b/apps/web/src/app/organizations/users/enroll-master-password-reset.component.ts @@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/models/request/organizationUserResetPasswordEnrollmentRequest"; @@ -22,7 +22,7 @@ export class EnrollMasterPasswordReset { organization: Organization; verification: Verification; - formPromise: Promise; + formPromise: Promise; constructor( private userVerificationService: UserVerificationService, diff --git a/apps/web/src/app/settings/api-key.component.ts b/apps/web/src/app/settings/api-key.component.ts index 25238c29895..c0e976ea087 100644 --- a/apps/web/src/app/settings/api-key.component.ts +++ b/apps/web/src/app/settings/api-key.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse"; import { Verification } from "@bitwarden/common/types/verification"; diff --git a/apps/web/src/app/settings/deauthorize-sessions.component.ts b/apps/web/src/app/settings/deauthorize-sessions.component.ts index c617b4750bc..05a93233b6a 100644 --- a/apps/web/src/app/settings/deauthorize-sessions.component.ts +++ b/apps/web/src/app/settings/deauthorize-sessions.component.ts @@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { Verification } from "@bitwarden/common/types/verification"; @Component({ @@ -14,7 +14,7 @@ import { Verification } from "@bitwarden/common/types/verification"; }) export class DeauthorizeSessionsComponent { masterPassword: Verification; - formPromise: Promise; + formPromise: Promise; constructor( private apiService: ApiService, diff --git a/apps/web/src/app/settings/purge-vault.component.ts b/apps/web/src/app/settings/purge-vault.component.ts index b2ee3ee8c3a..e10782914f0 100644 --- a/apps/web/src/app/settings/purge-vault.component.ts +++ b/apps/web/src/app/settings/purge-vault.component.ts @@ -5,7 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { Verification } from "@bitwarden/common/types/verification"; @Component({ @@ -16,7 +16,7 @@ export class PurgeVaultComponent { @Input() organizationId?: string = null; masterPassword: Verification; - formPromise: Promise; + formPromise: Promise; constructor( private apiService: ApiService, diff --git a/apps/web/src/app/settings/two-factor-authenticator.component.ts b/apps/web/src/app/settings/two-factor-authenticator.component.ts index 9a1269d5f83..569a95621fb 100644 --- a/apps/web/src/app/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/settings/two-factor-authenticator.component.ts @@ -5,13 +5,28 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/models/request/updateTwoFactorAuthenticatorRequest"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; import { TwoFactorBaseComponent } from "./two-factor-base.component"; +// NOTE: There are additional options available but these are just the ones we are current using. +// See: https://github.com/neocotic/qrious#examples +interface QRiousOptions { + element: HTMLElement; + value: string; + size: number; +} + +declare global { + interface Window { + QRious: new (options: QRiousOptions) => unknown; + } +} + @Component({ selector: "app-two-factor-authenticator", templateUrl: "two-factor-authenticator.component.html", @@ -23,7 +38,7 @@ export class TwoFactorAuthenticatorComponent type = TwoFactorProviderType.Authenticator; key: string; token: string; - formPromise: Promise; + formPromise: Promise; private qrScript: HTMLScriptElement; @@ -49,7 +64,7 @@ export class TwoFactorAuthenticatorComponent window.document.body.removeChild(this.qrScript); } - auth(authResponse: any) { + auth(authResponse: AuthResponse) { super.auth(authResponse); return this.processResponse(authResponse.response); } @@ -80,7 +95,7 @@ export class TwoFactorAuthenticatorComponent this.key = response.key; const email = await this.stateService.getEmail(); window.setTimeout(() => { - new (window as any).QRious({ + new window.QRious({ element: document.getElementById("qr"), value: "otpauth://totp/Bitwarden:" + diff --git a/apps/web/src/app/settings/two-factor-base.component.ts b/apps/web/src/app/settings/two-factor-base.component.ts index d38a59ed498..d54092aebd2 100644 --- a/apps/web/src/app/settings/two-factor-base.component.ts +++ b/apps/web/src/app/settings/two-factor-base.component.ts @@ -4,11 +4,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { TwoFactorProviderRequest } from "@bitwarden/common/models/request/twoFactorProviderRequest"; +import { AuthResponseBase } from "@bitwarden/common/types/authResponse"; @Directive() export abstract class TwoFactorBaseComponent { @@ -31,7 +32,7 @@ export abstract class TwoFactorBaseComponent { protected userVerificationService: UserVerificationService ) {} - protected auth(authResponse: any) { + protected auth(authResponse: AuthResponseBase) { this.hashedSecret = authResponse.secret; this.verificationType = authResponse.verificationType; this.authed = true; @@ -46,7 +47,7 @@ export abstract class TwoFactorBaseComponent { } } - protected async disable(promise: Promise) { + protected async disable(promise: Promise) { const confirmed = await this.platformUtilsService.showDialog( this.i18nService.t("twoStepDisableDesc"), this.i18nService.t("disable"), diff --git a/apps/web/src/app/settings/two-factor-duo.component.ts b/apps/web/src/app/settings/two-factor-duo.component.ts index e2cb8f62d6a..46c62b1bc58 100644 --- a/apps/web/src/app/settings/two-factor-duo.component.ts +++ b/apps/web/src/app/settings/two-factor-duo.component.ts @@ -4,10 +4,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/models/request/updateTwoFactorDuoRequest"; import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; import { TwoFactorBaseComponent } from "./two-factor-base.component"; @@ -20,7 +21,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent { ikey: string; skey: string; host: string; - formPromise: Promise; + formPromise: Promise; constructor( apiService: ApiService, @@ -32,7 +33,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent { super(apiService, i18nService, platformUtilsService, logService, userVerificationService); } - auth(authResponse: any) { + auth(authResponse: AuthResponse) { super.auth(authResponse); this.processResponse(authResponse.response); } diff --git a/apps/web/src/app/settings/two-factor-email.component.ts b/apps/web/src/app/settings/two-factor-email.component.ts index 4bf376ef6a4..968695812b9 100644 --- a/apps/web/src/app/settings/two-factor-email.component.ts +++ b/apps/web/src/app/settings/two-factor-email.component.ts @@ -5,11 +5,12 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/twoFactorEmailRequest"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/models/request/updateTwoFactorEmailRequest"; import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; import { TwoFactorBaseComponent } from "./two-factor-base.component"; @@ -22,8 +23,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { email: string; token: string; sentEmail: string; - formPromise: Promise; - emailPromise: Promise; + formPromise: Promise; + emailPromise: Promise; constructor( apiService: ApiService, @@ -36,7 +37,7 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { super(apiService, i18nService, platformUtilsService, logService, userVerificationService); } - auth(authResponse: any) { + auth(authResponse: AuthResponse) { super.auth(authResponse); return this.processResponse(authResponse.response); } diff --git a/apps/web/src/app/settings/two-factor-verify.component.ts b/apps/web/src/app/settings/two-factor-verify.component.ts index f9492edc6db..5e8b91fd774 100644 --- a/apps/web/src/app/settings/two-factor-verify.component.ts +++ b/apps/web/src/app/settings/two-factor-verify.component.ts @@ -2,26 +2,14 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse"; -import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse"; -import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/models/response/twoFactorRescoverResponse"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/models/response/twoFactorWebAuthnResponse"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/models/response/twoFactorYubiKeyResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; +import { TwoFactorResponse } from "@bitwarden/common/types/twoFactorResponse"; import { Verification } from "@bitwarden/common/types/verification"; -type TwoFactorResponse = - | TwoFactorRecoverResponse - | TwoFactorDuoResponse - | TwoFactorEmailResponse - | TwoFactorWebAuthnResponse - | TwoFactorAuthenticatorResponse - | TwoFactorYubiKeyResponse; - @Component({ selector: "app-two-factor-verify", templateUrl: "two-factor-verify.component.html", @@ -29,7 +17,7 @@ type TwoFactorResponse = export class TwoFactorVerifyComponent { @Input() type: TwoFactorProviderType; @Input() organizationId: string; - @Output() onAuthed = new EventEmitter(); + @Output() onAuthed = new EventEmitter>(); secret: Verification; formPromise: Promise; diff --git a/apps/web/src/app/settings/two-factor-webauthn.component.ts b/apps/web/src/app/settings/two-factor-webauthn.component.ts index f3c946c0ceb..4180b957543 100644 --- a/apps/web/src/app/settings/two-factor-webauthn.component.ts +++ b/apps/web/src/app/settings/two-factor-webauthn.component.ts @@ -4,7 +4,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnDeleteRequest"; @@ -13,9 +13,18 @@ import { ChallengeResponse, TwoFactorWebAuthnResponse, } from "@bitwarden/common/models/response/twoFactorWebAuthnResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; import { TwoFactorBaseComponent } from "./two-factor-base.component"; +interface Key { + id: number; + name: string; + configured: boolean; + migrated?: boolean; + removePromise: Promise | null; +} + @Component({ selector: "app-two-factor-webauthn", templateUrl: "two-factor-webauthn.component.html", @@ -23,14 +32,14 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component"; export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { type = TwoFactorProviderType.WebAuthn; name: string; - keys: any[]; + keys: Key[]; keyIdAvailable: number = null; keysConfiguredCount = 0; webAuthnError: boolean; webAuthnListening: boolean; webAuthnResponse: PublicKeyCredential; challengePromise: Promise; - formPromise: Promise; + formPromise: Promise; constructor( apiService: ApiService, @@ -43,7 +52,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { super(apiService, i18nService, platformUtilsService, logService, userVerificationService); } - auth(authResponse: any) { + auth(authResponse: AuthResponse) { super.auth(authResponse); this.processResponse(authResponse.response); } @@ -69,11 +78,11 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { return super.disable(this.formPromise); } - async remove(key: any) { + async remove(key: Key) { if (this.keysConfiguredCount <= 1 || key.removePromise != null) { return; } - const name = key.name != null ? key.name : this.i18nService.t("webAuthnkeyX", key.id); + const name = key.name != null ? key.name : this.i18nService.t("webAuthnkeyX", key.id as any); const confirmed = await this.platformUtilsService.showDialog( this.i18nService.t("removeU2fConfirmation"), name, diff --git a/apps/web/src/app/settings/two-factor-yubikey.component.ts b/apps/web/src/app/settings/two-factor-yubikey.component.ts index 23dba61e5e9..a6f75af1f88 100644 --- a/apps/web/src/app/settings/two-factor-yubikey.component.ts +++ b/apps/web/src/app/settings/two-factor-yubikey.component.ts @@ -4,24 +4,30 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/models/request/updateTwoFactorYubioOtpRequest"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/models/response/twoFactorYubiKeyResponse"; +import { AuthResponse } from "@bitwarden/common/types/authResponse"; import { TwoFactorBaseComponent } from "./two-factor-base.component"; +interface Key { + key: string; + existingKey: string; +} + @Component({ selector: "app-two-factor-yubikey", templateUrl: "two-factor-yubikey.component.html", }) export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent { type = TwoFactorProviderType.Yubikey; - keys: any[]; + keys: Key[]; nfc = false; - formPromise: Promise; - disablePromise: Promise; + formPromise: Promise; + disablePromise: Promise; constructor( apiService: ApiService, @@ -33,7 +39,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent { super(apiService, i18nService, platformUtilsService, logService, userVerificationService); } - auth(authResponse: any) { + auth(authResponse: AuthResponse) { super.auth(authResponse); this.processResponse(authResponse.response); } @@ -59,7 +65,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent { return super.disable(this.disablePromise); } - remove(key: any) { + remove(key: Key) { key.existingKey = null; key.key = null; } diff --git a/apps/web/src/app/tools/import-export/export.component.ts b/apps/web/src/app/tools/import-export/export.component.ts index 100f63015e5..b9d8d963b65 100644 --- a/apps/web/src/app/tools/import-export/export.component.ts +++ b/apps/web/src/app/tools/import-export/export.component.ts @@ -10,7 +10,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; @Component({ selector: "app-export", diff --git a/libs/angular/src/components/export.component.ts b/libs/angular/src/components/export.component.ts index c2a6187f443..d053b886d3e 100644 --- a/libs/angular/src/components/export.component.ts +++ b/libs/angular/src/components/export.component.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { EventType } from "@bitwarden/common/enums/eventType"; import { PolicyType } from "@bitwarden/common/enums/policyType"; @@ -142,7 +142,7 @@ export class ExportComponent implements OnInit { return this.exportService.getFileName(prefix, extension); } - protected async collectEvent(): Promise { + protected async collectEvent(): Promise { await this.eventService.collect(EventType.User_ClientExportedVault); } diff --git a/libs/angular/src/components/update-password.component.ts b/libs/angular/src/components/update-password.component.ts index bc6bbba9a7c..c2f076e1079 100644 --- a/libs/angular/src/components/update-password.component.ts +++ b/libs/angular/src/components/update-password.component.ts @@ -10,7 +10,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { EncString } from "@bitwarden/common/models/domain/encString"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions"; @@ -28,7 +28,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { showPassword = false; currentMasterPassword: string; - onSuccessfulChangePassword: () => Promise; + onSuccessfulChangePassword: () => Promise; constructor( protected router: Router, diff --git a/libs/angular/src/components/user-verification.component.ts b/libs/angular/src/components/user-verification.component.ts index 2a486db01a7..04af868b34e 100644 --- a/libs/angular/src/components/user-verification.component.ts +++ b/libs/angular/src/components/user-verification.component.ts @@ -1,9 +1,9 @@ import { animate, style, transition, trigger } from "@angular/animations"; import { Component, OnInit } from "@angular/core"; -import { ControlValueAccessor, UntypedFormControl, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from "@angular/forms"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { Utils } from "@bitwarden/common/misc/utils"; import { Verification } from "@bitwarden/common/types/verification"; @@ -35,7 +35,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit { disableRequestOTP = false; sentCode = false; - secret = new UntypedFormControl(""); + secret = new FormControl(""); private onChange: (value: Verification) => void; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 529b3b52faa..729f876d3d6 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -49,7 +49,8 @@ import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstrac import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.service"; -import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction"; +import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; @@ -89,7 +90,8 @@ import { SyncService } from "@bitwarden/common/services/sync.service"; import { TokenService } from "@bitwarden/common/services/token.service"; import { TotpService } from "@bitwarden/common/services/totp.service"; import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service"; -import { UserVerificationService } from "@bitwarden/common/services/userVerification.service"; +import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service"; +import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service"; import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; @@ -476,7 +478,11 @@ export const LOG_MAC_FAILURES = new InjectionToken("LOG_MAC_FAILURES"); { provide: UserVerificationServiceAbstraction, useClass: UserVerificationService, - deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction], + deps: [ + CryptoServiceAbstraction, + I18nServiceAbstraction, + UserVerificationApiServiceAbstraction, + ], }, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, { @@ -502,6 +508,11 @@ export const LOG_MAC_FAILURES = new InjectionToken("LOG_MAC_FAILURES"); provide: FormValidationErrorsServiceAbstraction, useClass: FormValidationErrorsService, }, + { + provide: UserVerificationApiServiceAbstraction, + useClass: UserVerificationApiService, + deps: [ApiServiceAbstraction], + }, ], }) export class JslibServicesModule {} diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 8599309836f..d5267fa4259 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,7 +1,6 @@ import { OrganizationApiKeyType } from "../enums/organizationApiKeyType"; import { OrganizationConnectionType } from "../enums/organizationConnectionType"; import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; -import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; import { AttachmentRequest } from "../models/request/attachmentRequest"; import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; @@ -228,8 +227,6 @@ export abstract class ApiService { postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise; postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; - postAccountRequestOTP: () => Promise; - postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise; postConvertToKeyConnector: () => Promise; getUserBillingHistory: () => Promise; diff --git a/libs/common/src/abstractions/userVerification/userVerification-api.service.abstraction.ts b/libs/common/src/abstractions/userVerification/userVerification-api.service.abstraction.ts new file mode 100644 index 00000000000..c54077ef184 --- /dev/null +++ b/libs/common/src/abstractions/userVerification/userVerification-api.service.abstraction.ts @@ -0,0 +1,6 @@ +import { VerifyOTPRequest } from "@bitwarden/common/models/request/account/verifyOTPRequest"; + +export abstract class UserVerificationApiServiceAbstraction { + postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise; + postAccountRequestOTP: () => Promise; +} diff --git a/libs/common/src/abstractions/userVerification.service.ts b/libs/common/src/abstractions/userVerification/userVerification.service.abstraction.ts similarity index 67% rename from libs/common/src/abstractions/userVerification.service.ts rename to libs/common/src/abstractions/userVerification/userVerification.service.abstraction.ts index ebd7d1f334f..3d53063e454 100644 --- a/libs/common/src/abstractions/userVerification.service.ts +++ b/libs/common/src/abstractions/userVerification/userVerification.service.abstraction.ts @@ -1,5 +1,5 @@ -import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -import { Verification } from "../types/verification"; +import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest"; +import { Verification } from "../../types/verification"; export abstract class UserVerificationService { buildRequest: ( diff --git a/libs/common/src/services/account/account.service.ts b/libs/common/src/services/account/account.service.ts index 0870003414b..2f3bacfbe1a 100644 --- a/libs/common/src/services/account/account.service.ts +++ b/libs/common/src/services/account/account.service.ts @@ -1,7 +1,7 @@ import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; -import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { AccountService as AccountServiceAbstraction } from "../../abstractions/account/account.service.abstraction"; import { Verification } from "../../types/verification"; @@ -14,7 +14,7 @@ export class AccountService implements AccountServiceAbstraction { private logService: LogService ) {} - async delete(verification: Verification): Promise { + async delete(verification: Verification): Promise { try { const verificationRequest = await this.userVerificationService.buildRequest(verification); await this.accountApiService.deleteAccount(verificationRequest); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index a68f06e97ee..5573e644c44 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -8,7 +8,6 @@ import { OrganizationApiKeyType } from "../enums/organizationApiKeyType"; import { OrganizationConnectionType } from "../enums/organizationConnectionType"; import { Utils } from "../misc/utils"; import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; -import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; import { AttachmentRequest } from "../models/request/attachmentRequest"; import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; @@ -457,14 +456,6 @@ export class ApiService implements ApiServiceAbstraction { return this.send("PUT", "/accounts/update-temp-password", request, true, false); } - postAccountRequestOTP(): Promise { - return this.send("POST", "/accounts/request-otp", null, true, false); - } - - postAccountVerifyOTP(request: VerifyOTPRequest): Promise { - return this.send("POST", "/accounts/verify-otp", request, true, false); - } - postConvertToKeyConnector(): Promise { return this.send("POST", "/accounts/convert-to-key-connector", null, true, false); } diff --git a/libs/common/src/services/userVerification/userVerification-api.service.ts b/libs/common/src/services/userVerification/userVerification-api.service.ts new file mode 100644 index 00000000000..26d50913547 --- /dev/null +++ b/libs/common/src/services/userVerification/userVerification-api.service.ts @@ -0,0 +1,14 @@ +import { ApiService } from "../../abstractions/api.service"; +import { UserVerificationApiServiceAbstraction } from "../../abstractions/userVerification/userVerification-api.service.abstraction"; +import { VerifyOTPRequest } from "../../models/request/account/verifyOTPRequest"; + +export class UserVerificationApiService implements UserVerificationApiServiceAbstraction { + constructor(private apiService: ApiService) {} + + postAccountVerifyOTP(request: VerifyOTPRequest): Promise { + return this.apiService.send("POST", "/accounts/verify-otp", request, true, false); + } + async postAccountRequestOTP(): Promise { + return this.apiService.send("POST", "/accounts/request-otp", null, true, false); + } +} diff --git a/libs/common/src/services/userVerification.service.ts b/libs/common/src/services/userVerification/userVerification.service.ts similarity index 74% rename from libs/common/src/services/userVerification.service.ts rename to libs/common/src/services/userVerification/userVerification.service.ts index 65207ac0a9a..f58515d9a40 100644 --- a/libs/common/src/services/userVerification.service.ts +++ b/libs/common/src/services/userVerification/userVerification.service.ts @@ -1,11 +1,11 @@ -import { ApiService } from "../abstractions/api.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { UserVerificationService as UserVerificationServiceAbstraction } from "../abstractions/userVerification.service"; -import { VerificationType } from "../enums/verificationType"; -import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; -import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -import { Verification } from "../types/verification"; +import { CryptoService } from "../../abstractions/crypto.service"; +import { I18nService } from "../../abstractions/i18n.service"; +import { UserVerificationApiServiceAbstraction } from "../../abstractions/userVerification/userVerification-api.service.abstraction"; +import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/userVerification/userVerification.service.abstraction"; +import { VerificationType } from "../../enums/verificationType"; +import { VerifyOTPRequest } from "../../models/request/account/verifyOTPRequest"; +import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest"; +import { Verification } from "../../types/verification"; /** * Used for general-purpose user verification throughout the app. @@ -15,7 +15,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti constructor( private cryptoService: CryptoService, private i18nService: I18nService, - private apiService: ApiService + private userVerificationApiService: UserVerificationApiServiceAbstraction ) {} /** @@ -56,7 +56,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti if (verification.type === VerificationType.OTP) { const request = new VerifyOTPRequest(verification.secret); try { - await this.apiService.postAccountVerifyOTP(request); + await this.userVerificationApiService.postAccountVerifyOTP(request); } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -73,7 +73,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti } async requestOTP() { - await this.apiService.postAccountRequestOTP(); + await this.userVerificationApiService.postAccountRequestOTP(); } private validateInput(verification: Verification) { diff --git a/libs/common/src/types/authResponse.ts b/libs/common/src/types/authResponse.ts new file mode 100644 index 00000000000..a6e7f1cb669 --- /dev/null +++ b/libs/common/src/types/authResponse.ts @@ -0,0 +1,12 @@ +import { VerificationType } from "../enums/verificationType"; + +import { TwoFactorResponse } from "./twoFactorResponse"; + +export type AuthResponseBase = { + secret: string; + verificationType: VerificationType; +}; + +export type AuthResponse = AuthResponseBase & { + response: T; +}; diff --git a/libs/common/src/types/twoFactorResponse.ts b/libs/common/src/types/twoFactorResponse.ts new file mode 100644 index 00000000000..fb9840fcc6f --- /dev/null +++ b/libs/common/src/types/twoFactorResponse.ts @@ -0,0 +1,14 @@ +import { TwoFactorAuthenticatorResponse } from "../models/response/twoFactorAuthenticatorResponse"; +import { TwoFactorDuoResponse } from "../models/response/twoFactorDuoResponse"; +import { TwoFactorEmailResponse } from "../models/response/twoFactorEmailResponse"; +import { TwoFactorRecoverResponse } from "../models/response/twoFactorRescoverResponse"; +import { TwoFactorWebAuthnResponse } from "../models/response/twoFactorWebAuthnResponse"; +import { TwoFactorYubiKeyResponse } from "../models/response/twoFactorYubiKeyResponse"; + +export type TwoFactorResponse = + | TwoFactorRecoverResponse + | TwoFactorDuoResponse + | TwoFactorEmailResponse + | TwoFactorWebAuthnResponse + | TwoFactorAuthenticatorResponse + | TwoFactorYubiKeyResponse; diff --git a/libs/electron/src/services/electronStorage.service.ts b/libs/electron/src/services/electronStorage.service.ts index e2ff9a92d0b..d6e5bc4d523 100644 --- a/libs/electron/src/services/electronStorage.service.ts +++ b/libs/electron/src/services/electronStorage.service.ts @@ -5,23 +5,48 @@ import { ipcMain } from "electron"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; +// See: https://github.com/sindresorhus/electron-store/blob/main/index.d.ts +interface ElectronStoreOptions { + defaults: unknown; + name: string; +} + +type ElectronStoreConstructor = new (options: ElectronStoreOptions) => ElectronStore; + // eslint-disable-next-line -const Store = require("electron-store"); +const Store: ElectronStoreConstructor = require("electron-store"); + +interface ElectronStore { + get: (key: string) => unknown; + set: (key: string, obj: unknown) => void; + delete: (key: string) => void; +} + +interface BaseOptions { + action: T; + key: string; +} + +interface SaveOptions extends BaseOptions<"save"> { + obj: unknown; +} + +type Options = BaseOptions<"get"> | BaseOptions<"has"> | SaveOptions | BaseOptions<"remove">; export class ElectronStorageService implements AbstractStorageService { - private store: any; + private store: ElectronStore; constructor(dir: string, defaults = {}) { if (!fs.existsSync(dir)) { NodeUtils.mkdirpSync(dir, "700"); } - const storeConfig: any = { + const storeConfig: ElectronStoreOptions = { defaults: defaults, name: "data", }; this.store = new Store(storeConfig); - ipcMain.handle("storageService", (event, options) => { + ipcMain.handle("storageService", (event, options: Options) => { switch (options.action) { case "get": return this.get(options.key); @@ -45,7 +70,7 @@ export class ElectronStorageService implements AbstractStorageService { return Promise.resolve(val != null); } - save(key: string, obj: any): Promise { + save(key: string, obj: unknown): Promise { if (obj instanceof Set) { obj = Array.from(obj); } @@ -53,7 +78,7 @@ export class ElectronStorageService implements AbstractStorageService { return Promise.resolve(); } - remove(key: string): Promise { + remove(key: string): Promise { this.store.delete(key); return Promise.resolve(); } From 0602d973582211ab3acb0f7da5aecd041f752bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 10 Aug 2022 16:25:39 +0200 Subject: [PATCH 7/7] Fix CLI publish to NPM (#3272) --- .github/workflows/release-cli.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index cacb9adfdaf..66c324bdd6e 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -287,7 +287,9 @@ jobs: artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip - name: Setup NPM - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc env: NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.cli-npm-api-key }} @@ -296,5 +298,5 @@ jobs: - name: Publish NPM if: ${{ github.event.inputs.release_type != 'Dry Run' }} - run: npm publish --access public + run: npm publish --access public --regsitry=https://registry.npmjs.org/ --userconfig=./.npmrc