1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 21:50:15 +00:00

Integration working

This commit is contained in:
Anders Åberg
2025-01-24 11:35:49 +01:00
parent 137bc8ace8
commit 4af3950009
5 changed files with 133 additions and 53 deletions

View File

@@ -1,9 +1,7 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import {
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction
} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
import { DesktopFido2UserInterfaceService } from "../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
@@ -45,31 +43,35 @@ export class Fido2PlaceholderComponent {
) {}
async confirmPasskey() {
// placeholder, actual api arguments needed here should be discussed
// just show casing we can call into the session to create the credential or change it.
const desktopUiService = this.fido2UserInterfaceService as DesktopFido2UserInterfaceService;
console.log("Got desktopService", desktopUiService.guid);
console.log("checking for session", this.fido2UserInterfaceService);
try {
console.log("checking for session", this.fido2UserInterfaceService);
// Add timeout to avoid infinite hanging
const session = desktopUiService.getCurrentSession();
if (!session) {
// todo: handle error
console.error("No session found");
return;
}
console.log("Got session", session.guid);
const desktopService = this.fido2UserInterfaceService as DesktopFido2UserInterfaceService;
// const cipher = await session.createCredential({
// userHandle: "userHandle2",
// userName: "username2",
// credentialName: "zxsd2",
// rpId: "webauthn.io",
// userVerification: true,
// });
const session = await desktopService.getCurrentSession();
console.log("Got session", session);
await session.createCredential({
userHandle: "userHandle",
userName: "",
credentialName: "",
rpId: "",
userVerification: true,
});
console.log("Created credential, will notify complete");
session.notifyOperationCompleted();
await this.router.navigate(["/"]);
await this.desktopSettingsService.setInModalMode(false);
session.notifyOperationCompleted();
await this.router.navigate(["/"]);
await this.desktopSettingsService.setInModalMode(false);
} catch (error) {
console.error("Failed during confirmation:", error);
// Handle error appropriately
}
}
async closeModal() {

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { APP_INITIALIZER, NgModule } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, merge } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
@@ -331,6 +332,8 @@ const safeProviders: SafeProvider[] = [
AccountService,
LogService,
MessagingServiceAbstraction,
Router,
DesktopSettingsService,
],
}),
safeProvider({

View File

@@ -1,4 +1,5 @@
import { filter, firstValueFrom, map, shareReplay, Subject } from "rxjs";
import { Router } from "@angular/router";
import { filter, lastValueFrom, firstValueFrom, map, shareReplay, Subject, tap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -13,12 +14,17 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType, SecureNoteType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
import { UsernameAlgorithms } from "@bitwarden/generator-core";
import { DesktopSettingsService } from "src/platform/services/desktop-settings.service";
// import the angular router
export class DesktopFido2UserInterfaceService
implements Fido2UserInterfaceServiceAbstraction<void>
@@ -29,22 +35,17 @@ export class DesktopFido2UserInterfaceService
private accountService: AccountService,
private logService: LogService,
private messagingService: MessagingService,
) {}
private currentSessionSubject = new Subject<DesktopFido2UserInterfaceSession | undefined>();
private currentSubject$ = this.currentSessionSubject.pipe(
shareReplay({ refCount: true, bufferSize: 1 }),
filter(c => c !== undefined)
);
setCurrentSession(session: DesktopFido2UserInterfaceSession) {
this.currentSessionSubject.next(session);
}
getCurrentSession(): Promise<DesktopFido2UserInterfaceSession> {
return firstValueFrom(this.currentSubject$);
private router: Router,
private desktopSettingsService: DesktopSettingsService,
) {
this.guid = "PARENT" + Math.random().toString(36).substring(7);
}
guid: string;
private currentSession: any;
getCurrentSession(): DesktopFido2UserInterfaceSession | undefined {
return this.currentSession;
}
async newSession(
fallbackSupported: boolean,
@@ -52,13 +53,21 @@ export class DesktopFido2UserInterfaceService
abortController?: AbortController,
): Promise<DesktopFido2UserInterfaceSession> {
this.logService.warning("newSession", fallbackSupported, abortController);
return new DesktopFido2UserInterfaceSession(
const session = new DesktopFido2UserInterfaceSession(
this.authService,
this.cipherService,
this.accountService,
this.logService,
this.messagingService,
this.router,
this.desktopSettingsService,
);
console.log("In parent", this.guid);
console.log("Setting current session", session.guid);
this.currentSession = session;
return session;
}
}
@@ -69,17 +78,42 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private accountService: AccountService,
private logService: LogService,
private messagingService: MessagingService,
) {}
async pickCredential({
cipherIds,
userVerification,
}: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
this.logService.warning("pickCredential", cipherIds, userVerification);
return { cipherId: cipherIds[0], userVerified: userVerification };
private router: Router,
private desktopSettingsService: DesktopSettingsService,
) {
this.guid = "SESSION" + Math.random().toString(36).substring(7);
}
guid: string;
pickCredential: (
params: PickCredentialParams,
) => Promise<{ cipherId: string; userVerified: boolean }>;
private operationSubject = new Subject<void>();
private createdCipher: Cipher;
/**
* Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
*/
notifyOperationCompleted() {
this.operationSubject.next();
this.operationSubject.complete();
}
/**
* Returns once the UI has confirmed and completed the operation
* @returns
*/
private async waitForUICompletion(): Promise<void> {
return lastValueFrom(this.operationSubject);
}
/**
* This is called by the OS. It loads the UI and waits for the user to confirm the new credential. Once the UI has confirmed, it returns to the the OS.
* @param param0
* @returns
*/
async confirmNewCredential({
credentialName,
userName,
@@ -94,8 +128,40 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
rpId,
);
this.messagingService.send("loadurl", { url: "/passkeys", modal: true });
try {
// Load the UI:
// maybe this modal mode thing shouldn't be done here?
await this.desktopSettingsService.setInModalMode(true);
await this.router.navigate(["/passkeys"]);
// Wait for the UI to wrap up
await this.waitForUICompletion();
await this.createCredential({
credentialName,
userName,
rpId,
userHandle: "",
userVerification,
});
console.log("Returning from confirmNewCredential, created cipher:", this.createdCipher);
// 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 };
} finally {
await this.desktopSettingsService.setInModalMode(false);
}
}
/**
* 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> {
// Store the passkey on a new cipher to avoid replacing something important
const cipher = new CipherView();
cipher.name = credentialName;
@@ -118,7 +184,9 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
const encCipher = await this.cipherService.encrypt(cipher, activeUserId);
const createdCipher = await this.cipherService.createWithServer(encCipher);
return { cipherId: createdCipher.id, userVerified: userVerification };
this.createdCipher = createdCipher;
return createdCipher;
}
async informExcludedCredential(existingCipherIds: string[]): Promise<void> {

View File

@@ -132,6 +132,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
userVerification: params.requireUserVerification,
rpId: params.rpEntity.id,
});
console.log("rpid", params.rpEntity.id, response.cipherId);
const cipherId = response.cipherId;
userVerified = response.userVerified;
@@ -146,6 +147,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
keyPair = await createKeyPair();
pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
const encrypted = await this.cipherService.get(cipherId);
console.log("Encrypted", encrypted);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
@@ -153,6 +155,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
cipher = await encrypted.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId),
);
if (
!userVerified &&
@@ -174,13 +177,15 @@ export class Fido2AuthenticatorService<ParentWindowReference>
await this.cipherService.updateWithServer(reencrypted);
await this.cipherService.clearCache(activeUserId);
credentialId = fido2Credential.credentialId;
console.log("rpid", params.rpEntity.id);
} catch (error) {
this.logService?.error(
`[Fido2Authenticator] Aborting because of unknown error when creating credential: ${error}`,
);
throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.Unknown);
}
console.log("authdata rpid", params.rpEntity.id);
const authData = await generateAuthData({
rpId: params.rpEntity.id,
credentialId: parseCredentialId(credentialId),

View File

@@ -10,6 +10,7 @@ import {
shareReplay,
Subject,
switchMap,
tap,
} from "rxjs";
import { SemVer } from "semver";
@@ -141,6 +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)),
shareReplay({ bufferSize: 1, refCount: true }),
);
this.addEditCipherInfo$ = this.addEditCipherInfoState.state$;