mirror of
https://github.com/bitwarden/browser
synced 2026-02-22 04:14:04 +00:00
update cipher retrieval
This commit is contained in:
@@ -338,7 +338,7 @@ const routes: Routes = [
|
||||
component: Fido2VaultComponent,
|
||||
},
|
||||
{
|
||||
path: "create-passkey",
|
||||
path: "passkey-create",
|
||||
component: Fido2CreateComponent,
|
||||
},
|
||||
{
|
||||
|
||||
125
apps/desktop/src/app/components/fido2placeholder.component.ts
Normal file
125
apps/desktop/src/app/components/fido2placeholder.component.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { CommonModule } from "@angular/common"; // Add this
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
DesktopFido2UserInterfaceService,
|
||||
DesktopFido2UserInterfaceSession,
|
||||
} from "../../autofill/services/desktop-fido2-user-interface.service";
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule], // Add this
|
||||
|
||||
template: `
|
||||
<div
|
||||
style="background:white; display:flex; justify-content: center; align-items: center; flex-direction: column"
|
||||
>
|
||||
<h1 style="color: black">Select your passkey</h1>
|
||||
|
||||
<div *ngFor="let item of cipherIds$ | async">
|
||||
<button
|
||||
style="color:black; padding: 10px 20px; border: 1px solid blue; margin: 10px"
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
(click)="chooseCipher(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<button
|
||||
style="color:black; padding: 10px 20px; border: 1px solid black; margin: 10px"
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
(click)="confirmPasskey()"
|
||||
>
|
||||
Confirm passkey
|
||||
</button>
|
||||
<button
|
||||
style="color:black; padding: 10px 20px; border: 1px solid black; margin: 10px"
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
(click)="closeModal()"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class Fido2PlaceholderComponent implements OnInit, OnDestroy {
|
||||
session?: DesktopFido2UserInterfaceSession = null;
|
||||
private cipherIdsSubject = new BehaviorSubject<string[]>([]);
|
||||
cipherIds$: Observable<string[]> = this.cipherIdsSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
private readonly desktopSettingsService: DesktopSettingsService,
|
||||
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
|
||||
private readonly router: Router,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
|
||||
const cipherIds = await this.session?.getAvailableCipherIds();
|
||||
this.cipherIdsSubject.next(cipherIds || []);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Available cipher IDs", cipherIds);
|
||||
}
|
||||
|
||||
async chooseCipher(cipherId: string) {
|
||||
this.session?.confirmChosenCipher(cipherId);
|
||||
|
||||
await this.router.navigate(["/"]);
|
||||
await this.desktopSettingsService.setInModalMode(false);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject
|
||||
}
|
||||
|
||||
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(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.setInModalMode(false);
|
||||
} catch (error) {
|
||||
// TODO: Handle error appropriately
|
||||
}
|
||||
}
|
||||
|
||||
async closeModal() {
|
||||
await this.router.navigate(["/"]);
|
||||
await this.desktopSettingsService.setInModalMode(false);
|
||||
|
||||
this.session.notifyConfirmCredential(false);
|
||||
// little bit hacky:
|
||||
this.session.confirmChosenCipher(null);
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,12 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
|
||||
ipc.autofill.listenPasskeyAssertionWithoutUserInterface(
|
||||
async (clientId, sequenceNumber, request, callback) => {
|
||||
this.logService.warning("listenPasskeyAssertion", clientId, sequenceNumber, request);
|
||||
this.logService.warning(
|
||||
"listenPasskeyAssertion without user interface",
|
||||
clientId,
|
||||
sequenceNumber,
|
||||
request,
|
||||
);
|
||||
|
||||
// TODO: For some reason the credentialId is passed as an empty array in the request, so we need to
|
||||
// get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
|
||||
@@ -193,7 +198,7 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
|
||||
const controller = new AbortController();
|
||||
void this.fido2AuthenticatorService
|
||||
.getAssertion(this.convertAssertionRequest(request), null, controller)
|
||||
.getAssertion(this.convertAssertionRequest(request, true), null, controller)
|
||||
.then((response) => {
|
||||
callback(null, this.convertAssertionResponse(request, response));
|
||||
})
|
||||
@@ -261,10 +266,17 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
* @param assumeUserPresence For WithoutUserInterface requests, we assume the user is present
|
||||
* @returns
|
||||
*/
|
||||
private convertAssertionRequest(
|
||||
request:
|
||||
| autofill.PasskeyAssertionRequest
|
||||
| autofill.PasskeyAssertionWithoutUserInterfaceRequest,
|
||||
assumeUserPresence: boolean = false,
|
||||
): Fido2AuthenticatorGetAssertionParams {
|
||||
let allowedCredentials;
|
||||
if ("credentialId" in request) {
|
||||
@@ -289,6 +301,7 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
requireUserVerification:
|
||||
request.userVerification === "required" || request.userVerification === "preferred",
|
||||
fallbackSupported: false,
|
||||
assumeUserPresence: assumeUserPresence,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Router } from "@angular/router";
|
||||
import { lastValueFrom, firstValueFrom, map, Subject } from "rxjs";
|
||||
import {
|
||||
lastValueFrom,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Subject,
|
||||
filter,
|
||||
take,
|
||||
timeout,
|
||||
BehaviorSubject,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
@@ -77,30 +86,105 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
) {}
|
||||
|
||||
private confirmCredentialSubject = new Subject<boolean>();
|
||||
|
||||
private createdCipher: Cipher;
|
||||
private updatedCipher: CipherView;
|
||||
|
||||
private availableCipherIds = new BehaviorSubject<string[]>(null);
|
||||
private rpId = new BehaviorSubject<string>(null);
|
||||
|
||||
private chosenCipherSubject = new Subject<string>();
|
||||
|
||||
// Method implementation
|
||||
async pickCredential(
|
||||
params: PickCredentialParams,
|
||||
): Promise<{ cipherId: string; userVerified: boolean }> {
|
||||
this.logService.warning("pickCredential desktop function", params);
|
||||
async pickCredential({
|
||||
cipherIds,
|
||||
userVerification,
|
||||
assumeUserPresence,
|
||||
masterPasswordRepromptRequired,
|
||||
}: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
|
||||
this.logService.warning("pickCredential desktop function", {
|
||||
cipherIds,
|
||||
userVerification,
|
||||
assumeUserPresence,
|
||||
masterPasswordRepromptRequired,
|
||||
});
|
||||
|
||||
try {
|
||||
await this.showUi();
|
||||
// Check if we can return the credential without user interaction
|
||||
// TODO: Assume user presence is undefined
|
||||
if (cipherIds.length === 1 && !masterPasswordRepromptRequired) {
|
||||
this.logService.debug(
|
||||
"shortcut - Assuming user presence and returning cipherId",
|
||||
cipherIds[0],
|
||||
);
|
||||
return { cipherId: cipherIds[0], userVerified: userVerification };
|
||||
}
|
||||
|
||||
await this.waitForUiCredentialConfirmation();
|
||||
this.logService.debug("Could not shortcut, showing UI");
|
||||
|
||||
return { cipherId: params.cipherIds[0], userVerified: true };
|
||||
// make the cipherIds available to the UI.
|
||||
// Not sure if the UI also need to know about masterPasswordRepromptRequired -- probably not, otherwise we can send all of the params.
|
||||
this.availableCipherIds.next(cipherIds);
|
||||
|
||||
await this.showUi("/passkeys");
|
||||
|
||||
const chosenCipherId = await this.waitForUiChosenCipher();
|
||||
|
||||
this.logService.debug("Received chosen cipher", chosenCipherId);
|
||||
if (!chosenCipherId) {
|
||||
throw new Error("User cancelled");
|
||||
}
|
||||
|
||||
const resultCipherId = cipherIds.find((id) => id === chosenCipherId);
|
||||
|
||||
// TODO: perform userverification
|
||||
return { cipherId: resultCipherId, userVerified: true };
|
||||
} finally {
|
||||
// Make sure to clean up so the app is never stuck in modal mode?
|
||||
await this.desktopSettingsService.setInModalMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns once the UI has confirmed and completed the operation
|
||||
* @returns
|
||||
*/
|
||||
async getAvailableCipherIds(): Promise<string[]> {
|
||||
return lastValueFrom(
|
||||
this.availableCipherIds.pipe(
|
||||
filter((ids) => ids != null),
|
||||
take(1),
|
||||
timeout(50000),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getRpId(): Promise<string> {
|
||||
return lastValueFrom(
|
||||
this.rpId.pipe(
|
||||
filter((id) => id != null),
|
||||
take(1),
|
||||
timeout(50000),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
confirmChosenCipher(cipherId: string): void {
|
||||
this.chosenCipherSubject.next(cipherId);
|
||||
this.chosenCipherSubject.complete();
|
||||
}
|
||||
|
||||
private async waitForUiChosenCipher(): Promise<string> {
|
||||
return lastValueFrom(this.chosenCipherSubject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
|
||||
*/
|
||||
notifyConfirmCredential(confirmed: boolean): void {
|
||||
notifyConfirmCredential(confirmed: boolean, updatedCipher?: CipherView): void {
|
||||
if (updatedCipher) {
|
||||
this.updatedCipher = updatedCipher;
|
||||
}
|
||||
this.confirmCredentialSubject.next(confirmed);
|
||||
this.confirmCredentialSubject.complete();
|
||||
}
|
||||
@@ -109,7 +193,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
* Returns once the UI has confirmed and completed the operation
|
||||
* @returns
|
||||
*/
|
||||
private async waitForUiCredentialConfirmation(): Promise<boolean> {
|
||||
private async waitForUiNewCredentialConfirmation(): Promise<boolean> {
|
||||
return lastValueFrom(this.confirmCredentialSubject);
|
||||
}
|
||||
|
||||
@@ -133,52 +217,53 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
);
|
||||
|
||||
try {
|
||||
await this.showUi(rpId);
|
||||
await this.showUi("/passkey-create");
|
||||
|
||||
// Wait for the UI to wrap up
|
||||
const confirmation = await this.waitForUiCredentialConfirmation();
|
||||
const confirmation = await this.waitForUiNewCredentialConfirmation();
|
||||
if (!confirmation) {
|
||||
throw new Error("User cancelled");
|
||||
//if existing credential is selected, update credential
|
||||
}
|
||||
// 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.setInModalMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async showUi(rpId?: string) {
|
||||
private async showUi(route: string) {
|
||||
// Load the UI:
|
||||
// maybe toggling to modal mode shouldn't be done here?
|
||||
await this.desktopSettingsService.setInModalMode(true);
|
||||
//pass the rpid to the fido2placeholder component through routing parameter
|
||||
|
||||
// await this.router.navigate(["/passkeys"]);
|
||||
await this.router.navigate(["/passkeys"], { state: { rpid: rpId } });
|
||||
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
|
||||
this.rpId.next(rpId);
|
||||
|
||||
const cipher = new CipherView();
|
||||
cipher.name = credentialName;
|
||||
|
||||
@@ -205,6 +290,15 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
return createdCipher;
|
||||
}
|
||||
|
||||
async updateCredential(cipher: CipherView): Promise<void> {
|
||||
this.logService.warning("updateCredential");
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const encCipher = await this.cipherService.encrypt(cipher, activeUserId);
|
||||
await this.cipherService.updateWithServer(encCipher);
|
||||
}
|
||||
|
||||
async informExcludedCredential(existingCipherIds: string[]): Promise<void> {
|
||||
this.logService.warning("informExcludedCredential", existingCipherIds);
|
||||
}
|
||||
|
||||
@@ -23,15 +23,24 @@
|
||||
</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()">
|
||||
<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">Select</span>
|
||||
<span bitBadge slot="end">Save</span>
|
||||
</button>
|
||||
</bit-item>
|
||||
<button
|
||||
style="color: black; padding: 10px 20px; border: 1px solid black; margin: 10px"
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
(click)="confirmPasskey()"
|
||||
>
|
||||
Save as new login
|
||||
</button>
|
||||
</bit-section>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { RouterModule, Router } from "@angular/router";
|
||||
import { BehaviorSubject, firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
BadgeModule,
|
||||
@@ -47,38 +48,44 @@ import { DesktopSettingsService } from "../../../platform/services/desktop-setti
|
||||
templateUrl: "fido2-create.component.html",
|
||||
})
|
||||
export class Fido2CreateComponent implements OnInit {
|
||||
ciphers: CipherView[];
|
||||
rpId: string;
|
||||
session?: DesktopFido2UserInterfaceSession = null;
|
||||
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
||||
readonly Icons = { BitwardenShield };
|
||||
|
||||
session?: DesktopFido2UserInterfaceSession = null;
|
||||
constructor(
|
||||
private readonly desktopSettingsService: DesktopSettingsService,
|
||||
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
|
||||
private readonly cipherService: CipherService,
|
||||
private readonly domainSettingsService: DomainSettingsService,
|
||||
private readonly router: Router,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.rpId = history.state.rpid;
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
|
||||
if (!this.session) {
|
||||
await this.fido2UserInterfaceService.newSession(false, null);
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
}
|
||||
let allCiphers = [];
|
||||
const rpid = await this.session.getRpId();
|
||||
const equivalentDomains = await firstValueFrom(
|
||||
this.domainSettingsService.getUrlEquivalentDomains(rpid),
|
||||
);
|
||||
|
||||
if (this.rpId) {
|
||||
allCiphers = await this.cipherService.getAllDecryptedForUrl(this.rpId, [CipherType.Login]);
|
||||
} else {
|
||||
allCiphers = await this.cipherService.getAllDecrypted();
|
||||
}
|
||||
this.cipherService
|
||||
.getPasskeyCiphersForUrl(rpid)
|
||||
.then((ciphers) => {
|
||||
const relevantCiphers = ciphers.filter(
|
||||
(cipher) =>
|
||||
cipher.login.matchesUri(rpid, equivalentDomains) &&
|
||||
(!cipher.login.fido2Credentials || cipher.login.fido2Credentials.length === 0),
|
||||
);
|
||||
this.ciphersSubject.next(relevantCiphers);
|
||||
})
|
||||
.catch(() => {
|
||||
// console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
//filter all ciphers to only return login ciphers without fido2Credentials
|
||||
this.ciphers = allCiphers.filter((cipher) => {
|
||||
return cipher.type === CipherType.Login && cipher.login.fido2Credentials.length === 0;
|
||||
});
|
||||
async addPasskeyToCipher(cipher: CipherView) {
|
||||
this.session.notifyConfirmCredential(true, cipher);
|
||||
}
|
||||
|
||||
async confirmPasskey() {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
</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()">
|
||||
<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 }}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { RouterModule, Router } from "@angular/router";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
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,
|
||||
@@ -45,68 +44,38 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings
|
||||
templateUrl: "fido2-vault.component.html",
|
||||
})
|
||||
export class Fido2VaultComponent implements OnInit {
|
||||
ciphers: CipherView[];
|
||||
rpId: string;
|
||||
session?: DesktopFido2UserInterfaceSession = null;
|
||||
private ciphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||
ciphers$: Observable<CipherView[]> = this.ciphersSubject.asObservable();
|
||||
readonly Icons = { BitwardenShield };
|
||||
|
||||
session?: DesktopFido2UserInterfaceSession = null;
|
||||
constructor(
|
||||
private readonly desktopSettingsService: DesktopSettingsService,
|
||||
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
|
||||
private readonly cipherService: CipherService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly router: Router,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.rpId = history.state.rpid;
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
|
||||
if (!this.session) {
|
||||
await this.fido2UserInterfaceService.newSession(false, null);
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
}
|
||||
const cipherIds = await this.session?.getAvailableCipherIds();
|
||||
|
||||
if (this.rpId) {
|
||||
this.ciphers = await this.cipherService.getPasskeyCiphersForUrl(this.rpId);
|
||||
} else {
|
||||
this.ciphers = await this.cipherService.getAllDecrypted();
|
||||
}
|
||||
this.cipherService
|
||||
.getAllDecryptedForIds(cipherIds || [])
|
||||
.then((ciphers) => {
|
||||
this.ciphersSubject.next(ciphers);
|
||||
})
|
||||
.catch(() => {
|
||||
// console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
async confirmPasskey() {
|
||||
try {
|
||||
this.session = this.fido2UserInterfaceService.getCurrentSession();
|
||||
async chooseCipher(cipherId: string) {
|
||||
this.session?.confirmChosenCipher(cipherId);
|
||||
|
||||
// this.session.pickCredential({
|
||||
// cipherIds: [],
|
||||
// userVerification: false,
|
||||
// });
|
||||
// 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(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.setInModalMode(false);
|
||||
} catch (error) {
|
||||
// TODO: Handle error appropriately
|
||||
}
|
||||
await this.router.navigate(["/"]);
|
||||
await this.desktopSettingsService.setInModalMode(false);
|
||||
}
|
||||
|
||||
async closeModal() {
|
||||
|
||||
@@ -45,6 +45,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
||||
includeOtherTypes?: CipherType[],
|
||||
defaultMatch?: UriMatchStrategySetting,
|
||||
) => Promise<CipherView[]>;
|
||||
getAllDecryptedForIds: (ids: string[]) => Promise<CipherView[]>;
|
||||
getPasskeyCiphersForUrl: (url: string) => Promise<CipherView[]>;
|
||||
getPasskeyCiphers: () => Promise<CipherView[]>;
|
||||
filterCiphersForUrl: (
|
||||
|
||||
@@ -485,6 +485,11 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
|
||||
}
|
||||
|
||||
async getAllDecryptedForIds(ids: string[]): Promise<CipherView[]> {
|
||||
const ciphers = await this.getAllDecrypted();
|
||||
return ciphers.filter((cipher) => ids.includes(cipher.id));
|
||||
}
|
||||
|
||||
async filterCiphersForUrl(
|
||||
ciphers: CipherView[],
|
||||
url: string,
|
||||
|
||||
Reference in New Issue
Block a user