1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 14:04:03 +00:00
This commit is contained in:
Evan Bassler
2025-02-13 10:25:24 -06:00
6 changed files with 153 additions and 89 deletions

View File

@@ -1,96 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17021"
targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"
customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17021" />
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0" />
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"
customClass="CredentialProviderViewController" customModuleProvider="target">
<customObject id="-2" userLabel="File's Owner" customClass="CredentialProviderViewController" customModule="autofill_extension" customModuleProvider="target">
<connections>
<outlet property="view" destination="1" id="2" />
<outlet property="view" destination="1" id="2"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder" />
<customObject id="-3" userLabel="Application" customClass="NSObject" />
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="378" height="94" />
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="378" height="94"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO"
id="1uM-r7-H1c">
<rect key="frame" x="177" y="3" width="197" height="32" />
<buttonCell key="cell" type="push" title="Return Example Password"
bezelStyle="rounded" alignment="center" borderStyle="border"
imageScaling="proportionallyDown" inset="2" id="2l4-PO-we5">
<behavior key="behavior" pushIn="YES" lightByBackground="YES"
lightByGray="YES" />
<font key="font" metaFont="system" />
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1uM-r7-H1c">
<rect key="frame" x="184" y="3" width="191" height="32"/>
<buttonCell key="cell" type="push" title="Return Example Password" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="2l4-PO-we5">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent">D</string>
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="passwordSelected:" target="-2" id="yic-EC-GGk" />
<action selector="passwordSelected:" target="-2" id="yic-EC-GGk"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO"
id="NVE-vN-dkz">
<rect key="frame" x="99" y="3" width="82" height="32" />
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual"
constant="60" id="cP1-hK-9ZX" />
</constraints>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded"
alignment="center" borderStyle="border" imageScaling="proportionallyDown"
inset="2" id="6Up-t3-mwm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES"
lightByGray="YES" />
<font key="font" metaFont="system" />
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz">
<rect key="frame" x="114" y="3" width="76" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
Gw
</string>
</buttonCell>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/>
</constraints>
<connections>
<action selector="cancel:" target="-2" id="Qav-AK-DGt" />
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/>
</connections>
</button>
<textField verticalHuggingPriority="750"
translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
<rect key="frame" x="135" y="63" width="108" height="16" />
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping"
sendsActionOnEndEditing="YES" alignment="left"
title="autofill-extension hello" id="0xp-rC-2gr">
<font key="font" metaFont="systemBold" />
<color key="textColor" name="controlTextColor" catalog="System"
colorSpace="catalog" />
<color key="backgroundColor" name="controlColor" catalog="System"
colorSpace="catalog" />
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
<rect key="frame" x="112" y="63" width="154" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="autofill-extension hello" id="0xp-rC-2gr">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz"
secondAttribute="trailing" constant="8" id="1UO-J1-LbJ" />
<constraint firstItem="NVE-vN-dkz" firstAttribute="leading"
relation="greaterThanOrEqual" secondItem="1" secondAttribute="leading"
constant="20" symbolic="YES" id="3N9-qo-UfS" />
<constraint firstAttribute="bottom" secondItem="1uM-r7-H1c" secondAttribute="bottom"
constant="10" id="4wH-De-nMF" />
<constraint firstItem="NVE-vN-dkz" firstAttribute="firstBaseline"
secondItem="aNc-0i-CWK" secondAttribute="baseline" constant="50" id="Dpq-cK-cPE" />
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom"
constant="10" id="USG-Gg-of3" />
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz"
secondAttribute="trailing" constant="8" id="a8N-vS-Ew9" />
<constraint firstAttribute="trailing" secondItem="1uM-r7-H1c"
secondAttribute="trailing" constant="10" id="qfT-cw-QQ2" />
<constraint firstAttribute="centerX" secondItem="aNc-0i-CWK"
secondAttribute="centerX" id="uV3-Wn-RA3" />
<constraint firstItem="aNc-0i-CWK" firstAttribute="top" secondItem="1"
secondAttribute="top" constant="15" id="vpR-tf-ebx" />
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="1UO-J1-LbJ"/>
<constraint firstItem="NVE-vN-dkz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="3N9-qo-UfS"/>
<constraint firstAttribute="bottom" secondItem="1uM-r7-H1c" secondAttribute="bottom" constant="10" id="4wH-De-nMF"/>
<constraint firstItem="NVE-vN-dkz" firstAttribute="firstBaseline" secondItem="aNc-0i-CWK" secondAttribute="baseline" constant="50" id="Dpq-cK-cPE"/>
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/>
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="a8N-vS-Ew9"/>
<constraint firstAttribute="trailing" secondItem="1uM-r7-H1c" secondAttribute="trailing" constant="10" id="qfT-cw-QQ2"/>
<constraint firstAttribute="centerX" secondItem="aNc-0i-CWK" secondAttribute="centerX" id="uV3-Wn-RA3"/>
<constraint firstItem="aNc-0i-CWK" firstAttribute="top" secondItem="1" secondAttribute="top" constant="15" id="vpR-tf-ebx"/>
</constraints>
<point key="canvasLocation" x="162" y="146" />
<point key="canvasLocation" x="162" y="146"/>
</customView>
</objects>
</document>
</document>

View File

@@ -48,6 +48,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
}
override func loadView() {
let view = NSView()
view.isHidden = true
//view.backgroundColor = .clear
self.view = view
}
/*
Implement this method if your extension supports showing credentials in the QuickType bar.
@@ -77,6 +84,9 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}
override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) {
//logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(credentialRequest) called \(request)")
if let request = credentialRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity {
@@ -155,6 +165,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) {
logger.log("[autofill-extension] prepareInterface")
// Create a timer to show UI after 10 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
guard let self = self else { return }
// Configure and show UI elements for manual cancellation
self.configureTimeoutUI()
}
if let request = registrationRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = registrationRequest.credentialIdentity as? ASPasskeyCredentialIdentity {
logger.log("[autofill-extension] prepareInterface(passkey) called \(request)")
@@ -232,6 +249,61 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
for serviceIdentifier in serviceIdentifiers {
logger.log(" service: \(serviceIdentifier.identifier)")
}
class CallbackImpl: PreparePasskeyAssertionCallback {
let ctx: ASCredentialProviderExtensionContext
required init(_ ctx: ASCredentialProviderExtensionContext) {
self.ctx = ctx
}
func onComplete(credential: PasskeyAssertionResponse) {
ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential(
userHandle: credential.userHandle,
relyingParty: credential.rpId,
signature: credential.signature,
clientDataHash: credential.clientDataHash,
authenticatorData: credential.authenticatorData,
credentialID: credential.credentialId
))
}
func onError(error: BitwardenError) {
ctx.cancelRequest(withError: error)
}
}
let userVerification = switch requestParameters.userVerificationPreference {
case .preferred:
UserVerification.preferred
case .required:
UserVerification.required
default:
UserVerification.discouraged
}
// TODO: PasskeyAssertionRequest does not implement allowedCredentials, extensions and required credentialId, username, userhandle, recordIdentifier??
let req = PasskeyAssertionRequest(
rpId: requestParameters.relyingPartyIdentifier,
// TODO: remove once the PasskeyAssertionRequest type has been improved
credentialId: Data(),
userName: "",
userHandle: Data(),
recordIdentifier: "",
//allowedCredentials: requestParameters.allowedCredentials,
//extensionInput: requestParameters.extensionInput,
clientDataHash: requestParameters.clientDataHash,
userVerification: userVerification
)
CredentialProviderViewController.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext))
return
}
private func configureTimeoutUI() {
self.view.isHidden = false;
}
}

View File

@@ -76,21 +76,32 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private desktopSettingsService: DesktopSettingsService,
) {}
private confirmCredentialSubject = new Subject<boolean>();
private createdCipher: Cipher;
// Method implementation
async pickCredential(
params: PickCredentialParams,
): Promise<{ cipherId: string; userVerified: boolean }> {
// Add your implementation here
return { cipherId: "", userVerified: false };
}
this.logService.warning("pickCredential desktop function", params);
private confirmCredentialSubject = new Subject<void>();
private createdCipher: Cipher;
try {
await this.showUi();
await this.waitForUiCredentialConfirmation();
return { cipherId: params.cipherIds[0], userVerified: true };
} finally {
// Make sure to clean up so the app is never stuck in modal mode?
await this.desktopSettingsService.setInModalMode(false);
}
}
/**
* Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
*/
notifyConfirmCredential() {
this.confirmCredentialSubject.next();
notifyConfirmCredential(confirmed: boolean): void {
this.confirmCredentialSubject.next(confirmed);
this.confirmCredentialSubject.complete();
}
@@ -98,7 +109,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
* Returns once the UI has confirmed and completed the operation
* @returns
*/
private async waitForUiCredentialConfirmation(): Promise<void> {
private async waitForUiCredentialConfirmation(): Promise<boolean> {
return lastValueFrom(this.confirmCredentialSubject);
}
@@ -125,8 +136,10 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
await this.showUi(rpId);
// Wait for the UI to wrap up
await this.waitForUiCredentialConfirmation();
const confirmation = await this.waitForUiCredentialConfirmation();
if (!confirmation) {
throw new Error("User cancelled");
}
// Create the credential
await this.createCredential({
credentialName,

View File

@@ -9,7 +9,6 @@
<h2 bitTypography="h6">{{ "savePasskeyQuestion" | i18n }}</h2>
</div>
<bit-search></bit-search>
<button
type="button"

View File

@@ -15,7 +15,7 @@ import {
SectionComponent,
TableModule,
} from "@bitwarden/components";
import { SearchComponent } from "@bitwarden/components/src/search/search.component";
// import { SearchComponent } from "@bitwarden/components/src/search/search.component";
import { BitwardenShield } from "../../../../../../libs/auth/src/angular/icons";
import { BitIconButtonComponent } from "../../../../../../libs/components/src/icon-button/icon-button.component";
@@ -42,7 +42,7 @@ import { DesktopSettingsService } from "../../../platform/services/desktop-setti
SectionComponent,
ItemModule,
BadgeModule,
SearchComponent,
// SearchComponent,
],
templateUrl: "fido2-create.component.html",
})
@@ -98,7 +98,7 @@ export class Fido2CreateComponent implements OnInit {
// userVerification: true,
// });
this.session.notifyConfirmCredential();
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
@@ -113,5 +113,6 @@ export class Fido2CreateComponent implements OnInit {
async closeModal() {
await this.router.navigate(["/"]);
await this.desktopSettingsService.setInModalMode(false);
this.session.notifyConfirmCredential(false);
}
}

View File

@@ -58,8 +58,6 @@ export class Fido2VaultComponent implements OnInit {
private readonly router: Router,
) {}
//function firings after init
async ngOnInit() {
this.rpId = history.state.rpid;
this.session = this.fido2UserInterfaceService.getCurrentSession();
@@ -78,6 +76,12 @@ export class Fido2VaultComponent implements OnInit {
async confirmPasskey() {
try {
this.session = this.fido2UserInterfaceService.getCurrentSession();
// this.session.pickCredential({
// cipherIds: [],
// userVerification: false,
// });
// Retrieve the current UI session to control the flow
if (!this.session) {
// todo: handle error
@@ -93,7 +97,7 @@ export class Fido2VaultComponent implements OnInit {
// userVerification: true,
// });
this.session.notifyConfirmCredential();
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
@@ -106,7 +110,8 @@ export class Fido2VaultComponent implements OnInit {
}
async closeModal() {
// await this.router.navigate(["/"]);
// await this.desktopSettingsService.setInModalMode(false);
await this.router.navigate(["/"]);
await this.desktopSettingsService.setInModalMode(false);
this.session.notifyConfirmCredential(false);
}
}