diff --git a/apps/browser/src/background.ts b/apps/browser/src/background.ts index ebc2aad10ad..f55cc4b1962 100644 --- a/apps/browser/src/background.ts +++ b/apps/browser/src/background.ts @@ -1,12 +1,25 @@ import MainBackground from "./background/main.background"; +import { ClearClipboard } from "./clipboard"; import { onCommandListener } from "./listeners/onCommandListener"; import { onInstallListener } from "./listeners/onInstallListener"; +type AlarmAction = (executionTime: Date, serviceCache: Record) => void; + +const AlarmActions: AlarmAction[] = [ClearClipboard.run]; + const manifest = chrome.runtime.getManifest(); if (manifest.manifest_version === 3) { chrome.commands.onCommand.addListener(onCommandListener); chrome.runtime.onInstalled.addListener(onInstallListener); + chrome.alarms.onAlarm.addListener((_alarm) => { + const executionTime = new Date(); + const serviceCache = {}; + + for (const alarmAction of AlarmActions) { + alarmAction(executionTime, serviceCache); + } + }); } else { const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); bitwardenMain.bootstrap().then(() => { diff --git a/apps/browser/src/background/service_factories/crypto-service.factory.ts b/apps/browser/src/background/service_factories/crypto-service.factory.ts index b61b72ec04b..784314b12d2 100644 --- a/apps/browser/src/background/service_factories/crypto-service.factory.ts +++ b/apps/browser/src/background/service_factories/crypto-service.factory.ts @@ -1,5 +1,6 @@ import { CryptoService as AbstractCryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { CryptoService } from "@bitwarden/common/services/crypto.service"; + +import { BrowserCryptoService } from "../../services/browserCrypto.service"; import { cryptoFunctionServiceFactory, @@ -32,7 +33,7 @@ export function cryptoServiceFactory( "cryptoService", opts, async () => - new CryptoService( + new BrowserCryptoService( await cryptoFunctionServiceFactory(cache, opts), await encryptServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts), diff --git a/apps/browser/src/background/service_factories/password-generation-service.factory.ts b/apps/browser/src/background/service_factories/password-generation-service.factory.ts new file mode 100644 index 00000000000..d69f22401c8 --- /dev/null +++ b/apps/browser/src/background/service_factories/password-generation-service.factory.ts @@ -0,0 +1,31 @@ +import { PasswordGenerationService as AbstractPasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; +import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service"; + +import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory"; +import { CachedServices, factory, FactoryOptions } from "./factory-options"; +import { policyServiceFactory, PolicyServiceInitOptions } from "./policy-service.factory"; +import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; + +type PasswordGenerationServiceFactoryOptions = FactoryOptions; + +export type PasswordGenerationServiceInitOptions = PasswordGenerationServiceFactoryOptions & + CryptoServiceInitOptions & + PolicyServiceInitOptions & + StateServiceInitOptions; + +export function passwordGenerationServiceFactory( + cache: { passwordGenerationService?: AbstractPasswordGenerationService } & CachedServices, + opts: PasswordGenerationServiceInitOptions +): Promise { + return factory( + cache, + "passwordGenerationService", + opts, + async () => + new PasswordGenerationService( + await cryptoServiceFactory(cache, opts), + await policyServiceFactory(cache, opts), + await stateServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/browser/browserApi.ts b/apps/browser/src/browser/browserApi.ts index 7b0152a9bdb..363adb43750 100644 --- a/apps/browser/src/browser/browserApi.ts +++ b/apps/browser/src/browser/browserApi.ts @@ -1,3 +1,5 @@ +import { TabMessage } from "../types/tab-messages"; + export class BrowserApi { static isWebExtensionsApi: boolean = typeof browser !== "undefined"; static isSafariApi: boolean = @@ -80,6 +82,14 @@ export class BrowserApi { }); } + static sendTabsMessage( + tabId: number, + message: TabMessage, + responseCallback?: (response: T) => void + ) { + chrome.tabs.sendMessage(tabId, message, responseCallback); + } + static async getPrivateModeWindows(): Promise { return (await browser.windows.getAll()).filter((win) => win.incognito); } diff --git a/apps/browser/src/clipboard/clearClipboard.spec.ts b/apps/browser/src/clipboard/clearClipboard.spec.ts new file mode 100644 index 00000000000..3e7d4e36712 --- /dev/null +++ b/apps/browser/src/clipboard/clearClipboard.spec.ts @@ -0,0 +1,79 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { BrowserApi } from "../browser/browserApi"; +import { StateService } from "../services/abstractions/state.service"; + +import { ClearClipboard } from "./clearClipboard"; +import { getClearClipboardTime, setClearClipboardTime } from "./clipboard-state"; + +jest.mock("./clipboard-state", () => { + return { + getClearClipboardTime: jest.fn(), + setClearClipboardTime: jest.fn(), + }; +}); + +const getClearClipboardTimeMock = getClearClipboardTime as jest.Mock; +const setClearClipboardTimeMock = setClearClipboardTime as jest.Mock; + +describe("clearClipboard", () => { + describe("run", () => { + let stateService: MockProxy; + let serviceCache: Record; + + beforeEach(() => { + stateService = mock(); + serviceCache = { + stateService: stateService, + }; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("has a clear time that is past execution time", async () => { + const executionTime = new Date(2022, 1, 1, 12); + const clearTime = new Date(2022, 1, 1, 12, 1); + + jest.spyOn(BrowserApi, "getActiveTabs").mockResolvedValue([ + { + id: 1, + }, + ] as any); + + jest.spyOn(BrowserApi, "sendTabsMessage").mockReturnValue(); + + getClearClipboardTimeMock.mockResolvedValue(clearTime.getTime()); + + await ClearClipboard.run(executionTime, serviceCache); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledTimes(1); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledWith(1, { + command: "clearClipboard", + }); + }); + + it("has a clear time before execution time", async () => { + const executionTime = new Date(2022, 1, 1, 12); + const clearTime = new Date(2022, 1, 1, 11); + + setClearClipboardTimeMock.mockResolvedValue(clearTime.getTime()); + + await ClearClipboard.run(executionTime, serviceCache); + + expect(jest.spyOn(BrowserApi, "getActiveTabs")).not.toHaveBeenCalled(); + }); + + it("has an undefined clearTime", async () => { + const executionTime = new Date(2022, 1, 1); + + getClearClipboardTimeMock.mockResolvedValue(undefined); + + await ClearClipboard.run(executionTime, serviceCache); + + expect(jest.spyOn(BrowserApi, "getActiveTabs")).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/clipboard/clearClipboard.ts b/apps/browser/src/clipboard/clearClipboard.ts new file mode 100644 index 00000000000..00bf329f8d3 --- /dev/null +++ b/apps/browser/src/clipboard/clearClipboard.ts @@ -0,0 +1,50 @@ +import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { GlobalState } from "@bitwarden/common/models/domain/global-state"; + +import { stateServiceFactory } from "../background/service_factories/state-service.factory"; +import { BrowserApi } from "../browser/browserApi"; +import { Account } from "../models/account"; + +import { getClearClipboardTime } from "./clipboard-state"; + +export class ClearClipboard { + static async run(executionTime: Date, serviceCache: Record) { + const stateFactory = new StateFactory(GlobalState, Account); + const stateService = await stateServiceFactory(serviceCache, { + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: false, + }, + logServiceOptions: { + isDev: false, + }, + stateMigrationServiceOptions: { + stateFactory: stateFactory, + }, + stateServiceOptions: { + stateFactory: stateFactory, + }, + }); + + const clearClipboardTime = await getClearClipboardTime(stateService); + + if (!clearClipboardTime) { + return; + } + + if (clearClipboardTime < executionTime.getTime()) { + return; + } + + const activeTabs = await BrowserApi.getActiveTabs(); + if (!activeTabs || activeTabs.length === 0) { + return; + } + + BrowserApi.sendTabsMessage(activeTabs[0].id, { + command: "clearClipboard", + }); + } +} diff --git a/apps/browser/src/clipboard/clipboard-state.ts b/apps/browser/src/clipboard/clipboard-state.ts new file mode 100644 index 00000000000..a1c15addc0a --- /dev/null +++ b/apps/browser/src/clipboard/clipboard-state.ts @@ -0,0 +1,10 @@ +import { StateService } from "../services/abstractions/state.service"; + +const clearClipboardStorageKey = "clearClipboardTime"; +export const getClearClipboardTime = async (stateService: StateService) => { + return await stateService.getFromSessionMemory(clearClipboardStorageKey); +}; + +export const setClearClipboardTime = async (stateService: StateService, time: number) => { + await stateService.setInSessionMemory(clearClipboardStorageKey, time); +}; diff --git a/apps/browser/src/clipboard/copy-to-clipboard-command.ts b/apps/browser/src/clipboard/copy-to-clipboard-command.ts new file mode 100644 index 00000000000..e1f2cca3f2c --- /dev/null +++ b/apps/browser/src/clipboard/copy-to-clipboard-command.ts @@ -0,0 +1,17 @@ +import { BrowserApi } from "../browser/browserApi"; + +/** + * Copies text to the clipboard in a MV3 safe way. + * @param tab - The tab that the text will be sent to so that it can be copied to the users clipboard this needs to be an active tab or the DOM won't be able to be used to do the action. The tab sent in here should be from a user started action or queried for active tabs. + * @param text - The text that you want added to the users clipboard. + */ +export const copyToClipboard = async (tab: chrome.tabs.Tab, text: string) => { + if (tab.id == null) { + throw new Error("Cannot copy text to clipboard with a tab that does not have an id."); + } + + BrowserApi.sendTabsMessage(tab.id, { + command: "copyText", + text: text, + }); +}; diff --git a/apps/browser/src/clipboard/generate-password-to-clipboard-command.spec.ts b/apps/browser/src/clipboard/generate-password-to-clipboard-command.spec.ts new file mode 100644 index 00000000000..e9c2141211f --- /dev/null +++ b/apps/browser/src/clipboard/generate-password-to-clipboard-command.spec.ts @@ -0,0 +1,76 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; + +import { BrowserApi } from "../browser/browserApi"; +import { StateService } from "../services/abstractions/state.service"; + +import { setClearClipboardTime } from "./clipboard-state"; +import { GeneratePasswordToClipboardCommand } from "./generate-password-to-clipboard-command"; + +jest.mock("./clipboard-state", () => { + return { + getClearClipboardTime: jest.fn(), + setClearClipboardTime: jest.fn(), + }; +}); + +const setClearClipboardTimeMock = setClearClipboardTime as jest.Mock; + +describe("GeneratePasswordToClipboardCommand", () => { + let passwordGenerationService: MockProxy; + let stateService: MockProxy; + + let sut: GeneratePasswordToClipboardCommand; + + beforeEach(() => { + passwordGenerationService = mock(); + stateService = mock(); + + passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]); + + passwordGenerationService.generatePassword.mockResolvedValue("PASSWORD"); + + jest.spyOn(BrowserApi, "sendTabsMessage").mockReturnValue(); + + sut = new GeneratePasswordToClipboardCommand(passwordGenerationService, stateService); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("generatePasswordToClipboard", () => { + it("has clear clipboard value", async () => { + stateService.getClearClipboard.mockResolvedValue(5 * 60); // 5 minutes + + await sut.generatePasswordToClipboard({ id: 1 } as any); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledTimes(1); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledWith(1, { + command: "copyText", + text: "PASSWORD", + }); + + expect(setClearClipboardTimeMock).toHaveBeenCalledTimes(1); + + expect(setClearClipboardTimeMock).toHaveBeenCalledWith(stateService, expect.any(Number)); + }); + + it("does not have clear clipboard value", async () => { + stateService.getClearClipboard.mockResolvedValue(null); + + await sut.generatePasswordToClipboard({ id: 1 } as any); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledTimes(1); + + expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledWith(1, { + command: "copyText", + text: "PASSWORD", + }); + + expect(setClearClipboardTimeMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/clipboard/generate-password-to-clipboard-command.ts b/apps/browser/src/clipboard/generate-password-to-clipboard-command.ts new file mode 100644 index 00000000000..ca92d2c686f --- /dev/null +++ b/apps/browser/src/clipboard/generate-password-to-clipboard-command.ts @@ -0,0 +1,26 @@ +import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; + +import { StateService } from "../services/abstractions/state.service"; + +import { setClearClipboardTime } from "./clipboard-state"; +import { copyToClipboard } from "./copy-to-clipboard-command"; + +export class GeneratePasswordToClipboardCommand { + constructor( + private passwordGenerationService: PasswordGenerationService, + private stateService: StateService + ) {} + + async generatePasswordToClipboard(tab: chrome.tabs.Tab) { + const [options] = await this.passwordGenerationService.getOptions(); + const password = await this.passwordGenerationService.generatePassword(options); + + copyToClipboard(tab, password); + + const clearClipboard = await this.stateService.getClearClipboard(); + + if (clearClipboard != null) { + await setClearClipboardTime(this.stateService, Date.now() + clearClipboard * 1000); + } + } +} diff --git a/apps/browser/src/clipboard/index.ts b/apps/browser/src/clipboard/index.ts new file mode 100644 index 00000000000..9a6cfde55f5 --- /dev/null +++ b/apps/browser/src/clipboard/index.ts @@ -0,0 +1,3 @@ +export * from "./clearClipboard"; +export * from "./copy-to-clipboard-command"; +export * from "./generate-password-to-clipboard-command"; diff --git a/apps/browser/src/content/misc-utils.ts b/apps/browser/src/content/misc-utils.ts new file mode 100644 index 00000000000..94e2e0f7a09 --- /dev/null +++ b/apps/browser/src/content/misc-utils.ts @@ -0,0 +1,23 @@ +import { TabMessage } from "../types/tab-messages"; + +async function copyText(text: string) { + await window.navigator.clipboard.writeText(text); +} + +async function onMessageListener( + msg: TabMessage, + sender: chrome.runtime.MessageSender, + responseCallback: (response: unknown) => void +) { + switch (msg.command) { + case "copyText": + await copyText(msg.text); + break; + case "clearClipboard": + await copyText("\u0000"); + break; + default: + } +} + +chrome.runtime.onMessage.addListener(onMessageListener); diff --git a/apps/browser/src/listeners/onCommandListener.ts b/apps/browser/src/listeners/onCommandListener.ts index f0c492da89b..c52e5cb61ac 100644 --- a/apps/browser/src/listeners/onCommandListener.ts +++ b/apps/browser/src/listeners/onCommandListener.ts @@ -7,7 +7,13 @@ import { authServiceFactory } from "../background/service_factories/auth-service import { autofillServiceFactory } from "../background/service_factories/autofill-service.factory"; import { CachedServices } from "../background/service_factories/factory-options"; import { logServiceFactory } from "../background/service_factories/log-service.factory"; +import { + passwordGenerationServiceFactory, + PasswordGenerationServiceInitOptions, +} from "../background/service_factories/password-generation-service.factory"; +import { stateServiceFactory } from "../background/service_factories/state-service.factory"; import { BrowserApi } from "../browser/browserApi"; +import { GeneratePasswordToClipboardCommand } from "../clipboard"; import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand"; import { Account } from "../models/account"; @@ -16,6 +22,9 @@ export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) = case "autofill_login": await doAutoFillLogin(tab); break; + case "generate_password": + await doGeneratePasswordToClipboard(tab); + break; } }; @@ -69,3 +78,37 @@ const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise => { const command = new AutoFillActiveTabCommand(autofillService); await command.doAutoFillActiveTabCommand(tab); }; + +const doGeneratePasswordToClipboard = async (tab: chrome.tabs.Tab): Promise => { + const stateFactory = new StateFactory(GlobalState, Account); + + const cache = {}; + const options: PasswordGenerationServiceInitOptions = { + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: false, + }, + logServiceOptions: { + isDev: false, + }, + platformUtilsServiceOptions: { + biometricCallback: () => Promise.resolve(true), + clipboardWriteCallback: (_clipboardValue, _clearMs) => Promise.resolve(), + win: self, + }, + stateMigrationServiceOptions: { + stateFactory: stateFactory, + }, + stateServiceOptions: { + stateFactory: stateFactory, + }, + }; + + const command = new GeneratePasswordToClipboardCommand( + await passwordGenerationServiceFactory(cache, options), + await stateServiceFactory(cache, options) + ); + command.generatePasswordToClipboard(tab); +}; diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 1af19e255f2..cf5a1d26f84 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -38,6 +38,12 @@ "css": ["content/autofill.css"], "matches": ["http://*/*", "https://*/*", "file:///*"], "run_at": "document_end" + }, + { + "all_frames": true, + "js": ["content/misc-utils.js"], + "matches": ["http://*/*", "https://*/*", "file:///*"], + "run_at": "document_end" } ], "background": { @@ -59,7 +65,8 @@ "unlimitedStorage", "clipboardRead", "clipboardWrite", - "idle" + "idle", + "alarms" ], "optional_permissions": ["nativeMessaging"], "host_permissions": ["http://*/*", "https://*/*"], diff --git a/apps/browser/src/types/tab-messages.ts b/apps/browser/src/types/tab-messages.ts new file mode 100644 index 00000000000..12496f5aa3d --- /dev/null +++ b/apps/browser/src/types/tab-messages.ts @@ -0,0 +1,9 @@ +export type TabMessage = CopyTextTabMessage | TabMessageBase<"clearClipboard">; + +export type TabMessageBase = { + command: T; +}; + +export type CopyTextTabMessage = TabMessageBase<"copyText"> & { + text: string; +}; diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 116ed19bd2e..9159028af01 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -218,6 +218,8 @@ if (manifestVersion == 2) { return chunk.name === "background"; }, }; +} else { + config.entry["content/misc-utils"] = "./src/content/misc-utils.ts"; } module.exports = config; diff --git a/libs/common/src/abstractions/passwordGeneration.service.ts b/libs/common/src/abstractions/passwordGeneration.service.ts index 20aee598768..b1057c312d5 100644 --- a/libs/common/src/abstractions/passwordGeneration.service.ts +++ b/libs/common/src/abstractions/passwordGeneration.service.ts @@ -1,20 +1,24 @@ import * as zxcvbn from "zxcvbn"; import { GeneratedPasswordHistory } from "../models/domain/generated-password-history"; +import { PasswordGeneratorOptions } from "../models/domain/password-generator-options"; import { PasswordGeneratorPolicyOptions } from "../models/domain/password-generator-policy-options"; export abstract class PasswordGenerationService { - generatePassword: (options: any) => Promise; - generatePassphrase: (options: any) => Promise; - getOptions: () => Promise<[any, PasswordGeneratorPolicyOptions]>; + generatePassword: (options: PasswordGeneratorOptions) => Promise; + generatePassphrase: (options: PasswordGeneratorOptions) => Promise; + getOptions: () => Promise<[PasswordGeneratorOptions, PasswordGeneratorPolicyOptions]>; enforcePasswordGeneratorPoliciesOnOptions: ( - options: any - ) => Promise<[any, PasswordGeneratorPolicyOptions]>; + options: PasswordGeneratorOptions + ) => Promise<[PasswordGeneratorOptions, PasswordGeneratorPolicyOptions]>; getPasswordGeneratorPolicyOptions: () => Promise; - saveOptions: (options: any) => Promise; + saveOptions: (options: PasswordGeneratorOptions) => Promise; getHistory: () => Promise; - addHistory: (password: string) => Promise; - clear: (userId?: string) => Promise; + addHistory: (password: string) => Promise; + clear: (userId?: string) => Promise; passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult; - normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void; + normalizeOptions: ( + options: PasswordGeneratorOptions, + enforcedPolicyOptions: PasswordGeneratorPolicyOptions + ) => void; } diff --git a/libs/common/src/models/domain/password-generator-options.ts b/libs/common/src/models/domain/password-generator-options.ts new file mode 100644 index 00000000000..287a91659e4 --- /dev/null +++ b/libs/common/src/models/domain/password-generator-options.ts @@ -0,0 +1,17 @@ +export type PasswordGeneratorOptions = { + length?: number; + ambiguous?: boolean; + uppercase?: boolean; + minUppercase?: number; + lowercase?: boolean; + minLowercase?: number; + number?: boolean; + minNumber?: number; + special?: boolean; + minSpecial?: number; + numWords?: number; + wordSeparator?: string; + capitalize?: boolean; + includeNumber?: boolean; + type?: "password" | "passphrase"; +}; diff --git a/libs/common/src/services/passwordGeneration.service.ts b/libs/common/src/services/passwordGeneration.service.ts index a86f6ac7ed9..99964449d5f 100644 --- a/libs/common/src/services/passwordGeneration.service.ts +++ b/libs/common/src/services/passwordGeneration.service.ts @@ -9,10 +9,11 @@ import { PolicyType } from "../enums/policyType"; import { EFFLongWordList } from "../misc/wordlist"; import { EncString } from "../models/domain/enc-string"; import { GeneratedPasswordHistory } from "../models/domain/generated-password-history"; +import { PasswordGeneratorOptions } from "../models/domain/password-generator-options"; import { PasswordGeneratorPolicyOptions } from "../models/domain/password-generator-policy-options"; import { Policy } from "../models/domain/policy"; -const DefaultOptions = { +const DefaultOptions: PasswordGeneratorOptions = { length: 14, ambiguous: false, number: true, @@ -39,7 +40,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr private stateService: StateService ) {} - async generatePassword(options: any): Promise { + async generatePassword(options: PasswordGeneratorOptions): Promise { // overload defaults with given options const o = Object.assign({}, DefaultOptions, options); @@ -145,7 +146,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return password; } - async generatePassphrase(options: any): Promise { + async generatePassphrase(options: PasswordGeneratorOptions): Promise { const o = Object.assign({}, DefaultOptions, options); if (o.numWords == null || o.numWords <= 2) { @@ -178,7 +179,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return wordList.join(o.wordSeparator); } - async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> { + async getOptions(): Promise<[PasswordGeneratorOptions, PasswordGeneratorPolicyOptions]> { let options = await this.stateService.getPasswordGenerationOptions(); if (options == null) { options = Object.assign({}, DefaultOptions); @@ -192,8 +193,8 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr } async enforcePasswordGeneratorPoliciesOnOptions( - options: any - ): Promise<[any, PasswordGeneratorPolicyOptions]> { + options: PasswordGeneratorOptions + ): Promise<[PasswordGeneratorOptions, PasswordGeneratorPolicyOptions]> { let enforcedPolicyOptions = await this.getPasswordGeneratorPolicyOptions(); if (enforcedPolicyOptions != null) { if (options.length < enforcedPolicyOptions.minLength) { @@ -340,7 +341,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return enforcedOptions; } - async saveOptions(options: any) { + async saveOptions(options: PasswordGeneratorOptions) { await this.stateService.setPasswordGenerationOptions(options); } @@ -363,7 +364,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr : new Array(); } - async addHistory(password: string): Promise { + async addHistory(password: string): Promise { // Cannot add new history if no key is available const hasKey = await this.cryptoService.hasKey(); if (!hasKey) { @@ -389,7 +390,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory); } - async clear(userId?: string): Promise { + async clear(userId?: string): Promise { await this.stateService.setEncryptedPasswordGenerationHistory(null, { userId: userId }); await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId }); } @@ -408,7 +409,10 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return result; } - normalizeOptions(options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) { + normalizeOptions( + options: PasswordGeneratorOptions, + enforcedPolicyOptions: PasswordGeneratorPolicyOptions + ) { options.minLowercase = 0; options.minUppercase = 0;