Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wire passkey autofill to UI #13051

Draft
wants to merge 12 commits into
base: passkey-window-creation
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
<?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"/>
<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"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<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"/>
<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"/>
Expand All @@ -28,24 +29,24 @@
</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>
<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
</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"/>
</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" id="0xp-rC-2gr">
<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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -77,6 +84,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}

override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) {


if let request = credentialRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity {

Expand Down Expand Up @@ -153,8 +162,19 @@ 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)")

class CallbackImpl: PreparePasskeyRegistrationCallback {
let ctx: ASCredentialProviderExtensionContext
required init(_ ctx: ASCredentialProviderExtensionContext) {
Expand Down Expand Up @@ -192,11 +212,18 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
userVerification: userVerification,
supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) }
)
logger.log("[autofill-extension] prepareInterface(passkey) calling preparePasskeyRegistration")
// Log details of the request
logger.log("[autofill-extension] rpId: \(req.rpId)")
logger.log("[autofill-extension] rpId: \(req.userName)")

CredentialProviderViewController.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext))
return
}
}

logger.log("[autofill-extension] We didn't get a passkey")

// If we didn't get a passkey, return an error
self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid registration request"))
}
Expand All @@ -223,4 +250,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}
}

private func configureTimeoutUI() {
self.view.isHidden = false;
}

}
2 changes: 0 additions & 2 deletions apps/desktop/resources/entitlements.mas.inherit.plist
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!--
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/>
-->
</dict>
</plist>
2 changes: 0 additions & 2 deletions apps/desktop/resources/entitlements.mas.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<!--
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/>
-->
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>/Library/Application Support/Mozilla/NativeMessagingHosts/</string>
Expand Down
54 changes: 52 additions & 2 deletions apps/desktop/src/app/components/fido2placeholder.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";

import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";

@Component({
Expand All @@ -11,6 +15,15 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings
>
<h1 style="color: black">Select your passkey</h1>
<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
Expand All @@ -23,14 +36,51 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings
</div>
`,
})
export class Fido2PlaceholderComponent {
export class Fido2PlaceholderComponent implements OnInit {
session?: DesktopFido2UserInterfaceSession = null;
constructor(
private readonly desktopSettingsService: DesktopSettingsService,
private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
private readonly router: Router,
) {}

ngOnInit(): void {
this.session = this.fido2UserInterfaceService.getCurrentSession();
}

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) {
abergs marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Handle error appropriately
}
}

async closeModal() {
await this.router.navigate(["/"]);
await this.desktopSettingsService.setInModalMode(false);

this.session.notifyConfirmCredential(false);
}
}
17 changes: 15 additions & 2 deletions apps/desktop/src/app/services/services.module.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -323,9 +324,21 @@ const safeProviders: SafeProvider[] = [
],
}),
safeProvider({
provide: Fido2UserInterfaceServiceAbstraction,
provide: DesktopFido2UserInterfaceService,
useClass: DesktopFido2UserInterfaceService,
deps: [AuthServiceAbstraction, CipherServiceAbstraction, AccountService, LogService],
deps: [
AuthServiceAbstraction,
CipherServiceAbstraction,
AccountService,
LogService,
MessagingServiceAbstraction,
Router,
DesktopSettingsService,
],
}),
safeProvider({
provide: Fido2UserInterfaceServiceAbstraction, // We utilize desktop specific methods when wiring OS API's
useExisting: DesktopFido2UserInterfaceService,
}),
safeProvider({
provide: Fido2AuthenticatorServiceAbstraction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Injectable, OnDestroy } from "@angular/core";
import { autofill } from "desktop_native/napi";
import {
EMPTY,
Subject,
distinctUntilChanged,
firstValueFrom,
map,
mergeMap,
switchMap,
takeUntil,
takeUntil
} from "rxjs";

import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
Expand Down Expand Up @@ -56,9 +55,9 @@ export class DesktopAutofillService implements OnDestroy {
.pipe(
distinctUntilChanged(),
switchMap((enabled) => {
if (!enabled) {
/*if (!enabled) {
return EMPTY;
}
}*/

return this.cipherService.cipherViews$;
}),
Expand Down
Loading
Loading