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 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/_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/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/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/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/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/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/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/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/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/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/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/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/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/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/accounts/update-temp-password.component.html b/apps/web/src/app/accounts/update-temp-password.component.html index ea7dc8c3a94..57cf23ad03a 100644 --- a/apps/web/src/app/accounts/update-temp-password.component.html +++ b/apps/web/src/app/accounts/update-temp-password.component.html @@ -28,6 +28,7 @@ [password]="masterPassword" [email]="email" [showText]="true" + (passwordStrengthResult)="getStrengthResult($event)" >
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/organizations/billing/billing-sync-api-key.component.ts b/apps/web/src/app/organizations/billing/billing-sync-api-key.component.ts index dfe87ebbf7e..5b36a69e172 100644 --- a/apps/web/src/app/organizations/billing/billing-sync-api-key.component.ts +++ b/apps/web/src/app/organizations/billing/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/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; + } } 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/send/send.component.html b/apps/web/src/app/send/send.component.html index eed3cdfa568..ddf7e63d79e 100644 --- a/apps/web/src/app/send/send.component.html +++ b/apps/web/src/app/send/send.component.html @@ -189,6 +189,7 @@ {{ "loading" | i18n }} +

{{ "noSendsInList" | i18n }}

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/app/vault/ciphers.component.html b/apps/web/src/app/vault/ciphers.component.html index c9c58327b94..afd6deabab1 100644 --- a/apps/web/src/app/vault/ciphers.component.html +++ b/apps/web/src/app/vault/ciphers.component.html @@ -142,6 +142,7 @@ {{ "loading" | i18n }}
+

{{ "noItemsInList" | i18n }}