From 638304819727109aa743eae14f635b4a1c596c7a Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Fri, 13 Dec 2024 12:37:16 -0500 Subject: [PATCH] PM-5550 Implement on-page autofil for single line TOTP (#12058) * PM-5550 initial commit -Initial render -Edit tests -Clean up styling -New method to validate totpfields * add refresh overlay * localize and clean up * - Clean up code - Remove unnecessary data from buildtotpelement - Add feature flag - Add aria labels to buildtotpelement - Add tests and update relevant snapshots * Add and translate aria labels * add aria labels * implement feature flag * address totp tests * clean up totpfield function * fix styling and tests, update snapshots * Update apps/browser/src/_locales/en/messages.json Formatting suggestion Co-authored-by: Jonathan Prusik * Update apps/browser/src/_locales/en/messages.json Formatting suggestion Co-authored-by: Jonathan Prusik * remove group tag * update snapshots * adress feedback --------- Co-authored-by: Jonathan Prusik --- apps/browser/src/_locales/en/messages.json | 15 + .../abstractions/overlay.background.ts | 4 + .../background/overlay.background.spec.ts | 11 + .../autofill/background/overlay.background.ts | 91 ++- .../autofill-inline-menu-list.spec.ts.snap | 649 +++++++++++++++++- .../list/autofill-inline-menu-list.spec.ts | 25 + .../pages/list/autofill-inline-menu-list.ts | 93 +++ .../overlay/inline-menu/pages/list/list.scss | 26 + ...nline-menu-field-qualifications.service.ts | 1 + ...e-menu-field-qualification.service.spec.ts | 18 +- ...inline-menu-field-qualification.service.ts | 25 +- .../src/autofill/shared/styles/variables.scss | 84 ++- .../src/autofill/spec/autofill-mocks.ts | 2 +- .../src/background/runtime.background.ts | 4 + libs/common/src/enums/feature-flag.enum.ts | 2 + 15 files changed, 944 insertions(+), 106 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 04858eecce..f4a498f3e0 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -192,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -3580,6 +3587,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index a0cdfb3ceb..03284f3fd8 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -160,6 +160,9 @@ export type InlineMenuCipherData = { icon: WebsiteIconData; accountCreationFieldType?: string; login?: { + totp?: string; + totpField?: boolean; + totpCodeTimeInterval?: number; username: string; passkey: { rpName: string; @@ -262,6 +265,7 @@ export type InlineMenuListPortMessageHandlers = { updateAutofillInlineMenuListHeight: ({ message, port }: PortOnMessageHandlerParams) => void; refreshGeneratedPassword: () => Promise; fillGeneratedPassword: ({ port }: PortConnectionParam) => Promise; + refreshOverlayCiphers: () => Promise; }; export interface OverlayBackground { diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 6ec3c0a9b5..e7b72b72c9 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -928,6 +928,7 @@ describe("OverlayBackground", () => { login: { username: "username-1", passkey: null, + totpField: false, }, name: "name-1", reprompt: loginCipher1.reprompt, @@ -1065,6 +1066,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, name: loginCipher1.name, reprompt: loginCipher1.reprompt, @@ -1189,6 +1191,7 @@ describe("OverlayBackground", () => { rpName: passkeyCipher.login.fido2Credentials[0].rpName, userName: passkeyCipher.login.fido2Credentials[0].userName, }, + totpField: false, }, }, { @@ -1207,6 +1210,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1225,6 +1229,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1272,6 +1277,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1290,6 +1296,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1337,6 +1344,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1355,6 +1363,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1400,6 +1409,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, { @@ -1418,6 +1428,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher2.login.username, passkey: null, + totpField: false, }, }, ], diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index aaeeea857b..fd16bfcf16 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -204,6 +204,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { updateAutofillInlineMenuListHeight: ({ message }) => this.updateInlineMenuListHeight(message), refreshGeneratedPassword: () => this.updateGeneratedPassword(true), fillGeneratedPassword: ({ port }) => this.fillGeneratedPassword(port), + refreshOverlayCiphers: () => this.updateOverlayCiphers(false), }; constructor( @@ -464,7 +465,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.showPasskeysLabelsWithinInlineMenu = false; if (this.shouldShowInlineMenuAccountCreation()) { - inlineMenuCipherData = this.buildInlineMenuAccountCreationCiphers( + inlineMenuCipherData = await this.buildInlineMenuAccountCreationCiphers( inlineMenuCiphersArray, true, ); @@ -485,7 +486,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param inlineMenuCiphersArray - Array of inline menu ciphers * @param showFavicons - Identifies whether favicons should be shown */ - private buildInlineMenuAccountCreationCiphers( + private async buildInlineMenuAccountCreationCiphers( inlineMenuCiphersArray: [string, CipherView][], showFavicons: boolean, ) { @@ -497,7 +498,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (cipher.type === CipherType.Login) { accountCreationLoginCiphers.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -517,7 +518,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } inlineMenuCipherData.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -561,13 +562,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (!passkeysEnabled || !(await this.showCipherAsPasskey(cipher, domainExclusionsSet))) { inlineMenuCipherData.push( - this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); continue; } passkeyCipherData.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -577,7 +578,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (cipher.login?.password && cipher.login.username) { inlineMenuCipherData.push( - this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); } } @@ -620,6 +621,23 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.inlineMenuFido2Credentials.has(credentialId); } + private isTotpFieldForCurrentField(): boolean { + if (!this.focusedFieldData) { + return false; + } + const { tabId, frameId } = this.focusedFieldData; + const pageDetailsMap = this.pageDetailsForTab[tabId]; + if (!pageDetailsMap || !pageDetailsMap.has(frameId)) { + return false; + } + const pageDetail = pageDetailsMap.get(frameId); + return ( + pageDetail?.details?.fields?.every((field) => + this.inlineMenuFieldQualificationService.isTotpField(field), + ) || false + ); + } + /** * Builds the cipher data for the inline menu list. * @@ -630,14 +648,14 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param hasPasskey - Identifies whether the cipher has a FIDO2 credential * @param identityData - Pre-created identity data */ - private buildCipherData({ + private async buildCipherData({ inlineMenuCipherId, cipher, showFavicons, showInlineMenuAccountCreation, hasPasskey, identityData, - }: BuildCipherDataParams): InlineMenuCipherData { + }: BuildCipherDataParams): Promise { const inlineMenuData: InlineMenuCipherData = { id: inlineMenuCipherId, name: cipher.name, @@ -649,8 +667,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { }; if (cipher.type === CipherType.Login) { + const totpCode = await this.totpService.getCode(cipher.login?.totp); + const totpCodeTimeInterval = this.totpService.getTimeInterval(cipher.login?.totp); inlineMenuData.login = { username: cipher.login.username, + totp: totpCode, + totpField: this.isTotpFieldForCurrentField(), + totpCodeTimeInterval: totpCodeTimeInterval, passkey: hasPasskey ? { rpName: cipher.login.fido2Credentials[0].rpName, @@ -1980,35 +2003,39 @@ export class OverlayBackground implements OverlayBackgroundInterface { private getInlineMenuTranslations() { if (!this.inlineMenuPageTranslations) { const translationKeys = [ + "addNewCardItemAria", + "addNewIdentityItemAria", + "addNewLoginItemAria", + "addNewVaultItem", + "authenticating", + "cardNumberEndsWith", + "fillCredentialsFor", + "fillGeneratedPassword", + "fillVerificationCode", + "fillVerificationCodeAria", + "generatedPassword", + "lowercaseAriaLabel", + "logInWithPasskeyAriaLabel", + "newCard", + "newIdentity", + "newItem", + "newLogin", + "noItemsToShow", "opensInANewWindow", + "passkeys", + "passwordRegenerated", + "passwords", + "regeneratePassword", + "saveLoginToBitwarden", "toggleBitwardenVaultOverlay", - "unlockYourAccountToViewAutofillSuggestions", + "totpCodeAria", + "totpSecondsSpanAria", "unlockAccount", "unlockAccountAria", - "fillCredentialsFor", + "unlockYourAccountToViewAutofillSuggestions", + "uppercaseAriaLabel", "username", "view", - "noItemsToShow", - "newItem", - "addNewVaultItem", - "newLogin", - "addNewLoginItemAria", - "newCard", - "addNewCardItemAria", - "newIdentity", - "addNewIdentityItemAria", - "cardNumberEndsWith", - "passkeys", - "passwords", - "logInWithPasskeyAriaLabel", - "authenticating", - "fillGeneratedPassword", - "regeneratePassword", - "passwordRegenerated", - "saveLoginToBitwarden", - "lowercaseAriaLabel", - "uppercaseAriaLabel", - "generatedPassword", ...Object.values(specialCharacterToKeyMap), ]; this.inlineMenuPageTranslations = translationKeys.reduce( diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index d920820b0e..785cadb551 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -681,10 +681,121 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f class="cipher-container" > + + + +
  • +
    + + +
    +
  • + + +`; + +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a totp field 1`] = ` +
    +
      +
    • +
      + + +
      +
    • +
    • +
      + + +
      +
    • +
    • +
      + + +
      +
    • +
    • +
      + + +
      +
    • +
    • +
      +