1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +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
- name: Setup NPM
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
run: |
echo 'registry="https://registry.npmjs.org/"' > ./.npmrc
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc
env:
NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.cli-npm-api-key }}
@@ -296,5 +298,5 @@ jobs:
- name: Publish NPM
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm publish --access public
run: npm publish --access public --regsitry=https://registry.npmjs.org/ --userconfig=./.npmrc

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -14,7 +14,10 @@ export default class WebRequestBackground {
private cipherService: CipherService,
private authService: AuthService
) {
this.webRequest = (window as any).chrome.webRequest;
const manifest = chrome.runtime.getManifest();
if (manifest.manifest_version === 2) {
this.webRequest = (window as any).chrome.webRequest;
}
this.isFirefox = platformUtilsService.isFirefox();
}

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.*
7. Remove "some useful globals" on window
8. Add ability to autofill span[data-bwautofill] elements
9. Add new handler, for new command that responds with page details in response callback
*/
function collect(document, undefined) {
@@ -1037,6 +1038,11 @@
fill(document, msg.fillScript);
sendResponse();
return true;
} else if (msg.command === 'collectPageDetailsImmediately') {
var pageDetails = collect(document);
var pageDetailsObj = JSON.parse(pageDetails);
sendResponse(pageDetailsObj);
return true;
}
});
})();

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": {
"service_worker": "background.js"
"service_worker": "background.js",
"type": "module"
},
"action": {
"default_icon": {

View File

@@ -22,4 +22,5 @@ export default class AutofillField {
selectInfo: any;
maxLength: number;
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
await this.clearComponentStates();
this.stateService.activeAccount.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
this.activeUserId = userId;
});
@@ -84,7 +84,7 @@ export class AppComponent implements OnInit, OnDestroy {
});
}
if (this.stateService.activeAccount.getValue() == null) {
if (this.activeUserId === null) {
this.router.navigate(["home"]);
}
});

View File

@@ -45,7 +45,7 @@ import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { AuthService } from "@bitwarden/common/services/auth.service";

View File

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

View File

@@ -1,6 +1,6 @@
<header>
<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>{{ "back" | i18n }}</span>
</button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -176,13 +176,6 @@ const config = {
return chunk.name === "popup/main";
},
},
commons2: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: (chunk) => {
return chunk.name === "background";
},
},
},
},
},
@@ -209,4 +202,16 @@ const config = {
plugins: plugins,
};
if (manifestVersion == 2) {
// We can't use this in manifest v3
// Ideally we understand why this breaks it and we don't have to do this
config.optimization.splitChunks.cacheGroups.commons2 = {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: (chunk) => {
return chunk.name === "background";
},
};
}
module.exports = config;

View File

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

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 { ipcRenderer } from "electron";
@@ -22,7 +22,7 @@ const BroadcasterSubscriptionId = "LockComponent";
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent implements OnDestroy {
export class LockComponent extends BaseLockComponent {
private deferFocus: boolean = null;
authenicatedUrl = "vault";
unAuthenicatedUrl = "update-temp-password";
@@ -103,6 +103,7 @@ export class LockComponent extends BaseLockComponent implements OnDestroy {
}
ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
const BroadcasterSubscriptionId = "ExportComponent";

View File

@@ -100,7 +100,7 @@
<div
class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode"
*ngIf="cipher.login.totp && totpCode && canAccessPremium"
>
<div class="row-main">
<span
@@ -138,6 +138,19 @@
</button>
</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>
<!-- 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": {
"message": "API Key"
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": {
"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 { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
@Component({
selector: "app-update-password",

View File

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

View File

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

View File

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

View File

@@ -77,7 +77,12 @@
</button>
</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>
</div>
</div>

View File

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

View File

@@ -6,7 +6,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipherView";
@@ -53,10 +53,10 @@ export class DeleteOrganizationComponent implements OnInit {
deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete";
organizationName: string;
organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary();
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
@Output() onSuccess: EventEmitter<unknown> = new EventEmitter();
masterPassword: Verification;
formPromise: Promise<any>;
formPromise: Promise<unknown>;
constructor(
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { EventType } from "@bitwarden/common/enums/eventType";
import { ExportComponent } from "../../../tools/import-export/export.component";
@@ -66,7 +66,7 @@ export class OrganizationExportComponent extends ExportComponent {
return super.getFileName("org");
}
async collectEvent(): Promise<any> {
async collectEvent(): Promise<void> {
await this.eventService.collect(
EventType.Organization_ClientExportedVault,
null,

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { Component } from "@angular/core";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { ApiKeyResponse } from "@bitwarden/common/models/response/apiKeyResponse";
import { Verification } from "@bitwarden/common/types/verification";

View File

@@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { Verification } from "@bitwarden/common/types/verification";
@Component({
@@ -14,7 +14,7 @@ import { Verification } from "@bitwarden/common/types/verification";
})
export class DeauthorizeSessionsComponent {
masterPassword: Verification;
formPromise: Promise<any>;
formPromise: Promise<unknown>;
constructor(
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 { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { Verification } from "@bitwarden/common/types/verification";
@Component({
@@ -16,7 +16,7 @@ export class PurgeVaultComponent {
@Input() organizationId?: string = null;
masterPassword: Verification;
formPromise: Promise<any>;
formPromise: Promise<unknown>;
constructor(
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/models/request/updateTwoFactorAuthenticatorRequest";
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
// NOTE: There are additional options available but these are just the ones we are current using.
// See: https://github.com/neocotic/qrious#examples
interface QRiousOptions {
element: HTMLElement;
value: string;
size: number;
}
declare global {
interface Window {
QRious: new (options: QRiousOptions) => unknown;
}
}
@Component({
selector: "app-two-factor-authenticator",
templateUrl: "two-factor-authenticator.component.html",
@@ -23,7 +38,7 @@ export class TwoFactorAuthenticatorComponent
type = TwoFactorProviderType.Authenticator;
key: string;
token: string;
formPromise: Promise<any>;
formPromise: Promise<TwoFactorAuthenticatorResponse>;
private qrScript: HTMLScriptElement;
@@ -49,7 +64,7 @@ export class TwoFactorAuthenticatorComponent
window.document.body.removeChild(this.qrScript);
}
auth(authResponse: any) {
auth(authResponse: AuthResponse<TwoFactorAuthenticatorResponse>) {
super.auth(authResponse);
return this.processResponse(authResponse.response);
}
@@ -80,7 +95,7 @@ export class TwoFactorAuthenticatorComponent
this.key = response.key;
const email = await this.stateService.getEmail();
window.setTimeout(() => {
new (window as any).QRious({
new window.QRious({
element: document.getElementById("qr"),
value:
"otpauth://totp/Bitwarden:" +

View File

@@ -4,11 +4,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { TwoFactorProviderRequest } from "@bitwarden/common/models/request/twoFactorProviderRequest";
import { AuthResponseBase } from "@bitwarden/common/types/authResponse";
@Directive()
export abstract class TwoFactorBaseComponent {
@@ -31,7 +32,7 @@ export abstract class TwoFactorBaseComponent {
protected userVerificationService: UserVerificationService
) {}
protected auth(authResponse: any) {
protected auth(authResponse: AuthResponseBase) {
this.hashedSecret = authResponse.secret;
this.verificationType = authResponse.verificationType;
this.authed = true;
@@ -46,7 +47,7 @@ export abstract class TwoFactorBaseComponent {
}
}
protected async disable(promise: Promise<any>) {
protected async disable(promise: Promise<unknown>) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("twoStepDisableDesc"),
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 { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/models/request/updateTwoFactorDuoRequest";
import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -20,7 +21,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
ikey: string;
skey: string;
host: string;
formPromise: Promise<any>;
formPromise: Promise<TwoFactorDuoResponse>;
constructor(
apiService: ApiService,
@@ -32,7 +33,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
}
auth(authResponse: any) {
auth(authResponse: AuthResponse<TwoFactorDuoResponse>) {
super.auth(authResponse);
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/twoFactorEmailRequest";
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/models/request/updateTwoFactorEmailRequest";
import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -22,8 +23,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
email: string;
token: string;
sentEmail: string;
formPromise: Promise<any>;
emailPromise: Promise<any>;
formPromise: Promise<TwoFactorEmailResponse>;
emailPromise: Promise<unknown>;
constructor(
apiService: ApiService,
@@ -36,7 +37,7 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
}
auth(authResponse: any) {
auth(authResponse: AuthResponse<TwoFactorEmailResponse>) {
super.auth(authResponse);
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 { LogService } from "@bitwarden/common/abstractions/log.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/models/response/twoFactorAuthenticatorResponse";
import { TwoFactorDuoResponse } from "@bitwarden/common/models/response/twoFactorDuoResponse";
import { TwoFactorEmailResponse } from "@bitwarden/common/models/response/twoFactorEmailResponse";
import { TwoFactorRecoverResponse } from "@bitwarden/common/models/response/twoFactorRescoverResponse";
import { TwoFactorWebAuthnResponse } from "@bitwarden/common/models/response/twoFactorWebAuthnResponse";
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/models/response/twoFactorYubiKeyResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorResponse } from "@bitwarden/common/types/twoFactorResponse";
import { Verification } from "@bitwarden/common/types/verification";
type TwoFactorResponse =
| TwoFactorRecoverResponse
| TwoFactorDuoResponse
| TwoFactorEmailResponse
| TwoFactorWebAuthnResponse
| TwoFactorAuthenticatorResponse
| TwoFactorYubiKeyResponse;
@Component({
selector: "app-two-factor-verify",
templateUrl: "two-factor-verify.component.html",
@@ -29,7 +17,7 @@ type TwoFactorResponse =
export class TwoFactorVerifyComponent {
@Input() type: TwoFactorProviderType;
@Input() organizationId: string;
@Output() onAuthed = new EventEmitter<any>();
@Output() onAuthed = new EventEmitter<AuthResponse<TwoFactorResponse>>();
secret: Verification;
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 { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnDeleteRequest";
@@ -13,9 +13,18 @@ import {
ChallengeResponse,
TwoFactorWebAuthnResponse,
} from "@bitwarden/common/models/response/twoFactorWebAuthnResponse";
import { AuthResponse } from "@bitwarden/common/types/authResponse";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
interface Key {
id: number;
name: string;
configured: boolean;
migrated?: boolean;
removePromise: Promise<TwoFactorWebAuthnResponse> | null;
}
@Component({
selector: "app-two-factor-webauthn",
templateUrl: "two-factor-webauthn.component.html",
@@ -23,14 +32,14 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component";
export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.WebAuthn;
name: string;
keys: any[];
keys: Key[];
keyIdAvailable: number = null;
keysConfiguredCount = 0;
webAuthnError: boolean;
webAuthnListening: boolean;
webAuthnResponse: PublicKeyCredential;
challengePromise: Promise<ChallengeResponse>;
formPromise: Promise<any>;
formPromise: Promise<TwoFactorWebAuthnResponse>;
constructor(
apiService: ApiService,
@@ -43,7 +52,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
}
auth(authResponse: any) {
auth(authResponse: AuthResponse<TwoFactorWebAuthnResponse>) {
super.auth(authResponse);
this.processResponse(authResponse.response);
}
@@ -69,11 +78,11 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
return super.disable(this.formPromise);
}
async remove(key: any) {
async remove(key: Key) {
if (this.keysConfiguredCount <= 1 || key.removePromise != null) {
return;
}
const name = key.name != null ? key.name : this.i18nService.t("webAuthnkeyX", key.id);
const name = key.name != null ? key.name : this.i18nService.t("webAuthnkeyX", key.id as any);
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removeU2fConfirmation"),
name,

View File

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

View File

@@ -10,7 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
templateUrl: "verify-email.component.html",
})
export class VerifyEmailComponent {
actionPromise: Promise<any>;
actionPromise: Promise<unknown>;
constructor(
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
@Component({
selector: "app-export",

View File

@@ -166,8 +166,8 @@
</div>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<div class="tw-flex tw-flex-row">
<div class="tw-mb-4 tw-w-1/2">
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
id="loginTotp"
@@ -179,14 +179,41 @@
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly"
/>
</div>
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{ low: totpLow }">
<div *ngIf="!cipher.login.totp || !totpCode">
<img
src="../../images/totp-countdown.png"
id="totpImage"
<div class="tw-mb-4 tw-ml-4 tw-flex tw-w-1/2 tw-items-end" [ngClass]="{ low: totpLow }">
<div
class="totp tw-flex tw-flex-row tw-items-center"
*ngIf="!cipher.login.totp || (cipher.login.totp && !canAccessPremium)"
>
<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 }}"
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
*ngIf="!organization && !cipher.organizationId"
class="ml-3"
@@ -209,8 +236,11 @@
{{ "upgrade" | i18n }}
</a>
</div>
<div *ngIf="cipher.login.totp && totpCode" class="d-flex align-items-center">
<span class="totp-countdown mr-3 ml-2">
<div
*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>
<svg>
<g>
@@ -225,16 +255,18 @@
</g>
</svg>
</span>
<span class="totp-code mr-2" title="{{ 'verificationCodeTotp' | i18n }}">{{
totpCodeFormatted
}}</span>
<span
class="totp-code tw-mr-2 tw-ml-2 tw-mt-1"
title="{{ 'verificationCodeTotp' | i18n }}"
>{{ totpCodeFormatted }}</span
>
<button
type="button"
class="btn btn-link"
class="tw-items-center tw-border-none tw-bg-transparent tw-text-primary-500"
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(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>
</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() {
this.viewingPasswordHistory = !this.viewingPasswordHistory;
}

View File

@@ -142,6 +142,7 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="loaded">
<img class="no-items-image" aria-hidden="true" />
<p>{{ "noItemsInList" | i18n }}</p>
<button (click)="addCipher()" class="btn btn-outline-primary" *ngIf="showAddNew">
<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": {
"message": "SCIM Provisioning",
"description": "The text, 'SCIM', is an acronymn and should not be translated."

View File

@@ -306,3 +306,18 @@ button i.bwi,
a i.bwi {
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;
}
}
.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 {

View File

@@ -219,6 +219,7 @@ $themes: (
textMuted: #6c757d,
textSuccessColor: $white,
textWarningColor: $white,
svgSuffix: "-light.svg",
),
dark: (
primary: $darkPrimary,
@@ -330,6 +331,7 @@ $themes: (
textMuted: $darkGrey1,
textSuccessColor: $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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
@@ -142,7 +142,7 @@ export class ExportComponent implements OnInit {
return this.exportService.getFileName(prefix, extension);
}
protected async collectEvent(): Promise<any> {
protected async collectEvent(): Promise<void> {
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 { Subscription } from "rxjs";
import { take } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -20,7 +21,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCry
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
@Directive()
export class LockComponent implements OnInit {
export class LockComponent implements OnInit, OnDestroy {
masterPassword = "";
pin = "";
showPassword = false;
@@ -39,6 +40,8 @@ export class LockComponent implements OnInit {
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
private activeAccountSubscription: Subscription;
constructor(
protected router: Router,
protected i18nService: I18nService,
@@ -57,11 +60,15 @@ export class LockComponent implements OnInit {
async ngOnInit() {
// Load the first and observe updates
await this.load();
this.stateService.activeAccount.subscribe(async () => {
this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => {
await this.load();
});
}
ngOnDestroy() {
this.activeAccountSubscription.unsubscribe();
}
async submit() {
if (this.pinLock && (this.pin == null || this.pin === "")) {
this.platformUtilsService.showToast(

View File

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

View File

@@ -1,9 +1,9 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { ControlValueAccessor, UntypedFormControl, NG_VALUE_ACCESSOR } from "@angular/forms";
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from "@angular/forms";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { VerificationType } from "@bitwarden/common/enums/verificationType";
import { Utils } from "@bitwarden/common/misc/utils";
import { Verification } from "@bitwarden/common/types/verification";
@@ -35,7 +35,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
disableRequestOTP = false;
sentCode = false;
secret = new UntypedFormControl("");
secret = new FormControl("");
private onChange: (value: Verification) => void;

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

View File

@@ -78,25 +78,4 @@ describe("ConsoleLogService", () => {
error: { 0: "this is an error message" },
});
});
it("times with output to info", async () => {
logService.time();
await new Promise((r) => setTimeout(r, 250));
const duration = logService.timeEnd();
expect(duration[0]).toBe(0);
expect(duration[1]).toBeGreaterThan(0);
expect(duration[1]).toBeLessThan(500 * 10e6);
expect(caughtMessage).toEqual(expect.arrayContaining([]));
expect(caughtMessage.log.length).toBe(1);
expect(caughtMessage.log[0]).toEqual(expect.stringMatching(/^default: \d+\.?\d*ms$/));
});
it("filters time output", async () => {
logService = new ConsoleLogService(true, () => true);
logService.time();
logService.timeEnd();
expect(caughtMessage).toEqual({});
});
});

View File

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

View File

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

View File

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

View File

@@ -26,9 +26,8 @@ import { SendView } from "../models/view/sendView";
export abstract class StateService<T extends Account = Account> {
accounts: BehaviorSubject<{ [userId: string]: T }>;
activeAccount: BehaviorSubject<string>;
activeAccountUnlocked: Observable<boolean>;
activeAccount$: Observable<string>;
activeAccountUnlocked$: Observable<boolean>;
addAccount: (account: T) => 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 { Verification } from "../types/verification";
import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
import { Verification } from "../../types/verification";
export abstract class UserVerificationService {
buildRequest: <T extends SecretVerificationRequest>(

View File

@@ -1,17 +1,28 @@
/* eslint-disable no-useless-escape */
import * as tldjs from "tldjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "../abstractions/i18n.service";
const nodeURL = typeof window === "undefined" ? require("url") : null;
declare global {
/* eslint-disable-next-line no-var */
var bitwardenContainerService: BitwardenContainerService;
}
interface BitwardenContainerService {
getCryptoService: () => CryptoService;
}
export class Utils {
static inited = false;
static isNode = false;
static isBrowser = true;
static isMobileBrowser = false;
static isAppleMobileBrowser = false;
static global: any = null;
static global: typeof global = null;
static tldEndingRegex =
/.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/;
// Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers.
@@ -29,16 +40,25 @@ export class Utils {
(process as any).release != null &&
(process as any).release.name === "node";
Utils.isBrowser = typeof window !== "undefined";
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window);
Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window);
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
if (Utils.isNode) {
Utils.global = global;
} else if (Utils.isBrowser) {
Utils.global = window;
} else {
// If it's not browser or node then it must be a service worker
Utils.global = self;
}
}
static fromB64ToArray(str: string): Uint8Array {
if (Utils.isNode) {
return new Uint8Array(Buffer.from(str, "base64"));
} else {
const binaryString = window.atob(str);
const binaryString = Utils.global.atob(str);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
@@ -93,7 +113,7 @@ export class Utils {
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
return Utils.global.btoa(binary);
}
}
@@ -157,7 +177,7 @@ export class Utils {
if (Utils.isNode) {
return Buffer.from(utfStr, "utf8").toString("base64");
} else {
return decodeURIComponent(escape(window.btoa(utfStr)));
return decodeURIComponent(escape(Utils.global.btoa(utfStr)));
}
}
@@ -169,7 +189,7 @@ export class Utils {
if (Utils.isNode) {
return Buffer.from(b64Str, "base64").toString("utf8");
} else {
return decodeURIComponent(escape(window.atob(b64Str)));
return decodeURIComponent(escape(Utils.global.atob(b64Str)));
}
}
@@ -384,7 +404,7 @@ export class Utils {
return new nodeURL.URL(uriString);
} else if (typeof URL === "function") {
return new URL(uriString);
} else if (window != null) {
} else if (typeof window !== "undefined") {
const hasProtocol = uriString.indexOf("://") > -1;
if (!hasProtocol && uriString.indexOf(".") > -1) {
uriString = "http://" + uriString;

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import { OrganizationApiKeyType } from "../enums/organizationApiKeyType";
import { OrganizationConnectionType } from "../enums/organizationConnectionType";
import { Utils } from "../misc/utils";
import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest";
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest";
import { AttachmentRequest } from "../models/request/attachmentRequest";
import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest";
import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest";
@@ -457,14 +456,6 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("PUT", "/accounts/update-temp-password", request, true, false);
}
postAccountRequestOTP(): Promise<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> {
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.");
}
const promises: any[] = [];
const promises: Promise<number>[] = [];
const ciphers = await this.getAll();
ciphers.forEach(async (cipher) => {
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 { LogLevelType } from "../enums/logLevelType";
@@ -56,17 +54,4 @@ export class ConsoleLogService implements LogServiceAbstraction {
break;
}
}
time(label = "default") {
if (!this.timersMap.has(label)) {
this.timersMap.set(label, hrtime());
}
}
timeEnd(label = "default"): [number, number] {
const elapsed = hrtime(this.timersMap.get(label));
this.timersMap.delete(label);
this.write(LogLevelType.Info, `${label}: ${elapsed[0] * 1000 + elapsed[1] / 10e6}ms`);
return elapsed;
}
}

View File

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

View File

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

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";
export class SearchService implements SearchServiceAbstraction {
private static registeredPipeline = false;
indexedEntityId?: string = null;
private indexing = false;
private index: lunr.Index = null;
@@ -31,8 +33,13 @@ export class SearchService implements SearchServiceAbstraction {
}
});
//register lunr pipeline function
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
// Currently have to ensure this is only done a single time. Lunr allows you to register a function
// multiple times but they will add a warning message to the console. The way they do that breaks when ran on a service worker.
if (!SearchService.registeredPipeline) {
SearchService.registeredPipeline = true;
//register lunr pipeline function
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
}
}
clearIndex(): void {
@@ -54,7 +61,6 @@ export class SearchService implements SearchServiceAbstraction {
return;
}
this.logService.time("search indexing");
this.indexing = true;
this.indexedEntityId = indexedEntityId;
this.index = null;
@@ -95,7 +101,7 @@ export class SearchService implements SearchServiceAbstraction {
this.indexing = false;
this.logService.timeEnd("search indexing");
this.logService.info("Finished search indexing");
}
async searchCiphers(

View File

@@ -55,8 +55,11 @@ export class StateService<
> implements StateServiceAbstraction<TAccount>
{
accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({});
activeAccount = new BehaviorSubject<string>(null);
activeAccountUnlocked = new BehaviorSubject<boolean>(false);
private activeAccountSubject = new BehaviorSubject<string>(null);
activeAccount$ = this.activeAccountSubject.asObservable();
private activeAccountUnlockedSubject = new BehaviorSubject<boolean>(false);
activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable();
private hasBeenInited = false;
private isRecoveredSession = false;
@@ -73,17 +76,17 @@ export class StateService<
protected useAccountCache: boolean = true
) {
// If the account gets changed, verify the new account is unlocked
this.activeAccount.subscribe(async (userId) => {
if (userId == null && this.activeAccountUnlocked.getValue() == false) {
this.activeAccountSubject.subscribe(async (userId) => {
if (userId == null && this.activeAccountUnlockedSubject.getValue() == false) {
return;
} else if (userId == null) {
this.activeAccountUnlocked.next(false);
this.activeAccountUnlockedSubject.next(false);
}
// FIXME: This should be refactored into AuthService or a similar service,
// as checking for the existance of the crypto key is a low level
// implementation detail.
this.activeAccountUnlocked.next((await this.getCryptoMasterKey()) != null);
this.activeAccountUnlockedSubject.next((await this.getCryptoMasterKey()) != null);
});
}
@@ -125,7 +128,7 @@ export class StateService<
state.activeUserId = storedActiveUser;
}
await this.pushAccounts();
this.activeAccount.next(state.activeUserId);
this.activeAccountSubject.next(state.activeUserId);
return state;
});
@@ -154,7 +157,7 @@ export class StateService<
await this.scaffoldNewAccountStorage(account);
await this.setLastActive(new Date().getTime(), { userId: account.profile.userId });
await this.setActiveUser(account.profile.userId);
this.activeAccount.next(account.profile.userId);
this.activeAccountSubject.next(account.profile.userId);
}
async setActiveUser(userId: string): Promise<void> {
@@ -162,7 +165,7 @@ export class StateService<
await this.updateState(async (state) => {
state.activeUserId = userId;
await this.storageService.save(keys.activeUserId, userId);
this.activeAccount.next(state.activeUserId);
this.activeAccountSubject.next(state.activeUserId);
return state;
});
@@ -497,12 +500,12 @@ export class StateService<
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
if (options.userId == this.activeAccount.getValue()) {
if (options.userId == this.activeAccountSubject.getValue()) {
const nextValue = value != null;
// Avoid emitting if we are already unlocked
if (this.activeAccountUnlocked.getValue() != nextValue) {
this.activeAccountUnlocked.next(nextValue);
if (this.activeAccountUnlockedSubject.getValue() != nextValue) {
this.activeAccountUnlockedSubject.next(nextValue);
}
}
}
@@ -607,7 +610,7 @@ export class StateService<
);
}
@withPrototypeForArrayMembers(CipherView)
@withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
return (
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 { I18nService } from "../abstractions/i18n.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../abstractions/userVerification.service";
import { VerificationType } from "../enums/verificationType";
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest";
import { SecretVerificationRequest } from "../models/request/secretVerificationRequest";
import { Verification } from "../types/verification";
import { CryptoService } from "../../abstractions/crypto.service";
import { I18nService } from "../../abstractions/i18n.service";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/userVerification/userVerification.service.abstraction";
import { VerificationType } from "../../enums/verificationType";
import { VerifyOTPRequest } from "../../models/request/account/verifyOTPRequest";
import { SecretVerificationRequest } from "../../models/request/secretVerificationRequest";
import { Verification } from "../../types/verification";
/**
* Used for general-purpose user verification throughout the app.
@@ -15,7 +15,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
constructor(
private cryptoService: CryptoService,
private i18nService: I18nService,
private apiService: ApiService
private userVerificationApiService: UserVerificationApiServiceAbstraction
) {}
/**
@@ -56,7 +56,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
if (verification.type === VerificationType.OTP) {
const request = new VerifyOTPRequest(verification.secret);
try {
await this.apiService.postAccountVerifyOTP(request);
await this.userVerificationApiService.postAccountVerifyOTP(request);
} catch (e) {
throw new Error(this.i18nService.t("invalidVerificationCode"));
}
@@ -73,7 +73,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
}
async requestOTP() {
await this.apiService.postAccountRequestOTP();
await this.userVerificationApiService.postAccountRequestOTP();
}
private validateInput(verification: Verification) {

View File

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

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 { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
// See: https://github.com/sindresorhus/electron-store/blob/main/index.d.ts
interface ElectronStoreOptions {
defaults: unknown;
name: string;
}
type ElectronStoreConstructor = new (options: ElectronStoreOptions) => ElectronStore;
// eslint-disable-next-line
const Store = require("electron-store");
const Store: ElectronStoreConstructor = require("electron-store");
interface ElectronStore {
get: (key: string) => unknown;
set: (key: string, obj: unknown) => void;
delete: (key: string) => void;
}
interface BaseOptions<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 {
private store: any;
private store: ElectronStore;
constructor(dir: string, defaults = {}) {
if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, "700");
}
const storeConfig: any = {
const storeConfig: ElectronStoreOptions = {
defaults: defaults,
name: "data",
};
this.store = new Store(storeConfig);
ipcMain.handle("storageService", (event, options) => {
ipcMain.handle("storageService", (event, options: Options) => {
switch (options.action) {
case "get":
return this.get(options.key);
@@ -45,7 +70,7 @@ export class ElectronStorageService implements AbstractStorageService {
return Promise.resolve(val != null);
}
save(key: string, obj: any): Promise<any> {
save(key: string, obj: unknown): Promise<void> {
if (obj instanceof Set) {
obj = Array.from(obj);
}
@@ -53,7 +78,7 @@ export class ElectronStorageService implements AbstractStorageService {
return Promise.resolve();
}
remove(key: string): Promise<any> {
remove(key: string): Promise<void> {
this.store.delete(key);
return Promise.resolve();
}

15
package-lock.json generated
View File

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

View File

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