1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 05:53:42 +00:00

update modal with correct ciphers and functionality

This commit is contained in:
Evan Bassler
2025-02-05 10:18:18 -06:00
parent 40445f57cf
commit d8c9e30553
10 changed files with 226 additions and 68 deletions

View File

@@ -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: "",

View File

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

View File

@@ -3430,5 +3430,8 @@
},
"changeAcctEmail": {
"message": "Change account email"
},
"passkeyLogin":{
"message": "Log in with passkey?"
}
}

View File

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

View File

@@ -0,0 +1,36 @@
<div class="tw-flex tw-flex-col">
<bit-section
disableMargin
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
>
<bit-section-header class="tw-bg-background">
<div class="tw-flex tw-items-center">
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
<h2 bitTypography="h6">{{ "passkeyLogin" | i18n }}Log in with passkey?</h2>
</div>
<button
type="button"
bitIconButton="bwi-close"
slot="end"
class="tw-mb-4 tw-mr-2"
(click)="closeModal()"
>
Close
</button>
</bit-section-header>
</bit-section>
<bit-section class="tw-bg-background-alt tw-p-4">
<bit-item *ngFor="let c of ciphers" class="">
<button type="button" bit-item-content (click)="confirmPasskey()">
<app-vault-icon [cipher]="c" slot="start"></app-vault-icon>
<button bitLink [title]="c.name" type="button">
{{ c.name }}
</button>
<span slot="secondary">{{ c.subTitle }}</span>
<span bitBadge slot="end">Select</span>
</button>
</bit-item>
</bit-section>
</div>

View File

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

View File

@@ -0,0 +1,36 @@
<div class="tw-flex tw-flex-col">
<bit-section
disableMargin
class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
>
<bit-section-header class="tw-bg-background">
<div class="tw-flex tw-items-center">
<bit-icon [icon]="Icons.BitwardenShield" class="tw-w-10 tw-mt-2 tw-ml-2"></bit-icon>
<h2 bitTypography="h6">{{ "passkeyLogin" | i18n }}Log in with passkey?</h2>
</div>
<button
type="button"
bitIconButton="bwi-close"
slot="end"
class="tw-mb-4 tw-mr-2"
(click)="closeModal()"
>
Close
</button>
</bit-section-header>
</bit-section>
<bit-section class="tw-bg-background-alt tw-p-4">
<bit-item *ngFor="let c of ciphers" class="">
<button type="button" bit-item-content (click)="confirmPasskey()">
<app-vault-icon [cipher]="c" slot="start"></app-vault-icon>
<button bitLink [title]="c.name" type="button">
{{ c.name }}
</button>
<span slot="secondary">{{ c.subTitle }}</span>
<span bitBadge slot="end">Select</span>
</button>
</bit-item>
</bit-section>
</div>

View File

@@ -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: `
<div class="tw-flex tw-flex-col">
<bit-section-header class="tw-p-4 tw-items-center">
<bit-icon [icon]="Icons.BitwardenShield" class=" tw-w-10"></bit-icon>
<h2 bitTypography="h6">Log in with passkey?</h2>
<button
type="button"
bitIconButton="bwi-close"
slot="end"
class="tw-align-center"
(click)="closeModal()"
>
Close
</button>
</bit-section-header>
<!-- insert wrapper and foreach for ciphers -->
<!-- <bit-table> -->
<bit-section class="tw-bg-background-alt tw-p-4">
<ng-container *ngFor="let c of ciphers" class="tw-p-4">
<bit-item class=" tw-mb-2 tw-py-2 tw-px-4">
<div class="tw-flex tw-items-center tw-justify-between tw-w-full">
<div class="tw-flex">
<app-vault-icon [cipher]="c"></app-vault-icon>
<div class="">
<button
bitLink
class="tw-overflow-hidden tw-text-start tw-leading-snug"
queryParamsHandling="merge"
[title]="c.name"
type="button"
appStopProp
aria-haspopup="true"
>
{{ c.name }}
</button>
<br />
<span class="tw-text-sm tw-text-muted" appStopProp>{{ c.subTitle }}</span>
</div>
</div>
<span bitBadge (click)="confirmPasskey()">Select</span>
</div>
</bit-item>
</ng-container>
</bit-section>
<!-- </bit-table> -->
<br />
<button style="" bitButton type="button" buttonType="secondary" [loading]="false">
Confirm passkey
</button>
</div>
`,
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() {

View File

@@ -45,6 +45,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
includeOtherTypes?: CipherType[],
defaultMatch?: UriMatchStrategySetting,
) => Promise<CipherView[]>;
getPasskeyCiphersForUrl: (url: string) => Promise<CipherView[]>;
filterCiphersForUrl: (
ciphers: CipherView[],
url: string,

View File

@@ -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<CipherView[]> {
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);
}