1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Vincent Salucci
2022-08-10 12:02:52 -05:00
100 changed files with 986 additions and 355 deletions

View File

@@ -287,7 +287,9 @@ jobs:
artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip
- name: Setup NPM - 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: env:
NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.cli-npm-api-key }} NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.cli-npm-api-key }}
@@ -296,5 +298,5 @@ jobs:
- name: Publish NPM - name: Publish NPM
if: ${{ github.event.inputs.release_type != 'Dry Run' }} 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

View File

@@ -1,7 +1,7 @@
{ {
"extends": "../tsconfig", "extends": "../tsconfig",
"compilerOptions": { "compilerOptions": {
"types": ["node", "jest"], "types": ["node", "jest", "chrome"],
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"], "exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],

View File

@@ -1967,6 +1967,9 @@
}, },
"ssoKeyConnectorError": { "ssoKeyConnectorError": {
"message": "Key Connector error: make sure Key Connector is available and working correctly." "message": "Key Connector error: make sure Key Connector is available and working correctly."
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Organization is disabled."

View File

@@ -1,6 +1,13 @@
import MainBackground from "./background/main.background"; import MainBackground from "./background/main.background";
import { onCommandListener } from "./listeners/onCommandListener";
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); const manifest = chrome.runtime.getManifest();
bitwardenMain.bootstrap().then(() => {
// Finished bootstrapping if (manifest.manifest_version === 3) {
}); chrome.commands.onCommand.addListener(onCommandListener);
} else {
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
bitwardenMain.bootstrap().then(() => {
// Finished bootstrapping
});
}

View File

@@ -81,6 +81,10 @@ export default class ContextMenusBackground {
} }
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
if (typeof info.menuItemId !== "string") {
return;
}
const id = info.menuItemId.split("_")[1]; const id = info.menuItemId.split("_")[1];
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {

View File

@@ -31,7 +31,8 @@ import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abs
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.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 { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; 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 { TokenService } from "@bitwarden/common/services/token.service";
import { TotpService } from "@bitwarden/common/services/totp.service"; import { TotpService } from "@bitwarden/common/services/totp.service";
import { TwoFactorService } from "@bitwarden/common/services/twoFactor.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 { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
@@ -152,6 +154,7 @@ export default class MainBackground {
encryptService: EncryptService; encryptService: EncryptService;
folderApiService: FolderApiServiceAbstraction; folderApiService: FolderApiServiceAbstraction;
policyApiService: PolicyApiServiceAbstraction; policyApiService: PolicyApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
// Passed to the popup for Safari to workaround issues with theming, downloading, etc. // Passed to the popup for Safari to workaround issues with theming, downloading, etc.
backgroundWindow = window; backgroundWindow = window;
@@ -422,10 +425,12 @@ export default class MainBackground {
); );
this.popupUtilsService = new PopupUtilsService(isPrivateMode); this.popupUtilsService = new PopupUtilsService(isPrivateMode);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
this.cryptoService, this.cryptoService,
this.i18nService, this.i18nService,
this.apiService this.userVerificationApiService
); );
const systemUtilsServiceReloadCallback = () => { const systemUtilsServiceReloadCallback = () => {

View File

@@ -14,7 +14,10 @@ export default class WebRequestBackground {
private cipherService: CipherService, private cipherService: CipherService,
private authService: AuthService 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(); this.isFirefox = platformUtilsService.isFirefox();
} }

View File

@@ -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<AutofillPageDetails> {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(
tabId,
{
command: "collectPageDetailsImmediately",
},
(response: AutofillPageDetails) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
return;
}
resolve(response);
}
);
});
}
}

View File

@@ -39,6 +39,7 @@
6. Rename com.agilebits.* stuff to com.bitwarden.* 6. Rename com.agilebits.* stuff to com.bitwarden.*
7. Remove "some useful globals" on window 7. Remove "some useful globals" on window
8. Add ability to autofill span[data-bwautofill] elements 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) { function collect(document, undefined) {
@@ -1037,6 +1038,11 @@
fill(document, msg.fillScript); fill(document, msg.fillScript);
sendResponse(); sendResponse();
return true; return true;
} else if (msg.command === 'collectPageDetailsImmediately') {
var pageDetails = collect(document);
var pageDetailsObj = JSON.parse(pageDetails);
sendResponse(pageDetailsObj);
return true;
} }
}); });
})(); })();

View File

@@ -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<void> => {
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);
};

View File

@@ -47,7 +47,8 @@
} }
], ],
"background": { "background": {
"service_worker": "background.js" "service_worker": "background.js",
"type": "module"
}, },
"action": { "action": {
"default_icon": { "default_icon": {

View File

@@ -22,4 +22,5 @@ export default class AutofillField {
selectInfo: any; selectInfo: any;
maxLength: number; maxLength: number;
tagName: string; tagName: string;
[key: string]: any;
} }

View File

@@ -56,7 +56,7 @@ export class AppComponent implements OnInit, OnDestroy {
// Clear them aggressively to make sure this doesn't occur // Clear them aggressively to make sure this doesn't occur
await this.clearComponentStates(); await this.clearComponentStates();
this.stateService.activeAccount.pipe(takeUntil(this.destroy$)).subscribe((userId) => { this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
this.activeUserId = 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"]); this.router.navigate(["home"]);
} }
}); });

View File

@@ -45,7 +45,7 @@ import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.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 { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { AuthService } from "@bitwarden/common/services/auth.service"; import { AuthService } from "@bitwarden/common/services/auth.service";

View File

@@ -11,7 +11,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; 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({ @Component({
selector: "app-export", selector: "app-export",

View File

@@ -1,6 +1,6 @@
<header> <header>
<div class="left"> <div class="left">
<button type="button" routerLink="/tabs/settings"> <button type="button" (click)="goBack()">
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span> <span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
<span>{{ "back" | i18n }}</span> <span>{{ "back" | i18n }}</span>
</button> </button>

View File

@@ -1,4 +1,4 @@
import { CurrencyPipe } from "@angular/common"; import { CurrencyPipe, Location } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/components/premium.component"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/components/premium.component";
@@ -21,6 +21,7 @@ export class PremiumComponent extends BasePremiumComponent {
apiService: ApiService, apiService: ApiService,
stateService: StateService, stateService: StateService,
logService: LogService, logService: LogService,
private location: Location,
private currencyPipe: CurrencyPipe private currencyPipe: CurrencyPipe
) { ) {
super(i18nService, platformUtilsService, apiService, logService, stateService); super(i18nService, platformUtilsService, apiService, logService, stateService);
@@ -32,4 +33,8 @@ export class PremiumComponent extends BasePremiumComponent {
this.priceString = this.priceString.replace("%price%", thePrice); this.priceString = this.priceString.replace("%price%", thePrice);
} }
} }
goBack() {
this.location.back();
}
} }

View File

@@ -84,6 +84,7 @@
<div class="no-items"> <div class="no-items">
<i class="bwi bwi-spinner bwi-spin bwi-3x" *ngIf="!loaded" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin bwi-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<img class="no-items-image" aria-hidden="true" />
<p>{{ "noItemsInList" | i18n }}</p> <p>{{ "noItemsInList" | i18n }}</p>
<button type="button" (click)="addCipher()" class="btn block primary link"> <button type="button" (click)="addCipher()" class="btn block primary link">
{{ "addItem" | i18n }} {{ "addItem" | i18n }}

View File

@@ -139,7 +139,7 @@
<div <div
class="box-content-row box-content-row-flex totp" class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }" [ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode" *ngIf="cipher.login.totp && totpCode && canAccessPremium"
> >
<div class="row-main"> <div class="row-main">
<span <span
@@ -177,6 +177,20 @@
</button> </button>
</div> </div>
</div> </div>
<div
class="box-content-row box-content-row-flex totp"
*ngIf="cipher.login.totp && !canAccessPremium"
>
<div class="row-main">
<span class="row-label">{{ "verificationCodeTotp" | i18n }}</span>
<span class="row-label">
<a routerLink="/premium">
{{ "premiumSubcriptionRequired" | i18n }}
</a>
</span>
</div>
</div>
</div> </div>
<!-- Card --> <!-- Card -->
<div *ngIf="cipher.card"> <div *ngIf="cipher.card">

View File

@@ -1,7 +1,7 @@
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
export default abstract class AbstractChromeStorageService implements AbstractStorageService { export default abstract class AbstractChromeStorageService implements AbstractStorageService {
protected abstract chromeStorageApi: any; protected abstract chromeStorageApi: chrome.storage.StorageArea;
async get<T>(key: string): Promise<T> { async get<T>(key: string): Promise<T> {
return new Promise((resolve) => { return new Promise((resolve) => {

View File

@@ -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"; import AutofillPageDetails from "../../models/autofillPageDetails";
export abstract class AutofillService { export interface PageDetail {
getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[]; frameId: number;
doAutoFill: (options: any) => Promise<string>; tab: chrome.tabs.Tab;
doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise<string>; 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<string>;
doAutoFillOnTab: (
pageDetails: PageDetail[],
tab: chrome.tabs.Tab,
fromCommand: boolean
) => Promise<string>;
doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise<string>;
} }

View File

@@ -15,13 +15,26 @@ import AutofillPageDetails from "../models/autofillPageDetails";
import AutofillScript from "../models/autofillScript"; import AutofillScript from "../models/autofillScript";
import { StateService } from "../services/abstractions/state.service"; 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 { import {
AutoFillConstants, AutoFillConstants,
CreditCardAutoFillConstants, CreditCardAutoFillConstants,
IdentityAutoFillConstants, IdentityAutoFillConstants,
} from "./autofillConstants"; } from "./autofillConstants";
export interface GenerateFillScriptOptions {
skipUsernameOnlyFill: boolean;
onlyEmptyFields: boolean;
onlyVisibleFields: boolean;
fillNewPassword: boolean;
cipher: CipherView;
}
export default class AutofillService implements AutofillServiceInterface { export default class AutofillService implements AutofillServiceInterface {
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
@@ -31,10 +44,16 @@ export default class AutofillService implements AutofillServiceInterface {
private logService: LogService private logService: LogService
) {} ) {}
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): any[] { getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
const formData: any[] = []; 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) { if (passwordFields.length === 0) {
return formData; return formData;
} }
@@ -64,16 +83,17 @@ export default class AutofillService implements AutofillServiceInterface {
return formData; return formData;
} }
async doAutoFill(options: any) { async doAutoFill(options: AutoFillOptions) {
let totpPromise: Promise<string> = null;
const tab = options.tab; const tab = options.tab;
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) { if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
throw new Error("Nothing to auto-fill."); throw new Error("Nothing to auto-fill.");
} }
let totpPromise: Promise<string> = null;
const canAccessPremium = await this.stateService.getCanAccessPremium(); const canAccessPremium = await this.stateService.getCanAccessPremium();
let didAutofill = false; let didAutofill = false;
options.pageDetails.forEach((pd: any) => { options.pageDetails.forEach((pd) => {
// make sure we're still on correct tab // make sure we're still on correct tab
if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) { if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) {
return; return;
@@ -138,12 +158,7 @@ export default class AutofillService implements AutofillServiceInterface {
} }
} }
async doAutoFillActiveTab(pageDetails: any, fromCommand: boolean) { async doAutoFillOnTab(pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean) {
const tab = await this.getActiveTab();
if (!tab || !tab.url) {
return;
}
let cipher: CipherView; let cipher: CipherView;
if (fromCommand) { if (fromCommand) {
cipher = await this.cipherService.getNextCipherForUrl(tab.url); cipher = await this.cipherService.getNextCipherForUrl(tab.url);
@@ -186,9 +201,18 @@ export default class AutofillService implements AutofillServiceInterface {
return totpCode; 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 // Helpers
private async getActiveTab(): Promise<any> { private async getActiveTab(): Promise<chrome.tabs.Tab> {
const tab = await BrowserApi.getTabFromCurrentWindow(); const tab = await BrowserApi.getTabFromCurrentWindow();
if (!tab) { if (!tab) {
throw new Error("No tab found."); throw new Error("No tab found.");
@@ -197,7 +221,10 @@ export default class AutofillService implements AutofillServiceInterface {
return tab; return tab;
} }
private generateFillScript(pageDetails: AutofillPageDetails, options: any): AutofillScript { private generateFillScript(
pageDetails: AutofillPageDetails,
options: GenerateFillScriptOptions
): AutofillScript {
if (!pageDetails || !options.cipher) { if (!pageDetails || !options.cipher) {
return null; return null;
} }
@@ -209,13 +236,13 @@ export default class AutofillService implements AutofillServiceInterface {
if (fields && fields.length) { if (fields && fields.length) {
const fieldNames: string[] = []; const fieldNames: string[] = [];
fields.forEach((f: any) => { fields.forEach((f) => {
if (this.hasValue(f.name)) { if (AutofillService.hasValue(f.name)) {
fieldNames.push(f.name.toLowerCase()); fieldNames.push(f.name.toLowerCase());
} }
}); });
pageDetails.fields.forEach((field: any) => { pageDetails.fields.forEach((field) => {
// eslint-disable-next-line // eslint-disable-next-line
if (filledFields.hasOwnProperty(field.opid)) { if (filledFields.hasOwnProperty(field.opid)) {
return; return;
@@ -228,10 +255,10 @@ export default class AutofillService implements AutofillServiceInterface {
const matchingIndex = this.findMatchingFieldIndex(field, fieldNames); const matchingIndex = this.findMatchingFieldIndex(field, fieldNames);
if (matchingIndex > -1) { if (matchingIndex > -1) {
const matchingField: FieldView = fields[matchingIndex]; const matchingField: FieldView = fields[matchingIndex];
let val; let val: string;
if (matchingField.type === FieldType.Linked) { if (matchingField.type === FieldType.Linked) {
// Assumption: Linked Field is not being used to autofill a boolean value // 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 { } else {
val = matchingField.value; val = matchingField.value;
if (val == null && matchingField.type === FieldType.Boolean) { if (val == null && matchingField.type === FieldType.Boolean) {
@@ -240,7 +267,7 @@ export default class AutofillService implements AutofillServiceInterface {
} }
filledFields[field.opid] = field; 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( private generateLoginFillScript(
fillScript: AutofillScript, fillScript: AutofillScript,
pageDetails: any, pageDetails: AutofillPageDetails,
filledFields: { [id: string]: AutofillField }, filledFields: { [id: string]: AutofillField },
options: any options: GenerateFillScriptOptions
): AutofillScript { ): AutofillScript {
if (!options.cipher.login) { if (!options.cipher.login) {
return null; return null;
@@ -285,11 +312,11 @@ export default class AutofillService implements AutofillServiceInterface {
if (!login.password || login.password === "") { if (!login.password || login.password === "") {
// No password for this login. Maybe they just wanted to auto-fill some custom fields? // 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; return fillScript;
} }
let passwordFields = this.loadPasswordFields( let passwordFields = AutofillService.loadPasswordFields(
pageDetails, pageDetails,
false, false,
false, false,
@@ -298,7 +325,7 @@ export default class AutofillService implements AutofillServiceInterface {
); );
if (!passwordFields.length && !options.onlyVisibleFields) { if (!passwordFields.length && !options.onlyVisibleFields) {
// not able to find any viewable password fields. maybe there are some "hidden" ones? // not able to find any viewable password fields. maybe there are some "hidden" ones?
passwordFields = this.loadPasswordFields( passwordFields = AutofillService.loadPasswordFields(
pageDetails, pageDetails,
true, true,
true, true,
@@ -362,11 +389,11 @@ export default class AutofillService implements AutofillServiceInterface {
if (!passwordFields.length && !options.skipUsernameOnlyFill) { if (!passwordFields.length && !options.skipUsernameOnlyFill) {
// No password fields on this page. Let's try to just fuzzy fill the username. // 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 ( if (
f.viewable && f.viewable &&
(f.type === "text" || f.type === "email" || f.type === "tel") && (f.type === "text" || f.type === "email" || f.type === "tel") &&
this.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames) AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
) { ) {
usernames.push(f); usernames.push(f);
} }
@@ -380,7 +407,7 @@ export default class AutofillService implements AutofillServiceInterface {
} }
filledFields[u.opid] = u; filledFields[u.opid] = u;
this.fillByOpid(fillScript, u, login.username); AutofillService.fillByOpid(fillScript, u, login.username);
}); });
passwords.forEach((p) => { passwords.forEach((p) => {
@@ -390,18 +417,18 @@ export default class AutofillService implements AutofillServiceInterface {
} }
filledFields[p.opid] = p; 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; return fillScript;
} }
private generateCardFillScript( private generateCardFillScript(
fillScript: AutofillScript, fillScript: AutofillScript,
pageDetails: any, pageDetails: AutofillPageDetails,
filledFields: { [id: string]: AutofillField }, filledFields: { [id: string]: AutofillField },
options: any options: GenerateFillScriptOptions
): AutofillScript { ): AutofillScript {
if (!options.cipher.card) { if (!options.cipher.card) {
return null; return null;
@@ -409,8 +436,8 @@ export default class AutofillService implements AutofillServiceInterface {
const fillFields: { [id: string]: AutofillField } = {}; const fillFields: { [id: string]: AutofillField } = {};
pageDetails.fields.forEach((f: any) => { pageDetails.fields.forEach((f) => {
if (this.forCustomFieldsOnly(f)) { if (AutofillService.forCustomFieldsOnly(f)) {
return; return;
} }
@@ -429,7 +456,7 @@ export default class AutofillService implements AutofillServiceInterface {
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
if ( if (
!fillFields.cardholderName && !fillFields.cardholderName &&
this.isFieldMatch( AutofillService.isFieldMatch(
f[attr], f[attr],
CreditCardAutoFillConstants.CardHolderFieldNames, CreditCardAutoFillConstants.CardHolderFieldNames,
CreditCardAutoFillConstants.CardHolderFieldNameValues CreditCardAutoFillConstants.CardHolderFieldNameValues
@@ -439,7 +466,7 @@ export default class AutofillService implements AutofillServiceInterface {
break; break;
} else if ( } else if (
!fillFields.number && !fillFields.number &&
this.isFieldMatch( AutofillService.isFieldMatch(
f[attr], f[attr],
CreditCardAutoFillConstants.CardNumberFieldNames, CreditCardAutoFillConstants.CardNumberFieldNames,
CreditCardAutoFillConstants.CardNumberFieldNameValues CreditCardAutoFillConstants.CardNumberFieldNameValues
@@ -449,7 +476,7 @@ export default class AutofillService implements AutofillServiceInterface {
break; break;
} else if ( } else if (
!fillFields.exp && !fillFields.exp &&
this.isFieldMatch( AutofillService.isFieldMatch(
f[attr], f[attr],
CreditCardAutoFillConstants.CardExpiryFieldNames, CreditCardAutoFillConstants.CardExpiryFieldNames,
CreditCardAutoFillConstants.CardExpiryFieldNameValues CreditCardAutoFillConstants.CardExpiryFieldNameValues
@@ -459,25 +486,25 @@ export default class AutofillService implements AutofillServiceInterface {
break; break;
} else if ( } else if (
!fillFields.expMonth && !fillFields.expMonth &&
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames) AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
) { ) {
fillFields.expMonth = f; fillFields.expMonth = f;
break; break;
} else if ( } else if (
!fillFields.expYear && !fillFields.expYear &&
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames) AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
) { ) {
fillFields.expYear = f; fillFields.expYear = f;
break; break;
} else if ( } else if (
!fillFields.code && !fillFields.code &&
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames) AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
) { ) {
fillFields.code = f; fillFields.code = f;
break; break;
} else if ( } else if (
!fillFields.brand && !fillFields.brand &&
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames) AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
) { ) {
fillFields.brand = f; fillFields.brand = f;
break; break;
@@ -491,7 +518,7 @@ export default class AutofillService implements AutofillServiceInterface {
this.makeScriptAction(fillScript, card, fillFields, filledFields, "code"); this.makeScriptAction(fillScript, card, fillFields, filledFields, "code");
this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand"); 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; let expMonth: string = card.expMonth;
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) { if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
@@ -526,10 +553,10 @@ export default class AutofillService implements AutofillServiceInterface {
} }
filledFields[fillFields.expMonth.opid] = fillFields.expMonth; 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; let expYear: string = card.expYear;
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) { if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) { 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; 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); const fullMonth = ("0" + card.expMonth).slice(-2);
let fullYear: string = card.expYear; let fullYear: string = card.expYear;
@@ -712,7 +743,7 @@ export default class AutofillService implements AutofillServiceInterface {
return fillScript; return fillScript;
} }
private fieldAttrsContain(field: any, containsVal: string) { private fieldAttrsContain(field: AutofillField, containsVal: string) {
if (!field) { if (!field) {
return false; return false;
} }
@@ -734,9 +765,9 @@ export default class AutofillService implements AutofillServiceInterface {
private generateIdentityFillScript( private generateIdentityFillScript(
fillScript: AutofillScript, fillScript: AutofillScript,
pageDetails: any, pageDetails: AutofillPageDetails,
filledFields: { [id: string]: AutofillField }, filledFields: { [id: string]: AutofillField },
options: any options: GenerateFillScriptOptions
): AutofillScript { ): AutofillScript {
if (!options.cipher.identity) { if (!options.cipher.identity) {
return null; return null;
@@ -744,8 +775,8 @@ export default class AutofillService implements AutofillServiceInterface {
const fillFields: { [id: string]: AutofillField } = {}; const fillFields: { [id: string]: AutofillField } = {};
pageDetails.fields.forEach((f: any) => { pageDetails.fields.forEach((f) => {
if (this.forCustomFieldsOnly(f)) { if (AutofillService.forCustomFieldsOnly(f)) {
return; return;
} }
@@ -764,7 +795,7 @@ export default class AutofillService implements AutofillServiceInterface {
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
if ( if (
!fillFields.name && !fillFields.name &&
this.isFieldMatch( AutofillService.isFieldMatch(
f[attr], f[attr],
IdentityAutoFillConstants.FullNameFieldNames, IdentityAutoFillConstants.FullNameFieldNames,
IdentityAutoFillConstants.FullNameFieldNameValues IdentityAutoFillConstants.FullNameFieldNameValues
@@ -774,37 +805,37 @@ export default class AutofillService implements AutofillServiceInterface {
break; break;
} else if ( } else if (
!fillFields.firstName && !fillFields.firstName &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
) { ) {
fillFields.firstName = f; fillFields.firstName = f;
break; break;
} else if ( } else if (
!fillFields.middleName && !fillFields.middleName &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
) { ) {
fillFields.middleName = f; fillFields.middleName = f;
break; break;
} else if ( } else if (
!fillFields.lastName && !fillFields.lastName &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
) { ) {
fillFields.lastName = f; fillFields.lastName = f;
break; break;
} else if ( } else if (
!fillFields.title && !fillFields.title &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
) { ) {
fillFields.title = f; fillFields.title = f;
break; break;
} else if ( } else if (
!fillFields.email && !fillFields.email &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
) { ) {
fillFields.email = f; fillFields.email = f;
break; break;
} else if ( } else if (
!fillFields.address && !fillFields.address &&
this.isFieldMatch( AutofillService.isFieldMatch(
f[attr], f[attr],
IdentityAutoFillConstants.AddressFieldNames, IdentityAutoFillConstants.AddressFieldNames,
IdentityAutoFillConstants.AddressFieldNameValues IdentityAutoFillConstants.AddressFieldNameValues
@@ -814,61 +845,61 @@ export default class AutofillService implements AutofillServiceInterface {
break; break;
} else if ( } else if (
!fillFields.address1 && !fillFields.address1 &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
) { ) {
fillFields.address1 = f; fillFields.address1 = f;
break; break;
} else if ( } else if (
!fillFields.address2 && !fillFields.address2 &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
) { ) {
fillFields.address2 = f; fillFields.address2 = f;
break; break;
} else if ( } else if (
!fillFields.address3 && !fillFields.address3 &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
) { ) {
fillFields.address3 = f; fillFields.address3 = f;
break; break;
} else if ( } else if (
!fillFields.postalCode && !fillFields.postalCode &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
) { ) {
fillFields.postalCode = f; fillFields.postalCode = f;
break; break;
} else if ( } else if (
!fillFields.city && !fillFields.city &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
) { ) {
fillFields.city = f; fillFields.city = f;
break; break;
} else if ( } else if (
!fillFields.state && !fillFields.state &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
) { ) {
fillFields.state = f; fillFields.state = f;
break; break;
} else if ( } else if (
!fillFields.country && !fillFields.country &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
) { ) {
fillFields.country = f; fillFields.country = f;
break; break;
} else if ( } else if (
!fillFields.phone && !fillFields.phone &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
) { ) {
fillFields.phone = f; fillFields.phone = f;
break; break;
} else if ( } else if (
!fillFields.username && !fillFields.username &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
) { ) {
fillFields.username = f; fillFields.username = f;
break; break;
} else if ( } else if (
!fillFields.company && !fillFields.company &&
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
) { ) {
fillFields.company = f; fillFields.company = f;
break; break;
@@ -923,16 +954,16 @@ export default class AutofillService implements AutofillServiceInterface {
if (fillFields.name && (identity.firstName || identity.lastName)) { if (fillFields.name && (identity.firstName || identity.lastName)) {
let fullName = ""; let fullName = "";
if (this.hasValue(identity.firstName)) { if (AutofillService.hasValue(identity.firstName)) {
fullName = identity.firstName; fullName = identity.firstName;
} }
if (this.hasValue(identity.middleName)) { if (AutofillService.hasValue(identity.middleName)) {
if (fullName !== "") { if (fullName !== "") {
fullName += " "; fullName += " ";
} }
fullName += identity.middleName; fullName += identity.middleName;
} }
if (this.hasValue(identity.lastName)) { if (AutofillService.hasValue(identity.lastName)) {
if (fullName !== "") { if (fullName !== "") {
fullName += " "; fullName += " ";
} }
@@ -942,18 +973,18 @@ export default class AutofillService implements AutofillServiceInterface {
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
} }
if (fillFields.address && this.hasValue(identity.address1)) { if (fillFields.address && AutofillService.hasValue(identity.address1)) {
let address = ""; let address = "";
if (this.hasValue(identity.address1)) { if (AutofillService.hasValue(identity.address1)) {
address = identity.address1; address = identity.address1;
} }
if (this.hasValue(identity.address2)) { if (AutofillService.hasValue(identity.address2)) {
if (address !== "") { if (address !== "") {
address += ", "; address += ", ";
} }
address += identity.address2; address += identity.address2;
} }
if (this.hasValue(identity.address3)) { if (AutofillService.hasValue(identity.address3)) {
if (address !== "") { if (address !== "") {
address += ", "; address += ", ";
} }
@@ -970,7 +1001,11 @@ export default class AutofillService implements AutofillServiceInterface {
return excludedTypes.indexOf(type) > -1; 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 value = value
.trim() .trim()
.toLowerCase() .toLowerCase()
@@ -1011,12 +1046,15 @@ export default class AutofillService implements AutofillServiceInterface {
filledFields: { [id: string]: AutofillField } filledFields: { [id: string]: AutofillField }
) { ) {
let doFill = false; let doFill = false;
if (this.hasValue(dataValue) && field) { if (AutofillService.hasValue(dataValue) && field) {
if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) { if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) {
for (let i = 0; i < field.selectInfo.options.length; i++) { for (let i = 0; i < field.selectInfo.options.length; i++) {
const option = field.selectInfo.options[i]; const option = field.selectInfo.options[i];
for (let j = 0; j < option.length; j++) { 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; doFill = true;
if (option.length > 1) { if (option.length > 1) {
dataValue = option[1]; dataValue = option[1];
@@ -1036,11 +1074,11 @@ export default class AutofillService implements AutofillServiceInterface {
if (doFill) { if (doFill) {
filledFields[field.opid] = field; filledFields[field.opid] = field;
this.fillByOpid(fillScript, field, dataValue); AutofillService.fillByOpid(fillScript, field, dataValue);
} }
} }
private loadPasswordFields( static loadPasswordFields(
pageDetails: AutofillPageDetails, pageDetails: AutofillPageDetails,
canBeHidden: boolean, canBeHidden: boolean,
canBeReadOnly: boolean, canBeReadOnly: boolean,
@@ -1049,7 +1087,7 @@ export default class AutofillService implements AutofillServiceInterface {
) { ) {
const arr: AutofillField[] = []; const arr: AutofillField[] = [];
pageDetails.fields.forEach((f) => { pageDetails.fields.forEach((f) => {
if (this.forCustomFieldsOnly(f)) { if (AutofillService.forCustomFieldsOnly(f)) {
return; return;
} }
@@ -1111,7 +1149,7 @@ export default class AutofillService implements AutofillServiceInterface {
let usernameField: AutofillField = null; let usernameField: AutofillField = null;
for (let i = 0; i < pageDetails.fields.length; i++) { for (let i = 0; i < pageDetails.fields.length; i++) {
const f = pageDetails.fields[i]; const f = pageDetails.fields[i];
if (this.forCustomFieldsOnly(f)) { if (AutofillService.forCustomFieldsOnly(f)) {
continue; continue;
} }
@@ -1195,7 +1233,7 @@ export default class AutofillService implements AutofillServiceInterface {
private fieldPropertyIsMatch(field: any, property: string, name: string): boolean { private fieldPropertyIsMatch(field: any, property: string, name: string): boolean {
let fieldVal = field[property] as string; let fieldVal = field[property] as string;
if (!this.hasValue(fieldVal)) { if (!AutofillService.hasValue(fieldVal)) {
return false; return false;
} }
@@ -1227,33 +1265,45 @@ export default class AutofillService implements AutofillServiceInterface {
return fieldVal.toLowerCase() === name; return fieldVal.toLowerCase() === name;
} }
private fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean { static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
if (this.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) { if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
return true; return true;
} }
if (this.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) { if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
return true; 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; return true;
} }
if (this.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) { if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
return true; 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; 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; 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 true;
} }
return false; 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 === "") { if (options == null || options.length === 0 || value == null || value === "") {
return false; return false;
} }
@@ -1272,11 +1322,11 @@ export default class AutofillService implements AutofillServiceInterface {
return false; return false;
} }
private hasValue(str: string): boolean { static hasValue(str: string): boolean {
return str && str !== ""; return str && str !== "";
} }
private setFillScriptForFocus( static setFillScriptForFocus(
filledFields: { [id: string]: AutofillField }, filledFields: { [id: string]: AutofillField },
fillScript: AutofillScript fillScript: AutofillScript
): AutofillScript { ): AutofillScript {
@@ -1304,7 +1354,7 @@ export default class AutofillService implements AutofillServiceInterface {
return fillScript; 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) { if (field.maxLength && value && value.length > field.maxLength) {
value = value.substr(0, value.length); 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]); fillScript.script.push(["fill_by_opid", field.opid, value]);
} }
private forCustomFieldsOnly(field: AutofillField): boolean { static forCustomFieldsOnly(field: AutofillField): boolean {
return field.tagName === "span"; return field.tagName === "span";
} }
} }

View File

@@ -1,5 +1,5 @@
import AbstractChromeStorageService from "./abstractChromeStorageApi.service"; import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
export default class BrowserLocalStorageService extends AbstractChromeStorageService { export default class BrowserLocalStorageService extends AbstractChromeStorageService {
protected chromeStorageApi: any = chrome.storage.local; protected chromeStorageApi = chrome.storage.local;
} }

View File

@@ -1,5 +1,5 @@
import AbstractChromeStorageService from "./abstractChromeStorageApi.service"; import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
export default class BrowserMemoryStorageService extends AbstractChromeStorageService { export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
protected chromeStorageApi: any = (chrome.storage as any).session; protected chromeStorageApi = chrome.storage.session;
} }

View File

@@ -176,13 +176,6 @@ const config = {
return chunk.name === "popup/main"; 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, 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; module.exports = config;

View File

@@ -42,7 +42,8 @@ import { SyncService } from "@bitwarden/common/services/sync.service";
import { TokenService } from "@bitwarden/common/services/token.service"; import { TokenService } from "@bitwarden/common/services/token.service";
import { TotpService } from "@bitwarden/common/services/totp.service"; import { TotpService } from "@bitwarden/common/services/totp.service";
import { TwoFactorService } from "@bitwarden/common/services/twoFactor.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 { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service";
import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service";
@@ -106,6 +107,7 @@ export class Main {
twoFactorService: TwoFactorService; twoFactorService: TwoFactorService;
broadcasterService: BroadcasterService; broadcasterService: BroadcasterService;
folderApiService: FolderApiService; folderApiService: FolderApiService;
userVerificationApiService: UserVerificationApiService;
constructor() { constructor() {
let p = null; let p = null;
@@ -330,10 +332,13 @@ export class Main {
this.program = new Program(this); this.program = new Program(this);
this.vaultProgram = new VaultProgram(this); this.vaultProgram = new VaultProgram(this);
this.sendProgram = new SendProgram(this); this.sendProgram = new SendProgram(this);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
this.cryptoService, this.cryptoService,
this.i18nService, this.i18nService,
this.apiService this.userVerificationApiService
); );
} }

View File

@@ -1,4 +1,4 @@
import { Component, NgZone, OnDestroy } from "@angular/core"; import { Component, NgZone } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
@@ -22,7 +22,7 @@ const BroadcasterSubscriptionId = "LockComponent";
selector: "app-lock", selector: "app-lock",
templateUrl: "lock.component.html", templateUrl: "lock.component.html",
}) })
export class LockComponent extends BaseLockComponent implements OnDestroy { export class LockComponent extends BaseLockComponent {
private deferFocus: boolean = null; private deferFocus: boolean = null;
authenicatedUrl = "vault"; authenicatedUrl = "vault";
unAuthenicatedUrl = "update-temp-password"; unAuthenicatedUrl = "update-temp-password";
@@ -103,6 +103,7 @@ export class LockComponent extends BaseLockComponent implements OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
} }

View File

@@ -1,6 +1,7 @@
import { import {
Component, Component,
NgZone, NgZone,
OnDestroy,
OnInit, OnInit,
SecurityContext, SecurityContext,
Type, Type,
@@ -77,7 +78,7 @@ const systemTimeoutOptions = {
</div> </div>
`, `,
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit, OnDestroy {
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
@ViewChild("premium", { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef; @ViewChild("premium", { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef;
@ViewChild("passwordHistory", { read: ViewContainerRef, static: true }) @ViewChild("passwordHistory", { read: ViewContainerRef, static: true })
@@ -129,7 +130,7 @@ export class AppComponent implements OnInit {
) {} ) {}
ngOnInit() { ngOnInit() {
this.stateService.activeAccount.pipe(takeUntil(this.destroy$)).subscribe((userId) => { this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
this.activeUserId = userId; this.activeUserId = userId;
}); });

View File

@@ -8,15 +8,16 @@ export type SearchBarState = {
@Injectable() @Injectable()
export class SearchBarService { export class SearchBarService {
searchText = new BehaviorSubject<string>(null); private searchTextSubject = new BehaviorSubject<string>(null);
searchText$ = this.searchTextSubject.asObservable();
private _state = { private _state = {
enabled: false, enabled: false,
placeholderText: "", placeholderText: "",
}; };
// tslint:disable-next-line:member-ordering private stateSubject = new BehaviorSubject<SearchBarState>(this._state);
state = new BehaviorSubject<SearchBarState>(this._state); state$ = this.stateSubject.asObservable();
setEnabled(enabled: boolean) { setEnabled(enabled: boolean) {
this._state.enabled = enabled; this._state.enabled = enabled;
@@ -29,10 +30,10 @@ export class SearchBarService {
} }
setSearchText(value: string) { setSearchText(value: string) {
this.searchText.next(value); this.searchTextSubject.next(value);
} }
private updateState() { private updateState() {
this.state.next(this._state); this.stateSubject.next(this._state);
} }
} }

View File

@@ -1,5 +1,6 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { UntypedFormControl } from "@angular/forms"; import { UntypedFormControl } from "@angular/forms";
import { Subscription } from "rxjs";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
@@ -13,8 +14,10 @@ export class SearchComponent implements OnInit, OnDestroy {
state: SearchBarState; state: SearchBarState;
searchText: UntypedFormControl = new UntypedFormControl(null); searchText: UntypedFormControl = new UntypedFormControl(null);
private activeAccountSubscription: Subscription;
constructor(private searchBarService: SearchBarService, private stateService: StateService) { constructor(private searchBarService: SearchBarService, private stateService: StateService) {
this.searchBarService.state.subscribe((state) => { this.searchBarService.state$.subscribe((state) => {
this.state = state; this.state = state;
}); });
@@ -24,13 +27,13 @@ export class SearchComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
this.stateService.activeAccount.subscribe((value) => { this.activeAccountSubscription = this.stateService.activeAccount$.subscribe((value) => {
this.searchBarService.setSearchText(""); this.searchBarService.setSearchText("");
this.searchText.patchValue(""); this.searchText.patchValue("");
}); });
} }
ngOnDestroy() { ngOnDestroy() {
this.stateService.activeAccount.unsubscribe(); this.activeAccountSubscription.unsubscribe();
} }
} }

View File

@@ -56,7 +56,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
policyService, policyService,
logService logService
); );
this.searchBarService.searchText.subscribe((searchText) => { this.searchBarService.searchText$.subscribe((searchText) => {
this.searchText = searchText; this.searchText = searchText;
this.searchTextChanged(); this.searchTextChanged();
}); });

View File

@@ -14,7 +14,7 @@ export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) { constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService); super(searchService);
searchBarService.searchText.subscribe((searchText) => { searchBarService.searchText$.subscribe((searchText) => {
this.searchText = searchText; this.searchText = searchText;
this.search(200); this.search(200);
}); });

View File

@@ -13,7 +13,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; 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"; const BroadcasterSubscriptionId = "ExportComponent";

View File

@@ -100,7 +100,7 @@
<div <div
class="box-content-row box-content-row-flex totp" class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }" [ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode" *ngIf="cipher.login.totp && totpCode && canAccessPremium"
> >
<div class="row-main"> <div class="row-main">
<span <span
@@ -138,6 +138,19 @@
</button> </button>
</div> </div>
</div> </div>
<div
class="box-content-row box-content-row-flex totp"
*ngIf="cipher.login.totp && !canAccessPremium"
>
<div class="row-main">
<span class="row-label">{{ "verificationCodeTotp" | i18n }}</span>
<span class="row-label">
<a [routerLink]="" (click)="showGetPremium()"
>{{ "premiumSubcriptionRequired" | i18n }}
</a>
</span>
</div>
</div>
</div> </div>
<!-- Card --> <!-- Card -->
<div *ngIf="cipher.card"> <div *ngIf="cipher.card">

View File

@@ -115,4 +115,10 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
}); });
} }
} }
showGetPremium() {
if (!this.canAccessPremium) {
this.messagingService.send("premiumRequired");
}
}
} }

View File

@@ -1979,6 +1979,9 @@
"apiKey": { "apiKey": {
"message": "API Key" "message": "API Key"
}, },
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Organization is disabled."
}, },

View File

@@ -11,7 +11,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.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";
@Component({ @Component({
selector: "app-update-password", selector: "app-update-password",

View File

@@ -28,6 +28,7 @@
[password]="masterPassword" [password]="masterPassword"
[email]="email" [email]="email"
[showText]="true" [showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
> >
</app-password-strength> </app-password-strength>
</div> </div>

View File

@@ -147,7 +147,7 @@ export class AppComponent implements OnDestroy, OnInit {
const premiumConfirmed = await this.platformUtilsService.showDialog( const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"), this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"), this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"), this.i18nService.t("upgrade"),
this.i18nService.t("cancel") this.i18nService.t("cancel")
); );
if (premiumConfirmed) { if (premiumConfirmed) {

View File

@@ -3,7 +3,7 @@ import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { OrganizationApiKeyType } from "@bitwarden/common/enums/organizationApiKeyType";
import { OrganizationApiKeyRequest } from "@bitwarden/common/models/request/organizationApiKeyRequest"; import { OrganizationApiKeyRequest } from "@bitwarden/common/models/request/organizationApiKeyRequest";
import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse"; import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse";

View File

@@ -77,7 +77,12 @@
</button> </button>
</div> </div>
</div> </div>
<app-password-strength [password]="newPassword" [email]="email" [showText]="true"> <app-password-strength
[password]="newPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength> </app-password-strength>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core"; 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 { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -28,7 +29,7 @@ export class ResetPasswordComponent implements OnInit {
enforcedPolicyOptions: MasterPasswordPolicyOptions; enforcedPolicyOptions: MasterPasswordPolicyOptions;
newPassword: string = null; newPassword: string = null;
showPassword = false; showPassword = false;
masterPasswordScore: number; passwordStrengthResult: zxcvbn.ZXCVBNResult;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor( constructor(
@@ -97,7 +98,7 @@ export class ResetPasswordComponent implements OnInit {
if ( if (
this.enforcedPolicyOptions != null && this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword( !this.policyService.evaluateMasterPassword(
this.masterPasswordScore, this.passwordStrengthResult.score,
this.newPassword, this.newPassword,
this.enforcedPolicyOptions this.enforcedPolicyOptions
) )
@@ -110,7 +111,7 @@ export class ResetPasswordComponent implements OnInit {
return; return;
} }
if (this.masterPasswordScore < 3) { if (this.passwordStrengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog( const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"), this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"), this.i18nService.t("weakMasterPassword"),
@@ -184,4 +185,8 @@ export class ResetPasswordComponent implements OnInit {
this.logService.error(e); this.logService.error(e);
} }
} }
getStrengthResult(result: zxcvbn.ZXCVBNResult) {
this.passwordStrengthResult = result;
}
} }

View File

@@ -6,7 +6,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { CipherType } from "@bitwarden/common/enums/cipherType";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipherView"; import { CipherView } from "@bitwarden/common/models/view/cipherView";
@@ -53,10 +53,10 @@ export class DeleteOrganizationComponent implements OnInit {
deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete"; deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete";
organizationName: string; organizationName: string;
organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary(); organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary();
@Output() onSuccess: EventEmitter<any> = new EventEmitter(); @Output() onSuccess: EventEmitter<unknown> = new EventEmitter();
masterPassword: Verification; masterPassword: Verification;
formPromise: Promise<any>; formPromise: Promise<unknown>;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,

View File

@@ -10,7 +10,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; 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 { EventType } from "@bitwarden/common/enums/eventType";
import { ExportComponent } from "../../../tools/import-export/export.component"; import { ExportComponent } from "../../../tools/import-export/export.component";
@@ -66,7 +66,7 @@ export class OrganizationExportComponent extends ExportComponent {
return super.getFileName("org"); return super.getFileName("org");
} }
async collectEvent(): Promise<any> { async collectEvent(): Promise<void> {
await this.eventService.collect( await this.eventService.collect(
EventType.Organization_ClientExportedVault, EventType.Organization_ClientExportedVault,
null, null,

View File

@@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.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 { Utils } from "@bitwarden/common/misc/utils";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/models/request/organizationUserResetPasswordEnrollmentRequest"; import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/models/request/organizationUserResetPasswordEnrollmentRequest";
@@ -22,7 +22,7 @@ export class EnrollMasterPasswordReset {
organization: Organization; organization: Organization;
verification: Verification; verification: Verification;
formPromise: Promise<any>; formPromise: Promise<void>;
constructor( constructor(
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,

View File

@@ -189,6 +189,7 @@
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<img class="no-items-image" aria-hidden="true" />
<p>{{ "noSendsInList" | i18n }}</p> <p>{{ "noSendsInList" | i18n }}</p>
<button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend"> <button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend">
<i class="bwi bwi-plus bwi-fw"></i>{{ "createSend" | i18n }} <i class="bwi bwi-plus bwi-fw"></i>{{ "createSend" | i18n }}

View File

@@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { LogService } from "@bitwarden/common/abstractions/log.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 { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse"; import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse";
import { Verification } from "@bitwarden/common/types/verification"; import { Verification } from "@bitwarden/common/types/verification";

View File

@@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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"; import { Verification } from "@bitwarden/common/types/verification";
@Component({ @Component({
@@ -14,7 +14,7 @@ import { Verification } from "@bitwarden/common/types/verification";
}) })
export class DeauthorizeSessionsComponent { export class DeauthorizeSessionsComponent {
masterPassword: Verification; masterPassword: Verification;
formPromise: Promise<any>; formPromise: Promise<unknown>;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,

View File

@@ -5,7 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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"; import { Verification } from "@bitwarden/common/types/verification";
@Component({ @Component({
@@ -16,7 +16,7 @@ export class PurgeVaultComponent {
@Input() organizationId?: string = null; @Input() organizationId?: string = null;
masterPassword: Verification; masterPassword: Verification;
formPromise: Promise<any>; formPromise: Promise<unknown>;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,

View File

@@ -5,13 +5,28 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/models/request/updateTwoFactorAuthenticatorRequest"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/models/request/updateTwoFactorAuthenticatorRequest";
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component"; 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({ @Component({
selector: "app-two-factor-authenticator", selector: "app-two-factor-authenticator",
templateUrl: "two-factor-authenticator.component.html", templateUrl: "two-factor-authenticator.component.html",
@@ -23,7 +38,7 @@ export class TwoFactorAuthenticatorComponent
type = TwoFactorProviderType.Authenticator; type = TwoFactorProviderType.Authenticator;
key: string; key: string;
token: string; token: string;
formPromise: Promise<any>; formPromise: Promise<TwoFactorAuthenticatorResponse>;
private qrScript: HTMLScriptElement; private qrScript: HTMLScriptElement;
@@ -49,7 +64,7 @@ export class TwoFactorAuthenticatorComponent
window.document.body.removeChild(this.qrScript); window.document.body.removeChild(this.qrScript);
} }
auth(authResponse: any) { auth(authResponse: AuthResponse<TwoFactorAuthenticatorResponse>) {
super.auth(authResponse); super.auth(authResponse);
return this.processResponse(authResponse.response); return this.processResponse(authResponse.response);
} }
@@ -80,7 +95,7 @@ export class TwoFactorAuthenticatorComponent
this.key = response.key; this.key = response.key;
const email = await this.stateService.getEmail(); const email = await this.stateService.getEmail();
window.setTimeout(() => { window.setTimeout(() => {
new (window as any).QRious({ new window.QRious({
element: document.getElementById("qr"), element: document.getElementById("qr"),
value: value:
"otpauth://totp/Bitwarden:" + "otpauth://totp/Bitwarden:" +

View File

@@ -4,11 +4,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { TwoFactorProviderRequest } from "@bitwarden/common/models/request/twoFactorProviderRequest"; import { TwoFactorProviderRequest } from "@bitwarden/common/models/request/twoFactorProviderRequest";
import { AuthResponseBase } from "@bitwarden/common/types/authResponse";
@Directive() @Directive()
export abstract class TwoFactorBaseComponent { export abstract class TwoFactorBaseComponent {
@@ -31,7 +32,7 @@ export abstract class TwoFactorBaseComponent {
protected userVerificationService: UserVerificationService protected userVerificationService: UserVerificationService
) {} ) {}
protected auth(authResponse: any) { protected auth(authResponse: AuthResponseBase) {
this.hashedSecret = authResponse.secret; this.hashedSecret = authResponse.secret;
this.verificationType = authResponse.verificationType; this.verificationType = authResponse.verificationType;
this.authed = true; this.authed = true;
@@ -46,7 +47,7 @@ export abstract class TwoFactorBaseComponent {
} }
} }
protected async disable(promise: Promise<any>) { protected async disable(promise: Promise<unknown>) {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("twoStepDisableDesc"), this.i18nService.t("twoStepDisableDesc"),
this.i18nService.t("disable"), this.i18nService.t("disable"),

View File

@@ -4,10 +4,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/models/request/updateTwoFactorDuoRequest"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/models/request/updateTwoFactorDuoRequest";
import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse"; import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component"; import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -20,7 +21,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
ikey: string; ikey: string;
skey: string; skey: string;
host: string; host: string;
formPromise: Promise<any>; formPromise: Promise<TwoFactorDuoResponse>;
constructor( constructor(
apiService: ApiService, apiService: ApiService,
@@ -32,7 +33,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService); super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
} }
auth(authResponse: any) { auth(authResponse: AuthResponse<TwoFactorDuoResponse>) {
super.auth(authResponse); super.auth(authResponse);
this.processResponse(authResponse.response); this.processResponse(authResponse.response);
} }

View File

@@ -5,11 +5,12 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/twoFactorEmailRequest"; import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/twoFactorEmailRequest";
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/models/request/updateTwoFactorEmailRequest"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/models/request/updateTwoFactorEmailRequest";
import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse"; import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component"; import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -22,8 +23,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
email: string; email: string;
token: string; token: string;
sentEmail: string; sentEmail: string;
formPromise: Promise<any>; formPromise: Promise<TwoFactorEmailResponse>;
emailPromise: Promise<any>; emailPromise: Promise<unknown>;
constructor( constructor(
apiService: ApiService, apiService: ApiService,
@@ -36,7 +37,7 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService); super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
} }
auth(authResponse: any) { auth(authResponse: AuthResponse<TwoFactorEmailResponse>) {
super.auth(authResponse); super.auth(authResponse);
return this.processResponse(authResponse.response); return this.processResponse(authResponse.response);
} }

View File

@@ -2,26 +2,14 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LogService } from "@bitwarden/common/abstractions/log.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { VerificationType } from "@bitwarden/common/enums/verificationType"; import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse"; import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse"; import { TwoFactorResponse } from "@bitwarden/common/types/twoFactorResponse";
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 { Verification } from "@bitwarden/common/types/verification"; import { Verification } from "@bitwarden/common/types/verification";
type TwoFactorResponse =
| TwoFactorRecoverResponse
| TwoFactorDuoResponse
| TwoFactorEmailResponse
| TwoFactorWebAuthnResponse
| TwoFactorAuthenticatorResponse
| TwoFactorYubiKeyResponse;
@Component({ @Component({
selector: "app-two-factor-verify", selector: "app-two-factor-verify",
templateUrl: "two-factor-verify.component.html", templateUrl: "two-factor-verify.component.html",
@@ -29,7 +17,7 @@ type TwoFactorResponse =
export class TwoFactorVerifyComponent { export class TwoFactorVerifyComponent {
@Input() type: TwoFactorProviderType; @Input() type: TwoFactorProviderType;
@Input() organizationId: string; @Input() organizationId: string;
@Output() onAuthed = new EventEmitter<any>(); @Output() onAuthed = new EventEmitter<AuthResponse<TwoFactorResponse>>();
secret: Verification; secret: Verification;
formPromise: Promise<TwoFactorResponse>; formPromise: Promise<TwoFactorResponse>;

View File

@@ -4,7 +4,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnDeleteRequest"; import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnDeleteRequest";
@@ -13,9 +13,18 @@ import {
ChallengeResponse, ChallengeResponse,
TwoFactorWebAuthnResponse, TwoFactorWebAuthnResponse,
} from "@bitwarden/common/models/response/twoFactorWebAuthnResponse"; } from "@bitwarden/common/models/response/twoFactorWebAuthnResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component"; import { TwoFactorBaseComponent } from "./two-factor-base.component";
interface Key {
id: number;
name: string;
configured: boolean;
migrated?: boolean;
removePromise: Promise<TwoFactorWebAuthnResponse> | null;
}
@Component({ @Component({
selector: "app-two-factor-webauthn", selector: "app-two-factor-webauthn",
templateUrl: "two-factor-webauthn.component.html", templateUrl: "two-factor-webauthn.component.html",
@@ -23,14 +32,14 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component";
export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.WebAuthn; type = TwoFactorProviderType.WebAuthn;
name: string; name: string;
keys: any[]; keys: Key[];
keyIdAvailable: number = null; keyIdAvailable: number = null;
keysConfiguredCount = 0; keysConfiguredCount = 0;
webAuthnError: boolean; webAuthnError: boolean;
webAuthnListening: boolean; webAuthnListening: boolean;
webAuthnResponse: PublicKeyCredential; webAuthnResponse: PublicKeyCredential;
challengePromise: Promise<ChallengeResponse>; challengePromise: Promise<ChallengeResponse>;
formPromise: Promise<any>; formPromise: Promise<TwoFactorWebAuthnResponse>;
constructor( constructor(
apiService: ApiService, apiService: ApiService,
@@ -43,7 +52,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService); super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
} }
auth(authResponse: any) { auth(authResponse: AuthResponse<TwoFactorWebAuthnResponse>) {
super.auth(authResponse); super.auth(authResponse);
this.processResponse(authResponse.response); this.processResponse(authResponse.response);
} }
@@ -69,11 +78,11 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
return super.disable(this.formPromise); return super.disable(this.formPromise);
} }
async remove(key: any) { async remove(key: Key) {
if (this.keysConfiguredCount <= 1 || key.removePromise != null) { if (this.keysConfiguredCount <= 1 || key.removePromise != null) {
return; 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( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removeU2fConfirmation"), this.i18nService.t("removeU2fConfirmation"),
name, name,

View File

@@ -4,24 +4,30 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/models/request/updateTwoFactorYubioOtpRequest"; import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/models/request/updateTwoFactorYubioOtpRequest";
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/models/response/twoFactorYubiKeyResponse"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/models/response/twoFactorYubiKeyResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component"; import { TwoFactorBaseComponent } from "./two-factor-base.component";
interface Key {
key: string;
existingKey: string;
}
@Component({ @Component({
selector: "app-two-factor-yubikey", selector: "app-two-factor-yubikey",
templateUrl: "two-factor-yubikey.component.html", templateUrl: "two-factor-yubikey.component.html",
}) })
export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent { export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.Yubikey; type = TwoFactorProviderType.Yubikey;
keys: any[]; keys: Key[];
nfc = false; nfc = false;
formPromise: Promise<any>; formPromise: Promise<TwoFactorYubiKeyResponse>;
disablePromise: Promise<any>; disablePromise: Promise<unknown>;
constructor( constructor(
apiService: ApiService, apiService: ApiService,
@@ -33,7 +39,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService); super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
} }
auth(authResponse: any) { auth(authResponse: AuthResponse<TwoFactorYubiKeyResponse>) {
super.auth(authResponse); super.auth(authResponse);
this.processResponse(authResponse.response); this.processResponse(authResponse.response);
} }
@@ -59,7 +65,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
return super.disable(this.disablePromise); return super.disable(this.disablePromise);
} }
remove(key: any) { remove(key: Key) {
key.existingKey = null; key.existingKey = null;
key.key = null; key.key = null;
} }

View File

@@ -10,7 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
templateUrl: "verify-email.component.html", templateUrl: "verify-email.component.html",
}) })
export class VerifyEmailComponent { export class VerifyEmailComponent {
actionPromise: Promise<any>; actionPromise: Promise<unknown>;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,

View File

@@ -10,7 +10,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; 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({ @Component({
selector: "app-export", selector: "app-export",

View File

@@ -166,8 +166,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="tw-flex tw-flex-row">
<div class="col-6 form-group"> <div class="tw-mb-4 tw-w-1/2">
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label> <label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input <input
id="loginTotp" id="loginTotp"
@@ -179,14 +179,41 @@
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly" [disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly"
/> />
</div> </div>
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{ low: totpLow }"> <div class="tw-mb-4 tw-ml-4 tw-flex tw-w-1/2 tw-items-end" [ngClass]="{ low: totpLow }">
<div *ngIf="!cipher.login.totp || !totpCode"> <div
<img class="totp tw-flex tw-flex-row tw-items-center"
src="../../images/totp-countdown.png" *ngIf="!cipher.login.totp || (cipher.login.totp && !canAccessPremium)"
id="totpImage" >
<span class="totp-countdown">
<span class="totp-sec tw-text-muted">15</span>
<svg>
<g>
<circle
class="totp-circle-muted inner"
r="12.6"
cy="16"
cx="16"
opacity="0.25"
[ngStyle]="{ 'stroke-dashoffset.px': 40 }"
></circle>
<circle
class="totp-circle-muted outer"
opacity="0.25"
r="14"
cy="16"
cx="16"
></circle>
</g>
</svg>
</span>
<span
class="totp-code tw-mr-3 tw-ml-2 tw-text-muted"
title="{{ 'verificationCodeTotp' | i18n }}" title="{{ 'verificationCodeTotp' | i18n }}"
class="ml-2" >--- ---</span
/> >
<i class="bwi bwi-lg bwi-clone tw-text-muted" aria-hidden="true"></i>
</div>
<div class="tw-pb-2" *ngIf="!cipher.login.totp || !totpCode">
<app-premium-badge <app-premium-badge
*ngIf="!organization && !cipher.organizationId" *ngIf="!organization && !cipher.organizationId"
class="ml-3" class="ml-3"
@@ -209,8 +236,11 @@
{{ "upgrade" | i18n }} {{ "upgrade" | i18n }}
</a> </a>
</div> </div>
<div *ngIf="cipher.login.totp && totpCode" class="d-flex align-items-center"> <div
<span class="totp-countdown mr-3 ml-2"> *ngIf="cipher.login.totp && totpCode && canAccessPremium"
class="totp tw-flex tw-flex-row tw-items-center"
>
<span class="totp-countdown">
<span class="totp-sec">{{ totpSec }}</span> <span class="totp-sec">{{ totpSec }}</span>
<svg> <svg>
<g> <g>
@@ -225,16 +255,18 @@
</g> </g>
</svg> </svg>
</span> </span>
<span class="totp-code mr-2" title="{{ 'verificationCodeTotp' | i18n }}">{{ <span
totpCodeFormatted class="totp-code tw-mr-2 tw-ml-2 tw-mt-1"
}}</span> title="{{ 'verificationCodeTotp' | i18n }}"
>{{ totpCodeFormatted }}</span
>
<button <button
type="button" type="button"
class="btn btn-link" class="tw-items-center tw-border-none tw-bg-transparent tw-text-primary-500"
appA11yTitle="{{ 'copyVerificationCode' | i18n }}" appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')" (click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
> >
<i class="bwi bwi-clone" aria-hidden="true"></i> <i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -152,6 +152,17 @@ export class AddEditComponent extends BaseAddEditComponent {
}); });
} }
showGetPremium() {
if (this.canAccessPremium) {
return;
}
if (this.cipher.organizationUseTotp) {
this.upgradeOrganization();
} else {
this.premiumRequired();
}
}
viewHistory() { viewHistory() {
this.viewingPasswordHistory = !this.viewingPasswordHistory; this.viewingPasswordHistory = !this.viewingPasswordHistory;
} }

View File

@@ -142,6 +142,7 @@
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<img class="no-items-image" aria-hidden="true" />
<p>{{ "noItemsInList" | i18n }}</p> <p>{{ "noItemsInList" | i18n }}</p>
<button (click)="addCipher()" class="btn btn-outline-primary" *ngIf="showAddNew"> <button (click)="addCipher()" class="btn btn-outline-primary" *ngIf="showAddNew">
<i class="bwi bwi-plus bwi-fw"></i>{{ "addItem" | i18n }} <i class="bwi bwi-plus bwi-fw"></i>{{ "addItem" | i18n }}

View File

@@ -0,0 +1,34 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.7">
<g opacity="0.7">
<g clip-path="url(#clip0_44_9647)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.3599 73.2564C43.579 74.4366 47.0654 75.0822 50.7059 75.0822C66.9882 75.0822 80.1876 62.1696 80.1876 46.2411C80.1876 45.8578 80.1804 45.4762 80.1648 45.0966H108.891V84.6672H40.3599V73.2564Z" fill="#6e7689"/>
<path d="M21.5461 46.241C21.5461 62.1696 34.7456 75.0822 51.028 75.0822C67.3104 75.0822 80.5098 62.1696 80.5098 46.241C80.5098 30.3125 67.3104 17.4 51.028 17.4C34.7456 17.4 21.5461 30.3125 21.5461 46.241Z" stroke="#bac0ce" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M35.3603 70.5954C35.3603 69.933 34.823 69.3954 34.1603 69.3954C33.4976 69.3954 32.9603 69.933 32.9603 70.5954H35.3603ZM112.835 40.2387C114.169 40.2387 115.2 41.3027 115.2 42.5698H117.6C117.6 39.9762 115.493 37.8387 112.835 37.8387V40.2387ZM115.2 42.5698V88.6158H117.6V42.5698H115.2ZM115.2 88.6158C115.2 89.9094 114.142 90.9468 112.835 90.9468V93.3468C115.425 93.3468 117.6 91.2774 117.6 88.6158H115.2ZM112.835 90.9468H37.7256V93.3468H112.835V90.9468ZM37.7256 90.9468C36.3913 90.9468 35.3603 89.883 35.3603 88.6158H32.9603C32.9603 91.2096 35.0667 93.3468 37.7256 93.3468V90.9468ZM35.3603 88.6158V70.5954H32.9603V88.6158H35.3603ZM79.8684 40.2387H112.835V37.8387H79.8684V40.2387Z" fill="#bac0ce"/>
<path d="M79.9068 45.2867H109.021V84.8574H40.4873V73.0512" stroke="#bac0ce" stroke-width="2" stroke-linejoin="round"/>
<path d="M57.3565 102.56H93.2046" stroke="#bac0ce" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M68.9544 92.1468V102.56" stroke="#bac0ce" stroke-width="4" stroke-linejoin="round"/>
<path d="M80.553 92.1468V102.56" stroke="#bac0ce" stroke-width="4" stroke-linejoin="round"/>
<path d="M27.4398 64.9452L22.9296 69.4554L5.72134 86.6634C4.54976 87.8352 4.54976 89.7342 5.72133 90.906L6.95929 92.1438C8.13085 93.3156 10.0304 93.3156 11.202 92.1438L28.4102 74.9358L32.9204 70.4256" stroke="#bac0ce" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M101.293 53.1537H85.1784" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 59.1966H90.2142" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M85.1784 59.1966H77.625" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 65.2392H94.2426" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M88.7034 65.2392H73.0926" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 71.2824H85.1784" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M79.6392 71.2824H71.0784" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 77.325H78.6318" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M73.0926 77.325H59.9997" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M54.4604 77.325H46.4032" stroke="#bac0ce" stroke-width="2" stroke-linecap="round"/>
<path d="M29.1638 33.0108H70.6926C72.0181 33.0108 73.0926 34.0853 73.0926 35.4108V41.6894C73.0926 43.0149 72.0181 44.0894 70.6926 44.0894H29.1638C27.8383 44.0894 26.7638 43.0149 26.7638 41.6894V35.4108C26.7638 34.0853 27.8383 33.0108 29.1638 33.0108Z" stroke="#bac0ce" stroke-width="4"/>
<path d="M22.7354 54.1609H57.0962C58.4217 54.1609 59.4962 55.2354 59.4962 56.5609V62.8392C59.4962 64.1652 58.4217 65.2392 57.0962 65.2392H28.7783" stroke="#bac0ce" stroke-width="4" stroke-linecap="round"/>
<path d="M79.1358 54.1609H72.975C71.6496 54.1609 70.575 55.2354 70.575 56.5609V62.9736C70.575 64.2252 71.5896 65.2392 72.8406 65.2392" stroke="#bac0ce" stroke-width="4" stroke-linecap="round"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_44_9647">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,34 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.7">
<g opacity="0.7">
<g clip-path="url(#clip0_44_9647)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.3599 73.2564C43.579 74.4366 47.0654 75.0822 50.7059 75.0822C66.9882 75.0822 80.1876 62.1696 80.1876 46.2411C80.1876 45.8578 80.1804 45.4762 80.1648 45.0966H108.891V84.6672H40.3599V73.2564Z" fill="#ced4dc"/>
<path d="M21.5461 46.241C21.5461 62.1696 34.7456 75.0822 51.028 75.0822C67.3104 75.0822 80.5098 62.1696 80.5098 46.241C80.5098 30.3125 67.3104 17.4 51.028 17.4C34.7456 17.4 21.5461 30.3125 21.5461 46.241Z" stroke="#6d757e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M35.3603 70.5954C35.3603 69.933 34.823 69.3954 34.1603 69.3954C33.4976 69.3954 32.9603 69.933 32.9603 70.5954H35.3603ZM112.835 40.2387C114.169 40.2387 115.2 41.3027 115.2 42.5698H117.6C117.6 39.9762 115.493 37.8387 112.835 37.8387V40.2387ZM115.2 42.5698V88.6158H117.6V42.5698H115.2ZM115.2 88.6158C115.2 89.9094 114.142 90.9468 112.835 90.9468V93.3468C115.425 93.3468 117.6 91.2774 117.6 88.6158H115.2ZM112.835 90.9468H37.7256V93.3468H112.835V90.9468ZM37.7256 90.9468C36.3913 90.9468 35.3603 89.883 35.3603 88.6158H32.9603C32.9603 91.2096 35.0667 93.3468 37.7256 93.3468V90.9468ZM35.3603 88.6158V70.5954H32.9603V88.6158H35.3603ZM79.8684 40.2387H112.835V37.8387H79.8684V40.2387Z" fill="#6d757e"/>
<path d="M79.9068 45.2867H109.021V84.8574H40.4873V73.0512" stroke="#6d757e" stroke-width="2" stroke-linejoin="round"/>
<path d="M57.3565 102.56H93.2046" stroke="#6d757e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M68.9544 92.1468V102.56" stroke="#6d757e" stroke-width="4" stroke-linejoin="round"/>
<path d="M80.553 92.1468V102.56" stroke="#6d757e" stroke-width="4" stroke-linejoin="round"/>
<path d="M27.4398 64.9452L22.9296 69.4554L5.72134 86.6634C4.54976 87.8352 4.54976 89.7342 5.72133 90.906L6.95929 92.1438C8.13085 93.3156 10.0304 93.3156 11.202 92.1438L28.4102 74.9358L32.9204 70.4256" stroke="#6d757e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M101.293 53.1537H85.1784" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 59.1966H90.2142" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M85.1784 59.1966H77.625" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 65.2392H94.2426" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M88.7034 65.2392H73.0926" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 71.2824H85.1784" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M79.6392 71.2824H71.0784" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M101.293 77.325H78.6318" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M73.0926 77.325H59.9997" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M54.4604 77.325H46.4032" stroke="#6d757e" stroke-width="2" stroke-linecap="round"/>
<path d="M29.1638 33.0108H70.6926C72.0181 33.0108 73.0926 34.0853 73.0926 35.4108V41.6894C73.0926 43.0149 72.0181 44.0894 70.6926 44.0894H29.1638C27.8383 44.0894 26.7638 43.0149 26.7638 41.6894V35.4108C26.7638 34.0853 27.8383 33.0108 29.1638 33.0108Z" stroke="#6d757e" stroke-width="4"/>
<path d="M22.7354 54.1609H57.0962C58.4217 54.1609 59.4962 55.2354 59.4962 56.5609V62.8392C59.4962 64.1652 58.4217 65.2392 57.0962 65.2392H28.7783" stroke="#6d757e" stroke-width="4" stroke-linecap="round"/>
<path d="M79.1358 54.1609H72.975C71.6496 54.1609 70.575 55.2354 70.575 56.5609V62.9736C70.575 64.2252 71.5896 65.2392 72.8406 65.2392" stroke="#6d757e" stroke-width="4" stroke-linecap="round"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_44_9647">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 B

View File

@@ -5227,6 +5227,9 @@
} }
} }
}, },
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"scim": { "scim": {
"message": "SCIM Provisioning", "message": "SCIM Provisioning",
"description": "The text, 'SCIM', is an acronymn and should not be translated." "description": "The text, 'SCIM', is an acronymn and should not be translated."

View File

@@ -306,3 +306,18 @@ button i.bwi,
a i.bwi { a i.bwi {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
.no-items {
display: flex;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
.no-items-image {
@include themify($themes) {
content: url("../images/search-web" + themed("svgSuffix"));
}
}
}

View File

@@ -130,6 +130,25 @@
stroke-width: 2; stroke-width: 2;
} }
} }
.totp-circle-muted {
fill: none;
@include themify($themes) {
stroke: themed("info");
}
&.inner {
stroke-dasharray: 78.6;
stroke-dashoffset: 0;
stroke-width: 3;
}
&.outer {
stroke-dasharray: 88;
stroke-dashoffset: 0;
stroke-width: 2;
}
}
} }
> .align-items-center { > .align-items-center {

View File

@@ -219,6 +219,7 @@ $themes: (
textMuted: #6c757d, textMuted: #6c757d,
textSuccessColor: $white, textSuccessColor: $white,
textWarningColor: $white, textWarningColor: $white,
svgSuffix: "-light.svg",
), ),
dark: ( dark: (
primary: $darkPrimary, primary: $darkPrimary,
@@ -330,6 +331,7 @@ $themes: (
textMuted: $darkGrey1, textMuted: $darkGrey1,
textSuccessColor: $darkDarkBlue2, textSuccessColor: $darkDarkBlue2,
textWarningColor: $darkDarkBlue2, textWarningColor: $darkDarkBlue2,
svgSuffix: "-dark.svg",
), ),
); );

View File

@@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; 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 { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/enums/policyType"; import { PolicyType } from "@bitwarden/common/enums/policyType";
@@ -142,7 +142,7 @@ export class ExportComponent implements OnInit {
return this.exportService.getFileName(prefix, extension); return this.exportService.getFileName(prefix, extension);
} }
protected async collectEvent(): Promise<any> { protected async collectEvent(): Promise<void> {
await this.eventService.collect(EventType.User_ClientExportedVault); await this.eventService.collect(EventType.User_ClientExportedVault);
} }

View File

@@ -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 { Router } from "@angular/router";
import { Subscription } from "rxjs";
import { take } from "rxjs/operators"; import { take } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
@Directive() @Directive()
export class LockComponent implements OnInit { export class LockComponent implements OnInit, OnDestroy {
masterPassword = ""; masterPassword = "";
pin = ""; pin = "";
showPassword = false; showPassword = false;
@@ -39,6 +40,8 @@ export class LockComponent implements OnInit {
private invalidPinAttempts = 0; private invalidPinAttempts = 0;
private pinSet: [boolean, boolean]; private pinSet: [boolean, boolean];
private activeAccountSubscription: Subscription;
constructor( constructor(
protected router: Router, protected router: Router,
protected i18nService: I18nService, protected i18nService: I18nService,
@@ -57,11 +60,15 @@ export class LockComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
// Load the first and observe updates // Load the first and observe updates
await this.load(); await this.load();
this.stateService.activeAccount.subscribe(async () => { this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => {
await this.load(); await this.load();
}); });
} }
ngOnDestroy() {
this.activeAccountSubscription.unsubscribe();
}
async submit() { async submit() {
if (this.pinLock && (this.pin == null || this.pin === "")) { if (this.pinLock && (this.pin == null || this.pin === "")) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(

View File

@@ -10,7 +10,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.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 { VerificationType } from "@bitwarden/common/enums/verificationType"; import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { EncString } from "@bitwarden/common/models/domain/encString"; import { EncString } from "@bitwarden/common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
@@ -28,7 +28,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
showPassword = false; showPassword = false;
currentMasterPassword: string; currentMasterPassword: string;
onSuccessfulChangePassword: () => Promise<any>; onSuccessfulChangePassword: () => Promise<void>;
constructor( constructor(
protected router: Router, protected router: Router,

View File

@@ -1,9 +1,9 @@
import { animate, style, transition, trigger } from "@angular/animations"; import { animate, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core"; 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 { 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 { VerificationType } from "@bitwarden/common/enums/verificationType";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { Verification } from "@bitwarden/common/types/verification"; import { Verification } from "@bitwarden/common/types/verification";
@@ -35,7 +35,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
disableRequestOTP = false; disableRequestOTP = false;
sentCode = false; sentCode = false;
secret = new UntypedFormControl(""); secret = new FormControl("");
private onChange: (value: Verification) => void; private onChange: (value: Verification) => void;

View File

@@ -49,7 +49,8 @@ import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstrac
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.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 { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; 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 { TokenService } from "@bitwarden/common/services/token.service";
import { TotpService } from "@bitwarden/common/services/totp.service"; import { TotpService } from "@bitwarden/common/services/totp.service";
import { TwoFactorService } from "@bitwarden/common/services/twoFactor.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 { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
@@ -476,7 +478,11 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
{ {
provide: UserVerificationServiceAbstraction, provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService, useClass: UserVerificationService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction], deps: [
CryptoServiceAbstraction,
I18nServiceAbstraction,
UserVerificationApiServiceAbstraction,
],
}, },
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{ {
@@ -502,6 +508,11 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
provide: FormValidationErrorsServiceAbstraction, provide: FormValidationErrorsServiceAbstraction,
useClass: FormValidationErrorsService, useClass: FormValidationErrorsService,
}, },
{
provide: UserVerificationApiServiceAbstraction,
useClass: UserVerificationApiService,
deps: [ApiServiceAbstraction],
},
], ],
}) })
export class JslibServicesModule {} export class JslibServicesModule {}

View File

@@ -78,25 +78,4 @@ describe("ConsoleLogService", () => {
error: { 0: "this is an error message" }, 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({});
});
}); });

View File

@@ -32,7 +32,7 @@ describe("Folder Service", () => {
stateService.getEncryptedFolders().resolves({ stateService.getEncryptedFolders().resolves({
"1": folderData("1", "test"), "1": folderData("1", "test"),
}); });
stateService.activeAccount.returns(activeAccount); stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked.returns(activeAccountUnlocked); stateService.activeAccountUnlocked.returns(activeAccountUnlocked);
(window as any).bitwardenContainerService = new ContainerService(cryptoService); (window as any).bitwardenContainerService = new ContainerService(cryptoService);

View File

@@ -1,7 +1,6 @@
import { OrganizationApiKeyType } from "../enums/organizationApiKeyType"; import { OrganizationApiKeyType } from "../enums/organizationApiKeyType";
import { OrganizationConnectionType } from "../enums/organizationConnectionType"; import { OrganizationConnectionType } from "../enums/organizationConnectionType";
import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest";
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest";
import { AttachmentRequest } from "../models/request/attachmentRequest"; import { AttachmentRequest } from "../models/request/attachmentRequest";
import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest";
import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest";
@@ -228,8 +227,6 @@ export abstract class ApiService {
postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>; postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>; postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise<any>; putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise<any>;
postAccountRequestOTP: () => Promise<void>;
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
postConvertToKeyConnector: () => Promise<void>; postConvertToKeyConnector: () => Promise<void>;
getUserBillingHistory: () => Promise<BillingHistoryResponse>; getUserBillingHistory: () => Promise<BillingHistoryResponse>;

View File

@@ -6,6 +6,4 @@ export abstract class LogService {
warning: (message: string) => void; warning: (message: string) => void;
error: (message: string) => void; error: (message: string) => void;
write: (level: LogLevelType, message: string) => void; write: (level: LogLevelType, message: string) => void;
time: (label: string) => void;
timeEnd: (label: string) => [number, number];
} }

View File

@@ -26,9 +26,8 @@ import { SendView } from "../models/view/sendView";
export abstract class StateService<T extends Account = Account> { export abstract class StateService<T extends Account = Account> {
accounts: BehaviorSubject<{ [userId: string]: T }>; accounts: BehaviorSubject<{ [userId: string]: T }>;
activeAccount: BehaviorSubject<string>; activeAccount$: Observable<string>;
activeAccountUnlocked$: Observable<boolean>;
activeAccountUnlocked: Observable<boolean>;
addAccount: (account: T) => Promise<void>; addAccount: (account: T) => Promise<void>;
setActiveUser: (userId: string) => Promise<void>; setActiveUser: (userId: string) => Promise<void>;

View File

@@ -0,0 +1,6 @@
import { VerifyOTPRequest } from "@bitwarden/common/models/request/account/verifyOTPRequest";
export abstract class UserVerificationApiServiceAbstraction {
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
postAccountRequestOTP: () => Promise<void>;
}

View File

@@ -1,5 +1,5 @@
import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
import { Verification } from "../types/verification"; import { Verification } from "../../types/verification";
export abstract class UserVerificationService { export abstract class UserVerificationService {
buildRequest: <T extends SecretVerificationRequest>( buildRequest: <T extends SecretVerificationRequest>(

View File

@@ -1,17 +1,28 @@
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape */
import * as tldjs from "tldjs"; import * as tldjs from "tldjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "../abstractions/i18n.service"; import { I18nService } from "../abstractions/i18n.service";
const nodeURL = typeof window === "undefined" ? require("url") : null; 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 { export class Utils {
static inited = false; static inited = false;
static isNode = false; static isNode = false;
static isBrowser = true; static isBrowser = true;
static isMobileBrowser = false; static isMobileBrowser = false;
static isAppleMobileBrowser = false; static isAppleMobileBrowser = false;
static global: any = null; static global: typeof global = null;
static tldEndingRegex = static tldEndingRegex =
/.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/; /.*\.(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. // 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 != null &&
(process as any).release.name === "node"; (process as any).release.name === "node";
Utils.isBrowser = typeof window !== "undefined"; Utils.isBrowser = typeof window !== "undefined";
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window);
Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(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 { static fromB64ToArray(str: string): Uint8Array {
if (Utils.isNode) { if (Utils.isNode) {
return new Uint8Array(Buffer.from(str, "base64")); return new Uint8Array(Buffer.from(str, "base64"));
} else { } else {
const binaryString = window.atob(str); const binaryString = Utils.global.atob(str);
const bytes = new Uint8Array(binaryString.length); const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) { for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i); bytes[i] = binaryString.charCodeAt(i);
@@ -93,7 +113,7 @@ export class Utils {
for (let i = 0; i < bytes.byteLength; i++) { for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[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) { if (Utils.isNode) {
return Buffer.from(utfStr, "utf8").toString("base64"); return Buffer.from(utfStr, "utf8").toString("base64");
} else { } else {
return decodeURIComponent(escape(window.btoa(utfStr))); return decodeURIComponent(escape(Utils.global.btoa(utfStr)));
} }
} }
@@ -169,7 +189,7 @@ export class Utils {
if (Utils.isNode) { if (Utils.isNode) {
return Buffer.from(b64Str, "base64").toString("utf8"); return Buffer.from(b64Str, "base64").toString("utf8");
} else { } 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); return new nodeURL.URL(uriString);
} else if (typeof URL === "function") { } else if (typeof URL === "function") {
return new URL(uriString); return new URL(uriString);
} else if (window != null) { } else if (typeof window !== "undefined") {
const hasProtocol = uriString.indexOf("://") > -1; const hasProtocol = uriString.indexOf("://") > -1;
if (!hasProtocol && uriString.indexOf(".") > -1) { if (!hasProtocol && uriString.indexOf(".") > -1) {
uriString = "http://" + uriString; uriString = "http://" + uriString;

View File

@@ -48,7 +48,7 @@ export class Attachment extends Domain {
if (this.key != null) { if (this.key != null) {
let cryptoService: CryptoService; let cryptoService: CryptoService;
const containerService = (Utils.global as any).bitwardenContainerService; const containerService = Utils.global.bitwardenContainerService;
if (containerService) { if (containerService) {
cryptoService = containerService.getCryptoService(); cryptoService = containerService.getCryptoService();
} else { } else {

View File

@@ -104,7 +104,7 @@ export class EncString implements IEncrypted {
} }
let cryptoService: CryptoService; let cryptoService: CryptoService;
const containerService = (Utils.global as any).bitwardenContainerService; const containerService = Utils.global.bitwardenContainerService;
if (containerService) { if (containerService) {
cryptoService = containerService.getCryptoService(); cryptoService = containerService.getCryptoService();
} else { } else {

View File

@@ -1,7 +1,7 @@
import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction"; import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.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 { AccountService as AccountServiceAbstraction } from "../../abstractions/account/account.service.abstraction";
import { Verification } from "../../types/verification"; import { Verification } from "../../types/verification";
@@ -14,7 +14,7 @@ export class AccountService implements AccountServiceAbstraction {
private logService: LogService private logService: LogService
) {} ) {}
async delete(verification: Verification): Promise<any> { async delete(verification: Verification): Promise<void> {
try { try {
const verificationRequest = await this.userVerificationService.buildRequest(verification); const verificationRequest = await this.userVerificationService.buildRequest(verification);
await this.accountApiService.deleteAccount(verificationRequest); await this.accountApiService.deleteAccount(verificationRequest);

View File

@@ -8,7 +8,6 @@ import { OrganizationApiKeyType } from "../enums/organizationApiKeyType";
import { OrganizationConnectionType } from "../enums/organizationConnectionType"; import { OrganizationConnectionType } from "../enums/organizationConnectionType";
import { Utils } from "../misc/utils"; import { Utils } from "../misc/utils";
import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest";
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest";
import { AttachmentRequest } from "../models/request/attachmentRequest"; import { AttachmentRequest } from "../models/request/attachmentRequest";
import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest";
import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; 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); return this.send("PUT", "/accounts/update-temp-password", request, true, false);
} }
postAccountRequestOTP(): Promise<void> {
return this.send("POST", "/accounts/request-otp", null, true, false);
}
postAccountVerifyOTP(request: VerifyOTPRequest): Promise<void> {
return this.send("POST", "/accounts/verify-otp", request, true, false);
}
postConvertToKeyConnector(): Promise<void> { postConvertToKeyConnector(): Promise<void> {
return this.send("POST", "/accounts/convert-to-key-connector", null, true, false); return this.send("POST", "/accounts/convert-to-key-connector", null, true, false);
} }

View File

@@ -341,7 +341,7 @@ export class CipherService implements CipherServiceAbstraction {
throw new Error("No key."); throw new Error("No key.");
} }
const promises: any[] = []; const promises: Promise<number>[] = [];
const ciphers = await this.getAll(); const ciphers = await this.getAll();
ciphers.forEach(async (cipher) => { ciphers.forEach(async (cipher) => {
promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); promises.push(cipher.decrypt().then((c) => decCiphers.push(c)));

View File

@@ -1,5 +1,3 @@
import * as hrtime from "browser-hrtime";
import { LogService as LogServiceAbstraction } from "../abstractions/log.service"; import { LogService as LogServiceAbstraction } from "../abstractions/log.service";
import { LogLevelType } from "../enums/logLevelType"; import { LogLevelType } from "../enums/logLevelType";
@@ -56,17 +54,4 @@ export class ConsoleLogService implements LogServiceAbstraction {
break; 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;
}
} }

View File

@@ -22,7 +22,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
private scimUrl: string = null; private scimUrl: string = null;
constructor(private stateService: StateService) { constructor(private stateService: StateService) {
this.stateService.activeAccount.subscribe(async () => { this.stateService.activeAccount$.subscribe(async () => {
await this.setUrlsFromStorage(); await this.setUrlsFromStorage();
}); });
} }

View File

@@ -25,7 +25,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
private cipherService: CipherService, private cipherService: CipherService,
private stateService: StateService private stateService: StateService
) { ) {
this.stateService.activeAccountUnlocked.subscribe(async (unlocked) => { this.stateService.activeAccountUnlocked$.subscribe(async (unlocked) => {
if ((Utils.global as any).bitwardenContainerService == null) { if ((Utils.global as any).bitwardenContainerService == null) {
return; return;
} }

View File

@@ -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();
}
}

View File

@@ -11,6 +11,8 @@ import { CipherView } from "../models/view/cipherView";
import { SendView } from "../models/view/sendView"; import { SendView } from "../models/view/sendView";
export class SearchService implements SearchServiceAbstraction { export class SearchService implements SearchServiceAbstraction {
private static registeredPipeline = false;
indexedEntityId?: string = null; indexedEntityId?: string = null;
private indexing = false; private indexing = false;
private index: lunr.Index = null; private index: lunr.Index = null;
@@ -31,8 +33,13 @@ export class SearchService implements SearchServiceAbstraction {
} }
}); });
//register lunr pipeline function // Currently have to ensure this is only done a single time. Lunr allows you to register a function
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents"); // 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 { clearIndex(): void {
@@ -54,7 +61,6 @@ export class SearchService implements SearchServiceAbstraction {
return; return;
} }
this.logService.time("search indexing");
this.indexing = true; this.indexing = true;
this.indexedEntityId = indexedEntityId; this.indexedEntityId = indexedEntityId;
this.index = null; this.index = null;
@@ -95,7 +101,7 @@ export class SearchService implements SearchServiceAbstraction {
this.indexing = false; this.indexing = false;
this.logService.timeEnd("search indexing"); this.logService.info("Finished search indexing");
} }
async searchCiphers( async searchCiphers(

View File

@@ -55,8 +55,11 @@ export class StateService<
> implements StateServiceAbstraction<TAccount> > implements StateServiceAbstraction<TAccount>
{ {
accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({});
activeAccount = new BehaviorSubject<string>(null); private activeAccountSubject = new BehaviorSubject<string>(null);
activeAccountUnlocked = new BehaviorSubject<boolean>(false); activeAccount$ = this.activeAccountSubject.asObservable();
private activeAccountUnlockedSubject = new BehaviorSubject<boolean>(false);
activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable();
private hasBeenInited = false; private hasBeenInited = false;
private isRecoveredSession = false; private isRecoveredSession = false;
@@ -73,17 +76,17 @@ export class StateService<
protected useAccountCache: boolean = true protected useAccountCache: boolean = true
) { ) {
// If the account gets changed, verify the new account is unlocked // If the account gets changed, verify the new account is unlocked
this.activeAccount.subscribe(async (userId) => { this.activeAccountSubject.subscribe(async (userId) => {
if (userId == null && this.activeAccountUnlocked.getValue() == false) { if (userId == null && this.activeAccountUnlockedSubject.getValue() == false) {
return; return;
} else if (userId == null) { } else if (userId == null) {
this.activeAccountUnlocked.next(false); this.activeAccountUnlockedSubject.next(false);
} }
// FIXME: This should be refactored into AuthService or a similar service, // FIXME: This should be refactored into AuthService or a similar service,
// as checking for the existance of the crypto key is a low level // as checking for the existance of the crypto key is a low level
// implementation detail. // 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; state.activeUserId = storedActiveUser;
} }
await this.pushAccounts(); await this.pushAccounts();
this.activeAccount.next(state.activeUserId); this.activeAccountSubject.next(state.activeUserId);
return state; return state;
}); });
@@ -154,7 +157,7 @@ export class StateService<
await this.scaffoldNewAccountStorage(account); await this.scaffoldNewAccountStorage(account);
await this.setLastActive(new Date().getTime(), { userId: account.profile.userId }); await this.setLastActive(new Date().getTime(), { userId: account.profile.userId });
await this.setActiveUser(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<void> { async setActiveUser(userId: string): Promise<void> {
@@ -162,7 +165,7 @@ export class StateService<
await this.updateState(async (state) => { await this.updateState(async (state) => {
state.activeUserId = userId; state.activeUserId = userId;
await this.storageService.save(keys.activeUserId, userId); await this.storageService.save(keys.activeUserId, userId);
this.activeAccount.next(state.activeUserId); this.activeAccountSubject.next(state.activeUserId);
return state; return state;
}); });
@@ -497,12 +500,12 @@ export class StateService<
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
if (options.userId == this.activeAccount.getValue()) { if (options.userId == this.activeAccountSubject.getValue()) {
const nextValue = value != null; const nextValue = value != null;
// Avoid emitting if we are already unlocked // Avoid emitting if we are already unlocked
if (this.activeAccountUnlocked.getValue() != nextValue) { if (this.activeAccountUnlockedSubject.getValue() != nextValue) {
this.activeAccountUnlocked.next(nextValue); this.activeAccountUnlockedSubject.next(nextValue);
} }
} }
} }
@@ -607,7 +610,7 @@ export class StateService<
); );
} }
@withPrototypeForArrayMembers(CipherView) @withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> { async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))

View File

@@ -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<void> {
return this.apiService.send("POST", "/accounts/verify-otp", request, true, false);
}
async postAccountRequestOTP(): Promise<void> {
return this.apiService.send("POST", "/accounts/request-otp", null, true, false);
}
}

View File

@@ -1,11 +1,11 @@
import { ApiService } from "../abstractions/api.service"; import { CryptoService } from "../../abstractions/crypto.service";
import { CryptoService } from "../abstractions/crypto.service"; import { I18nService } from "../../abstractions/i18n.service";
import { I18nService } from "../abstractions/i18n.service"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../abstractions/userVerification.service"; import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/userVerification/userVerification.service.abstraction";
import { VerificationType } from "../enums/verificationType"; import { VerificationType } from "../../enums/verificationType";
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; import { VerifyOTPRequest } from "../../models/request/account/verifyOTPRequest";
import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
import { Verification } from "../types/verification"; import { Verification } from "../../types/verification";
/** /**
* Used for general-purpose user verification throughout the app. * Used for general-purpose user verification throughout the app.
@@ -15,7 +15,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
constructor( constructor(
private cryptoService: CryptoService, private cryptoService: CryptoService,
private i18nService: I18nService, private i18nService: I18nService,
private apiService: ApiService private userVerificationApiService: UserVerificationApiServiceAbstraction
) {} ) {}
/** /**
@@ -56,7 +56,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
if (verification.type === VerificationType.OTP) { if (verification.type === VerificationType.OTP) {
const request = new VerifyOTPRequest(verification.secret); const request = new VerifyOTPRequest(verification.secret);
try { try {
await this.apiService.postAccountVerifyOTP(request); await this.userVerificationApiService.postAccountVerifyOTP(request);
} catch (e) { } catch (e) {
throw new Error(this.i18nService.t("invalidVerificationCode")); throw new Error(this.i18nService.t("invalidVerificationCode"));
} }
@@ -73,7 +73,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
} }
async requestOTP() { async requestOTP() {
await this.apiService.postAccountRequestOTP(); await this.userVerificationApiService.postAccountRequestOTP();
} }
private validateInput(verification: Verification) { private validateInput(verification: Verification) {

View File

@@ -9,7 +9,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
private crypto: Crypto; private crypto: Crypto;
private subtle: SubtleCrypto; private subtle: SubtleCrypto;
constructor(win: Window) { constructor(win: Window | typeof global) {
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null; this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
this.subtle = this.subtle =
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null; !!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;

View File

@@ -0,0 +1,12 @@
import { VerificationType } from "../enums/verificationType";
import { TwoFactorResponse } from "./twoFactorResponse";
export type AuthResponseBase = {
secret: string;
verificationType: VerificationType;
};
export type AuthResponse<T extends TwoFactorResponse> = AuthResponseBase & {
response: T;
};

View File

@@ -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;

View File

@@ -5,23 +5,48 @@ import { ipcMain } from "electron";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; 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 // 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<T extends string> {
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 { export class ElectronStorageService implements AbstractStorageService {
private store: any; private store: ElectronStore;
constructor(dir: string, defaults = {}) { constructor(dir: string, defaults = {}) {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, "700"); NodeUtils.mkdirpSync(dir, "700");
} }
const storeConfig: any = { const storeConfig: ElectronStoreOptions = {
defaults: defaults, defaults: defaults,
name: "data", name: "data",
}; };
this.store = new Store(storeConfig); this.store = new Store(storeConfig);
ipcMain.handle("storageService", (event, options) => { ipcMain.handle("storageService", (event, options: Options) => {
switch (options.action) { switch (options.action) {
case "get": case "get":
return this.get(options.key); return this.get(options.key);
@@ -45,7 +70,7 @@ export class ElectronStorageService implements AbstractStorageService {
return Promise.resolve(val != null); return Promise.resolve(val != null);
} }
save(key: string, obj: any): Promise<any> { save(key: string, obj: unknown): Promise<void> {
if (obj instanceof Set) { if (obj instanceof Set) {
obj = Array.from(obj); obj = Array.from(obj);
} }
@@ -53,7 +78,7 @@ export class ElectronStorageService implements AbstractStorageService {
return Promise.resolve(); return Promise.resolve();
} }
remove(key: string): Promise<any> { remove(key: string): Promise<void> {
this.store.delete(key); this.store.delete(key);
return Promise.resolve(); return Promise.resolve();
} }

15
package-lock.json generated
View File

@@ -30,7 +30,6 @@
"big-integer": "^1.6.51", "big-integer": "^1.6.51",
"bootstrap": "4.6.0", "bootstrap": "4.6.0",
"braintree-web-drop-in": "^1.33.1", "braintree-web-drop-in": "^1.33.1",
"browser-hrtime": "^1.1.8",
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"commander": "^7.2.0", "commander": "^7.2.0",
@@ -83,7 +82,7 @@
"@storybook/angular": "^6.5.7", "@storybook/angular": "^6.5.7",
"@storybook/builder-webpack5": "^6.5.7", "@storybook/builder-webpack5": "^6.5.7",
"@storybook/manager-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/duo_web_sdk": "^2.7.1",
"@types/firefox-webext-browser": "^82.0.0", "@types/firefox-webext-browser": "^82.0.0",
"@types/inquirer": "^8.2.1", "@types/inquirer": "^8.2.1",
@@ -12504,9 +12503,9 @@
} }
}, },
"node_modules/@types/chrome": { "node_modules/@types/chrome": {
"version": "0.0.139", "version": "0.0.190",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==", "integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/filesystem": "*", "@types/filesystem": "*",
@@ -52084,9 +52083,9 @@
} }
}, },
"@types/chrome": { "@types/chrome": {
"version": "0.0.139", "version": "0.0.190",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==", "integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/filesystem": "*", "@types/filesystem": "*",

View File

@@ -45,7 +45,7 @@
"@storybook/angular": "^6.5.7", "@storybook/angular": "^6.5.7",
"@storybook/builder-webpack5": "^6.5.7", "@storybook/builder-webpack5": "^6.5.7",
"@storybook/manager-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/duo_web_sdk": "^2.7.1",
"@types/firefox-webext-browser": "^82.0.0", "@types/firefox-webext-browser": "^82.0.0",
"@types/inquirer": "^8.2.1", "@types/inquirer": "^8.2.1",
@@ -155,7 +155,6 @@
"big-integer": "^1.6.51", "big-integer": "^1.6.51",
"bootstrap": "4.6.0", "bootstrap": "4.6.0",
"braintree-web-drop-in": "^1.33.1", "braintree-web-drop-in": "^1.33.1",
"browser-hrtime": "^1.1.8",
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"commander": "^7.2.0", "commander": "^7.2.0",