From d8c9e30553c2f98e3c36e34fea776227da887ef1 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Wed, 5 Feb 2025 10:18:18 -0600 Subject: [PATCH] update modal with correct ciphers and functionality --- apps/desktop/src/app/app-routing.module.ts | 8 +- .../desktop-fido2-user-interface.service.ts | 16 ++- apps/desktop/src/locales/en/messages.json | 3 + apps/desktop/src/main/tray.main.ts | 11 +- .../create/fido2-vault.component.html | 36 +++++++ .../passkeys/create/fido2-vault.component.ts | 102 ++++++++++++++++++ .../modal/passkeys/fido2-vault.component.html | 36 +++++++ .../passkeys/fido2-vault.component.ts} | 68 ++---------- .../src/vault/abstractions/cipher.service.ts | 1 + .../src/vault/services/cipher.service.ts | 13 ++- 10 files changed, 226 insertions(+), 68 deletions(-) create mode 100644 apps/desktop/src/modal/passkeys/create/fido2-vault.component.html create mode 100644 apps/desktop/src/modal/passkeys/create/fido2-vault.component.ts create mode 100644 apps/desktop/src/modal/passkeys/fido2-vault.component.html rename apps/desktop/src/{app/components/fido2placeholder.component.ts => modal/passkeys/fido2-vault.component.ts} (59%) diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 5980cae2a05..2b8a63871c1 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -62,9 +62,9 @@ import { SsoComponentV1 } from "../auth/sso-v1.component"; import { TwoFactorAuthComponent } from "../auth/two-factor-auth.component"; import { TwoFactorComponent } from "../auth/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; +import { Fido2VaultComponent } from "../modal/passkeys/fido2-vault.component"; import { VaultComponent } from "../vault/app/vault/vault.component"; -import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component"; import { SendComponent } from "./tools/send/send.component"; /** @@ -334,7 +334,11 @@ const routes: Routes = [ ), { path: "passkeys", - component: Fido2PlaceholderComponent, + component: Fido2VaultComponent, + }, + { + path: "create-passkey", + component: Fido2VaultComponent, }, { path: "", diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts index 46171aa130a..002096cc107 100644 --- a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts +++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts @@ -76,9 +76,12 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi private desktopSettingsService: DesktopSettingsService, ) {} - pickCredential: ( + async pickCredential( params: PickCredentialParams, - ) => Promise<{ cipherId: string; userVerified: boolean }>; + ): Promise<{ cipherId: string; userVerified: boolean }> { + // Add your implementation here + return { cipherId: "", userVerified: false }; + } private confirmCredentialSubject = new Subject(); private createdCipher: Cipher; @@ -119,7 +122,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi ); try { - await this.showUi(); + await this.showUi(rpId); // Wait for the UI to wrap up await this.waitForUiCredentialConfirmation(); @@ -147,11 +150,14 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi } } - private async showUi() { + private async showUi(rpId?: string) { // Load the UI: // maybe toggling to modal mode shouldn't be done here? await this.desktopSettingsService.setInModalMode(true); - await this.router.navigate(["/passkeys"]); + //pass the rpid to the fido2placeholder component through routing parameter + + // await this.router.navigate(["/passkeys"]); + await this.router.navigate(["/passkeys"], { state: { rpid: rpId } }); } /** diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 323d0cd3f7b..57a07782909 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3430,5 +3430,8 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "passkeyLogin":{ + "message": "Log in with passkey?" } } diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index f9000680907..d8a95e85c53 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -51,9 +51,14 @@ export class TrayMain { }, { visible: isDev(), - label: "Fake Popup", + label: "Fake Popup Select", click: () => this.fakePopup(), }, + { + visible: isDev(), + label: "Fake Popup Create", + click: () => this.fakePopupCreate(), + }, { type: "separator" }, { label: this.i18nService.t("exit"), @@ -212,4 +217,8 @@ export class TrayMain { private async fakePopup() { await this.messagingService.send("loadurl", { url: "/passkeys", modal: true }); } + + private async fakePopupCreate() { + await this.messagingService.send("loadurl", { url: "/passkeys", modal: true }); + } } diff --git a/apps/desktop/src/modal/passkeys/create/fido2-vault.component.html b/apps/desktop/src/modal/passkeys/create/fido2-vault.component.html new file mode 100644 index 00000000000..5ce1f804fe3 --- /dev/null +++ b/apps/desktop/src/modal/passkeys/create/fido2-vault.component.html @@ -0,0 +1,36 @@ +
+ + +
+ + +

{{ "passkeyLogin" | i18n }}Log in with passkey?

+
+ +
+
+ + + + + {{ c.subTitle }} + Select + + + +
diff --git a/apps/desktop/src/modal/passkeys/create/fido2-vault.component.ts b/apps/desktop/src/modal/passkeys/create/fido2-vault.component.ts new file mode 100644 index 00000000000..47692c12e33 --- /dev/null +++ b/apps/desktop/src/modal/passkeys/create/fido2-vault.component.ts @@ -0,0 +1,102 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { RouterModule, Router } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + ButtonModule, + DialogModule, + IconModule, + ItemModule, + SectionComponent, + TableModule, +} from "@bitwarden/components"; + +import { BitwardenShield } from "../../../../../../libs/auth/src/angular/icons"; +import { BitIconButtonComponent } from "../../../../../../libs/components/src/icon-button/icon-button.component"; +import { SectionHeaderComponent } from "../../../../../../libs/components/src/section/section-header.component"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../../autofill/services/desktop-fido2-user-interface.service"; +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +// import { AnchorLinkDirective } from "../../../../../libs/components/src/link/link.directive"; + +@Component({ + standalone: true, + imports: [ + CommonModule, + RouterModule, + SectionHeaderComponent, + BitIconButtonComponent, + TableModule, + JslibModule, + IconModule, + ButtonModule, + DialogModule, + SectionComponent, + ItemModule, + BadgeModule, + ], + templateUrl: "fido2-vault.component.html", +}) +export class Fido2VaultComponent implements OnInit { + ciphers: CipherView[]; + rpId: string; + readonly Icons = { BitwardenShield }; + + session?: DesktopFido2UserInterfaceSession = null; + constructor( + private readonly desktopSettingsService: DesktopSettingsService, + private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService, + private readonly cipherService: CipherService, + private readonly router: Router, + ) {} + + async ngOnInit() { + this.rpId = history.state.rpid; + this.session = this.fido2UserInterfaceService.getCurrentSession(); + if (this.rpId !== null) { + this.ciphers = await this.cipherService.getPasskeyCiphersForUrl(this.rpId); + } else { + this.ciphers = await this.cipherService.getAllDecrypted(); + } + } + + async confirmPasskey() { + try { + // Retrieve the current UI session to control the flow + if (!this.session) { + // todo: handle error + throw new Error("No session found"); + } + + // If we want to we could submit information to the session in order to create the credential + // const cipher = await session.createCredential({ + // userHandle: "userHandle2", + // userName: "username2", + // credentialName: "zxsd2", + // rpId: "webauthn.io", + // userVerification: true, + // }); + + this.session.notifyConfirmCredential(); + + // Not sure this clean up should happen here or in session. + // The session currently toggles modal on and send us here + // But if this route is somehow opened outside of session we want to make sure we clean up? + await this.router.navigate(["/"]); + await this.desktopSettingsService.setInModalMode(false); + } catch (error) { + // TODO: Handle error appropriately + } + } + + async closeModal() { + await this.router.navigate(["/"]); + await this.desktopSettingsService.setInModalMode(false); + } +} diff --git a/apps/desktop/src/modal/passkeys/fido2-vault.component.html b/apps/desktop/src/modal/passkeys/fido2-vault.component.html new file mode 100644 index 00000000000..5ce1f804fe3 --- /dev/null +++ b/apps/desktop/src/modal/passkeys/fido2-vault.component.html @@ -0,0 +1,36 @@ +
+ + +
+ + +

{{ "passkeyLogin" | i18n }}Log in with passkey?

+
+ +
+
+ + + + + {{ c.subTitle }} + Select + + + +
diff --git a/apps/desktop/src/app/components/fido2placeholder.component.ts b/apps/desktop/src/modal/passkeys/fido2-vault.component.ts similarity index 59% rename from apps/desktop/src/app/components/fido2placeholder.component.ts rename to apps/desktop/src/modal/passkeys/fido2-vault.component.ts index 0be3158d5c3..c16481e17e5 100644 --- a/apps/desktop/src/app/components/fido2placeholder.component.ts +++ b/apps/desktop/src/modal/passkeys/fido2-vault.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; -import { RouterModule , Router } from "@angular/router"; +import { RouterModule, Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -25,8 +25,6 @@ import { import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; // import { AnchorLinkDirective } from "../../../../../libs/components/src/link/link.directive"; - - @Component({ standalone: true, imports: [ @@ -43,62 +41,11 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings ItemModule, BadgeModule, ], - template: ` -
- - - -

Log in with passkey?

- -
- - - - - -
-
- - -
- -
- {{ c.subTitle }} -
-
- Select -
-
-
-
- -
- -
- `, + templateUrl: "fido2-vault.component.html", }) -export class Fido2PlaceholderComponent implements OnInit { +export class Fido2VaultComponent implements OnInit { ciphers: CipherView[]; + rpId: string; readonly Icons = { BitwardenShield }; session?: DesktopFido2UserInterfaceSession = null; @@ -110,8 +57,13 @@ export class Fido2PlaceholderComponent implements OnInit { ) {} async ngOnInit() { + this.rpId = history.state.rpid; this.session = this.fido2UserInterfaceService.getCurrentSession(); - this.ciphers = await this.cipherService.getAllDecrypted(); + if (this.rpId !== null) { + this.ciphers = await this.cipherService.getPasskeyCiphersForUrl(this.rpId); + } else { + this.ciphers = await this.cipherService.getAllDecrypted(); + } } async confirmPasskey() { diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 870cb8b3d73..a0dca4e4d06 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -45,6 +45,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + getPasskeyCiphersForUrl: (url: string) => Promise; filterCiphersForUrl: ( ciphers: CipherView[], url: string, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 0f1b1fe93b4..732b3922cf9 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -10,7 +10,7 @@ import { shareReplay, Subject, switchMap, - tap, + //tap, } from "rxjs"; import { SemVer } from "semver"; @@ -142,7 +142,7 @@ export class CipherService implements CipherServiceAbstraction { this.cipherViews$ = combineLatest([this.encryptedCiphersState.state$, this.localData$]).pipe( filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())), - tap((v) => console.log("---- cipherViews$", v)), + // tap((v) => console.log("---- cipherViews$", v)), shareReplay({ bufferSize: 1, refCount: true }), ); this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; @@ -1632,6 +1632,15 @@ export class CipherService implements CipherServiceAbstraction { } } + async getPasskeyCiphersForUrl(url: string): Promise { + let ciphers = await this.getAllDecryptedForUrl(url); + if (!ciphers) { + return null; + } + ciphers = ciphers.filter((cipher) => cipher.login.fido2Credentials?.length); + return ciphers; + } + private async clearEncryptedCiphersState(userId: UserId) { await this.stateProvider.setUserState(ENCRYPTED_CIPHERS, {}, userId); }