1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Autofill/pm 9034 implement passkey for unlocked accounts (#13826)

* Passkey stuff

Co-authored-by: Anders Åberg <github@andersaberg.com>

* Ugly hacks

* Work On Modal State Management

* Applying modalStyles

* modal

* Improved hide/show

* fixed promise

* File name

* fix prettier

* Protecting against null API's and undefined data

* Only show fake popup to devs

* cleanup mock code

* rename minmimal-app to modal-app

* Added comment

* Added comment

* removed old comment

* Avoided changing minimum size

* Add small comment

* Rename component

* adress feedback

* Fixed uppercase file

* Fixed build

* Added codeowners

* added void

* commentary

* feat: reset setting on app start

* Moved reset to be in main / process launch

* Add comment to create window

* Added a little bit of styling

* Use Messaging service to loadUrl

* Enable passkeysautofill

* Add logging

* halfbaked

* Integration working

* And now it works without extra delay

* Clean up

* add note about messaging

* lb

* removed console.logs

* Cleanup and adress review feedback

* This hides the swift UI

* add modal components

* update modal with correct ciphers and functionality

* add create screen

* pick credential, draft

* Remove logger

* a whole lot of wiring

* not working

* Improved wiring

* Cancel after 90s

* Introduced observable

* update cipher handling

* update to use matchesUri

* Launching bitwarden if its not running

* Passing position from native to electron

* Rename inModalMode to modalMode

* remove tap

* revert spaces

* added back isDev

* cleaned up a bit

* Cleanup swift file

* tweaked logging

* clean up

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* Update apps/desktop/src/platform/services/desktop-settings.service.ts

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* adress position feedback

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* Removed extra logging

* Adjusted error logging

* Use .error to log errors

* remove dead code

* Update desktop-autofill.service.ts

* use parseCredentialId instead of guidToRawFormat

* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* Change windowXy to a Record instead of [number,number]

* Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* Remove unsued dep and comment

* changed timeout to be spec recommended maxium, 10 minutes, for now.

* Correctly assume UP

* Removed extra cancelRequest in deinint

* Add timeout and UV to confirmChoseCipher

UV is performed by UI, not the service

* Improved docs regarding undefined cipherId

* cleanup: UP is no longer undefined

* Run completeError if ipc messages conversion failed

* don't throw, instead return undefined

* Disabled passkey provider

* Throw error if no activeUserId was found

* removed comment

* Fixed lint

* removed unsued service

* reset entitlement formatting

* Update entitlements.mas.plist

* Fix build issues

* Fix import issues

* Update route names to use `fido2`

* Fix being unable to select a passkey

* Fix linting issues

* Followup to fix merge issues and other comments

* Update `userHandle` value

* Add error handling for missing session or other errors

* Remove unused route

* Fix linting issues

* Simplify updateCredential method

* Followup to remove comments and timeouts and handle errors

* Address lint issue by using `takeUntilDestroyed`

* PR Followup for typescript and vault concerns

* Add try block for cipher creation

* Make userId manditory for cipher service

---------

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Co-authored-by: Anders Åberg <github@andersaberg.com>
Co-authored-by: Anders Åberg <anders@andersaberg.com>
Co-authored-by: Colton Hurst <colton@coltonhurst.com>
Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>
Co-authored-by: Evan Bassler <evanbassler@Mac.attlocal.net>
Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
This commit is contained in:
Jeffrey Holland
2025-04-08 16:59:23 +02:00
committed by GitHub
parent 849aa546d4
commit 92e9dca6b4
12 changed files with 439 additions and 43 deletions

View File

@@ -55,9 +55,10 @@ import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { Fido2CreateComponent } from "../modal/passkeys/create/fido2-create.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";
/**
@@ -179,12 +180,12 @@ const routes: Routes = [
canActivate: [authGuard],
},
{
path: "passkeys",
component: Fido2PlaceholderComponent,
path: "fido2-assertion",
component: Fido2VaultComponent,
},
{
path: "passkeys",
component: Fido2PlaceholderComponent,
path: "fido2-creation",
component: Fido2CreateComponent,
},
{
path: "",

View File

@@ -97,7 +97,7 @@ export class Fido2PlaceholderComponent implements OnInit, OnDestroy {
// userVerification: true,
// });
this.session.notifyConfirmNewCredential(true);
this.session.notifyConfirmCreateCredential(true);
// Not sure this clean up should happen here or in session.
// The session currently toggles modal on and send us here
@@ -113,7 +113,7 @@ export class Fido2PlaceholderComponent implements OnInit, OnDestroy {
await this.router.navigate(["/"]);
await this.desktopSettingsService.setModalMode(false);
this.session.notifyConfirmNewCredential(false);
this.session.notifyConfirmCreateCredential(false);
// little bit hacky:
this.session.confirmChosenCipher(null);
}

View File

@@ -60,10 +60,6 @@ export class DesktopAutofillService implements OnDestroy {
.pipe(
distinctUntilChanged(),
switchMap((enabled) => {
// if (!enabled) {
// return EMPTY;
// }
return this.accountService.activeAccount$.pipe(
map((account) => account?.id),
filter((userId): userId is UserId => userId != null),

View File

@@ -94,9 +94,12 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
) {}
private confirmCredentialSubject = new Subject<boolean>();
private createdCipher: Cipher;
private availableCipherIdsSubject = new BehaviorSubject<string[]>(null);
private createdCipher: Cipher = new Cipher();
private updatedCipher: CipherView = new CipherView();
private rpId = new BehaviorSubject<string>("");
private availableCipherIdsSubject = new BehaviorSubject<string[]>([""]);
/**
* Observable that emits available cipher IDs once they're confirmed by the UI
*/
@@ -136,15 +139,15 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
// make the cipherIds available to the UI.
this.availableCipherIdsSubject.next(cipherIds);
await this.showUi("/passkeys", this.windowObject.windowXy);
await this.showUi("/fido2-assertion", this.windowObject.windowXy);
const chosenCipherResponse = await this.waitForUiChosenCipher();
this.logService.debug("Received chosen cipher", chosenCipherResponse);
return {
cipherId: chosenCipherResponse.cipherId,
userVerified: chosenCipherResponse.userVerified,
cipherId: chosenCipherResponse?.cipherId,
userVerified: chosenCipherResponse?.userVerified,
};
} finally {
// Make sure to clean up so the app is never stuck in modal mode?
@@ -152,6 +155,15 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
}
}
async getRpId(): Promise<string> {
return lastValueFrom(
this.rpId.pipe(
filter((id) => id != null),
take(1),
),
);
}
confirmChosenCipher(cipherId: string, userVerified: boolean = false): void {
this.chosenCipherSubject.next({ cipherId, userVerified });
this.chosenCipherSubject.complete();
@@ -159,7 +171,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private async waitForUiChosenCipher(
timeoutMs: number = 60000,
): Promise<{ cipherId: string; userVerified: boolean } | undefined> {
): Promise<{ cipherId?: string; userVerified: boolean } | undefined> {
try {
return await lastValueFrom(this.chosenCipherSubject.pipe(timeout(timeoutMs)));
} catch {
@@ -174,7 +186,10 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
/**
* Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
*/
notifyConfirmNewCredential(confirmed: boolean): void {
notifyConfirmCreateCredential(confirmed: boolean, updatedCipher?: CipherView): void {
if (updatedCipher) {
this.updatedCipher = updatedCipher;
}
this.confirmCredentialSubject.next(confirmed);
this.confirmCredentialSubject.complete();
}
@@ -195,42 +210,43 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
async confirmNewCredential({
credentialName,
userName,
userHandle,
userVerification,
rpId,
}: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
}: NewCredentialParams): Promise<{ cipherId?: string; userVerified: boolean }> {
this.logService.warning(
"confirmNewCredential",
credentialName,
userName,
userHandle,
userVerification,
rpId,
);
this.rpId.next(rpId);
try {
await this.showUi("/passkeys", this.windowObject.windowXy);
await this.showUi("/fido2-creation", this.windowObject.windowXy);
// Wait for the UI to wrap up
const confirmation = await this.waitForUiNewCredentialConfirmation();
if (!confirmation) {
return { cipherId: undefined, userVerified: false };
}
// Create the credential
await this.createCredential({
credentialName,
userName,
rpId,
userHandle: "",
userVerification,
});
// wait for 10ms to help RXJS catch up(?)
// We sometimes get a race condition from this.createCredential not updating cipherService in time
//console.log("waiting 10ms..");
//await new Promise((resolve) => setTimeout(resolve, 10));
//console.log("Just waited 10ms");
// Return the new cipher (this.createdCipher)
return { cipherId: this.createdCipher.id, userVerified: userVerification };
if (this.updatedCipher) {
await this.updateCredential(this.updatedCipher);
return { cipherId: this.updatedCipher.id, userVerified: userVerification };
} else {
// Create the credential
await this.createCipher({
credentialName,
userName,
rpId,
userHandle,
userVerification,
});
return { cipherId: this.createdCipher.id, userVerified: userVerification };
}
} finally {
// Make sure to clean up so the app is never stuck in modal mode?
await this.desktopSettingsService.setModalMode(false);
@@ -240,15 +256,16 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private async showUi(route: string, position?: { x: number; y: number }): Promise<void> {
// Load the UI:
await this.desktopSettingsService.setModalMode(true, position);
await this.router.navigate(["/passkeys"]);
await this.router.navigate([route]);
}
/**
* Can be called by the UI to create a new credential with user input etc.
* @param param0
*/
async createCredential({ credentialName, userName, rpId }: NewCredentialParams): Promise<Cipher> {
async createCipher({ credentialName, userName, rpId }: NewCredentialParams): Promise<Cipher> {
// Store the passkey on a new cipher to avoid replacing something important
const cipher = new CipherView();
cipher.name = credentialName;
@@ -267,12 +284,34 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (!activeUserId) {
throw new Error("No active user ID found!");
}
const encCipher = await this.cipherService.encrypt(cipher, activeUserId);
const createdCipher = await this.cipherService.createWithServer(encCipher);
this.createdCipher = createdCipher;
try {
const createdCipher = await this.cipherService.createWithServer(encCipher);
this.createdCipher = createdCipher;
return createdCipher;
return createdCipher;
} catch {
throw new Error("Unable to create cipher");
}
}
async updateCredential(cipher: CipherView): Promise<void> {
this.logService.warning("updateCredential");
await firstValueFrom(
this.accountService.activeAccount$.pipe(
map(async (a) => {
if (a) {
const encCipher = await this.cipherService.encrypt(cipher, a.id);
await this.cipherService.updateWithServer(encCipher);
}
}),
),
);
}
async informExcludedCredential(existingCipherIds: string[]): Promise<void> {

View File

@@ -797,6 +797,12 @@
"unexpectedError": {
"message": "An unexpected error has occurred."
},
"unexpectedErrorShort": {
"message": "Unexpected error"
},
"closeThisBitwardenWindow": {
"message": "Close this Bitwarden window and try again."
},
"itemInformation": {
"message": "Item information"
},
@@ -3559,6 +3565,21 @@
"changeAcctEmail": {
"message": "Change account email"
},
"passkeyLogin": {
"message": "Log in with passkey?"
},
"savePasskeyQuestion": {
"message": "Save passkey?"
},
"saveNewPasskey": {
"message": "Save as new login"
},
"unableToSavePasskey": {
"message": "Unable to save passkey"
},
"closeBitwarden": {
"message": "Close Bitwarden"
},
"allowScreenshots": {
"message": "Allow screen capture"
},

View File

@@ -53,9 +53,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"),
@@ -218,4 +223,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: "/create-passkey", modal: true });
}
}

View File

@@ -0,0 +1,48 @@
<div class="tw-flex tw-flex-col tw-h-full">
<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="h4" class="tw-font-semibold tw-text-lg">
{{ "savePasskeyQuestion" | i18n }}
</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 tw-flex tw-flex-col tw-grow">
<bit-item *ngFor="let c of ciphers$ | async" class="">
<button type="button" bit-item-content (click)="addPasskeyToCipher(c)">
<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">Save</span>
</button>
</bit-item>
</bit-section>
<bit-section class="tw-bg-background-alt tw-p-4">
<bit-item class="">
<button bitLink linkType="primary" type="button" bit-item-content (click)="confirmPasskey()">
<a bitLink linkType="primary" class="tw-font-medium tw-text-base">
{{ "saveNewPasskey" | i18n }}
</a>
</button>
</bit-item>
</bit-section>
</div>

View File

@@ -0,0 +1,143 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { RouterModule, Router } from "@angular/router";
import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BitwardenShield } from "@bitwarden/auth/angular";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
DialogService,
BadgeModule,
ButtonModule,
DialogModule,
IconModule,
ItemModule,
SectionComponent,
TableModule,
SectionHeaderComponent,
BitIconButtonComponent,
} from "@bitwarden/components";
import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "../../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service";
@Component({
standalone: true,
imports: [
CommonModule,
RouterModule,
SectionHeaderComponent,
BitIconButtonComponent,
TableModule,
JslibModule,
IconModule,
ButtonModule,
DialogModule,
SectionComponent,
ItemModule,
BadgeModule,
],
templateUrl: "fido2-create.component.html",
})
export class Fido2CreateComponent implements OnInit {
session?: DesktopFido2UserInterfaceSession = null;
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
readonly Icons = { BitwardenShield };
constructor(
private readonly desktopSettingsService: DesktopSettingsService,
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
private readonly accountService: AccountService,
private readonly cipherService: CipherService,
private readonly dialogService: DialogService,
private readonly domainSettingsService: DomainSettingsService,
private readonly logService: LogService,
private readonly router: Router,
) {}
async ngOnInit() {
this.session = this.fido2UserInterfaceService.getCurrentSession();
const rpid = await this.session.getRpId();
const equivalentDomains = await firstValueFrom(
this.domainSettingsService.getUrlEquivalentDomains(rpid),
);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipherService
.getAllDecrypted(activeUserId)
.then((ciphers) => {
const relevantCiphers = ciphers.filter((cipher) => {
if (!cipher.login || !cipher.login.hasUris) {
return false;
}
return (
cipher.login.matchesUri(rpid, equivalentDomains) &&
(!cipher.login.fido2Credentials || cipher.login.fido2Credentials.length === 0)
);
});
this.ciphersSubject.next(relevantCiphers);
})
.catch((error) => this.logService.error(error));
}
async addPasskeyToCipher(cipher: CipherView) {
this.session.notifyConfirmCreateCredential(true, cipher);
}
async confirmPasskey() {
try {
// Retrieve the current UI session to control the flow
if (!this.session) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "unexpectedErrorShort" },
content: { key: "closeThisBitwardenWindow" },
type: "danger",
acceptButtonText: { key: "closeBitwarden" },
cancelButtonText: null,
});
if (confirmed) {
await this.closeModal();
}
} else {
this.session.notifyConfirmCreateCredential(true);
}
// 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.setModalMode(false);
} catch {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "unableToSavePasskey" },
content: { key: "closeThisBitwardenWindow" },
type: "danger",
acceptButtonText: { key: "closeBitwarden" },
cancelButtonText: null,
});
if (confirmed) {
await this.closeModal();
}
}
}
async closeModal() {
await this.router.navigate(["/"]);
await this.desktopSettingsService.setModalMode(false);
this.session.notifyConfirmCreateCredential(false);
this.session.confirmChosenCipher(null);
}
}

View File

@@ -0,0 +1,36 @@
<div class="tw-flex tw-flex-col tw-h-full">
<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="h4" class="tw-font-semibold tw-text-lg">{{ "passkeyLogin" | i18n }}</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 tw-flex tw-flex-col tw-grow">
<bit-item *ngFor="let c of ciphers$ | async" class="">
<button type="button" bit-item-content (click)="chooseCipher(c.id)">
<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, OnDestroy } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterModule, Router } from "@angular/router";
import { firstValueFrom, map, BehaviorSubject, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BitwardenShield } from "@bitwarden/auth/angular";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
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,
BitIconButtonComponent,
SectionHeaderComponent,
} from "@bitwarden/components";
import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
@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, OnDestroy {
session?: DesktopFido2UserInterfaceSession = null;
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
private cipherIdsSubject = new BehaviorSubject<string[]>([]);
cipherIds$: Observable<string[]>;
readonly Icons = { BitwardenShield };
constructor(
private readonly desktopSettingsService: DesktopSettingsService,
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
private readonly cipherService: CipherService,
private readonly accountService: AccountService,
private readonly logService: LogService,
private readonly router: Router,
) {}
async ngOnInit() {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.session = this.fido2UserInterfaceService.getCurrentSession();
this.cipherIds$ = this.session?.availableCipherIds$;
this.cipherIds$.pipe(takeUntilDestroyed()).subscribe((cipherIds) => {
this.cipherService
.getAllDecrypted(activeUserId)
.then((ciphers) => {
this.ciphersSubject.next(ciphers.filter((cipher) => cipherIds.includes(cipher.id)));
})
.catch((error) => this.logService.error(error));
});
}
ngOnDestroy() {
this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject
}
async chooseCipher(cipherId: string) {
this.session?.confirmChosenCipher(cipherId, true);
await this.router.navigate(["/"]);
await this.desktopSettingsService.setModalMode(false);
}
async closeModal() {
await this.router.navigate(["/"]);
await this.desktopSettingsService.setModalMode(false);
this.session.notifyConfirmCreateCredential(false);
this.session.confirmChosenCipher(null);
}
}