summaryrefslogtreecommitdiffstats
path: root/examples/webenginequick
diff options
context:
space:
mode:
authorAnu Aliyas <anu.aliyas@qt.io>2023-05-12 13:32:12 +0200
committerAnu Aliyas <anu.aliyas@qt.io>2023-07-12 17:20:57 +0200
commit605b0b3dcce24ff82c1e7a1ab3db7dace9668b81 (patch)
treee688549a6ef361f243ec82a406abb875ae0ee371 /examples/webenginequick
parenta3452104907874f4a3ffee83ec99c639004405e6 (diff)
Support FIDO2 user verification
- Implemented AuthenticatorRequestClientDelegateQt support to handle authenticator requests. - Added FIDO user verification and resident credential support Fixes: QTBUG-90938 Fixes: QTBUG-90941 Change-Id: I6367791e1e9e8aaac27c376408377f838832f426 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'examples/webenginequick')
-rw-r--r--examples/webenginequick/quicknanobrowser/BrowserWindow.qml8
-rw-r--r--examples/webenginequick/quicknanobrowser/CMakeLists.txt1
-rw-r--r--examples/webenginequick/quicknanobrowser/WebAuthDialog.qml287
-rw-r--r--examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc24
-rw-r--r--examples/webenginequick/quicknanobrowser/resources.qrc1
5 files changed, 321 insertions, 0 deletions
diff --git a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
index d5ae19f54..bfbe13f6d 100644
--- a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
+++ b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
@@ -607,6 +607,9 @@ ApplicationWindow {
featurePermissionDialog.feature = feature;
featurePermissionDialog.visible = true;
}
+ onWebAuthUXRequested: function(request) {
+ webAuthDialog.init(request);
+ }
Timer {
id: reloadTimer
@@ -759,6 +762,11 @@ ApplicationWindow {
anchors.fill: parent
}
+ WebAuthDialog {
+ id: webAuthDialog
+ visible: false
+ }
+
function onDownloadRequested(download) {
downloadView.visible = true;
downloadView.append(download);
diff --git a/examples/webenginequick/quicknanobrowser/CMakeLists.txt b/examples/webenginequick/quicknanobrowser/CMakeLists.txt
index c1f0080bb..8a1a9b706 100644
--- a/examples/webenginequick/quicknanobrowser/CMakeLists.txt
+++ b/examples/webenginequick/quicknanobrowser/CMakeLists.txt
@@ -53,6 +53,7 @@ set(resources_resource_files
"DownloadView.qml"
"FindBar.qml"
"FullScreenNotification.qml"
+ "WebAuthDialog.qml"
)
qt_add_resources(quicknanobrowser "resources"
diff --git a/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml
new file mode 100644
index 000000000..4af401237
--- /dev/null
+++ b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml
@@ -0,0 +1,287 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtWebEngine
+
+Dialog {
+ id: webAuthDialog
+ anchors.centerIn: parent
+ width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
+ contentWidth: verticalLayout.width +10;
+ contentHeight: verticalLayout.height +10;
+ standardButtons: Dialog.Cancel | Dialog.Apply
+ title: "WebAuth Request"
+
+ property var selectAccount;
+ property var authrequest: null;
+
+ Connections {
+ id: webauthConnection
+ ignoreUnknownSignals: true
+ function onStateChanged(state) {
+ webAuthDialog.setupUI(state);
+ }
+ }
+
+ onApplied: {
+ switch (webAuthDialog.authrequest.state) {
+ case WebEngineWebAuthUXRequest.CollectPIN:
+ webAuthDialog.authrequest.setPin(pinEdit.text);
+ break;
+ case WebEngineWebAuthUXRequest.SelectAccount:
+ webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
+ break;
+ default:
+ break;
+ }
+ }
+
+ onRejected: {
+ webAuthDialog.authrequest.cancel();
+ }
+
+ function init(request) {
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ webAuthDialog.authrequest = request;
+ webauthConnection.target = request;
+ setupUI(webAuthDialog.authrequest.state)
+ webAuthDialog.visible = true;
+ pinEntryError.visible = false;
+ }
+
+ function setupUI(state) {
+ switch (state) {
+ case WebEngineWebAuthUXRequest.SelectAccount:
+ setupSelectAccountUI();
+ break;
+ case WebEngineWebAuthUXRequest.CollectPIN:
+ setupCollectPIN();
+ break;
+ case WebEngineWebAuthUXRequest.FinishTokenCollection:
+ setupFinishCollectToken();
+ break;
+ case WebEngineWebAuthUXRequest.RequestFailed:
+ setupErrorUI();
+ break;
+ case WebEngineWebAuthUXRequest.Completed:
+ webAuthDialog.close();
+ break;
+ }
+ }
+
+ ButtonGroup {
+ id : selectAccount;
+ exclusive: true;
+ }
+
+ ListModel {
+ id: selectAccountModel
+
+ }
+ contentItem: Item {
+ ColumnLayout {
+ id : verticalLayout
+ spacing : 10
+
+ Label {
+ id: heading
+ text: "";
+ }
+
+ Label {
+ id: description
+ text: "";
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: pinLabel
+ text: "PIN";
+ }
+ TextInput {
+ id: pinEdit
+ text: "EnterPin"
+ enabled: true
+ focus: true
+ color: "white"
+ layer.sourceRect: {
+ Rectangle: {
+ width: 20
+ height: 20
+ color: "#00B000"
+ }
+ }
+ }
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: confirmPinLabel
+ text: "Confirm PIN";
+ }
+ TextEdit {
+ id: confirmPinEdit
+ text: ""
+ }
+ }
+
+ Label {
+ id: pinEntryError
+ text: "";
+ }
+
+ Repeater {
+ id : selectAccountRepeater
+ model: selectAccountModel
+ Column {
+ spacing : 5
+ RadioButton {
+ text: modelData
+ ButtonGroup.group : selectAccount;
+ onClicked: function(){
+ webAuthDialog.selectAccount = text;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function setupSelectAccountUI() {
+ webAuthDialog.selectAccount = "";
+ heading.text = "Choose a passkey";
+ description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;
+
+ selectAccountModel.clear();
+ var userNames = webAuthDialog.authrequest.userNames;
+ for (var i = 0; i < userNames.length; i++) {
+ selectAccountModel.append( {"name" : userNames[i]});
+ }
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = true;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupCollectPIN() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+
+ pinEdit.clear();
+
+ if (requestInfo.reason === WebEngineWebAuthUXRequest.Challenge) {
+ heading.text = "PIN required";
+ description.text = "Enter the PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ } else if (reason === WebEngineWebAuthUXRequest.Set) {
+ heading.text = "Set PIN ";
+ description.text = "Set new PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = true;
+ confirmPinEdit.visible = true;
+ }
+ pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
+ pinEntryError.visible = true;
+ selectAccountModel.clear();
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ standardButton(Dialog.Apply).visible = true;
+ }
+
+ function getPINErrorDetails() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+ switch (requestInfo.error) {
+ case WebEngineWebAuthUXRequest.NoError:
+ return "";
+ case WebEngineWebAuthUXRequest.TooShort:
+ return "Too short";
+ case WebEngineWebAuthUXRequest.InternalUvLocked:
+ return "Internal Uv Locked";
+ case WebEngineWebAuthUXRequest.WrongPIN:
+ return "Wrong PIN";
+ case WebEngineWebAuthUXRequest.InvalidCharacters:
+ return "Invalid Characters";
+ case WebEngineWebAuthUXRequest.SameAsCurrentPIN:
+ return "Same as current PIN";
+ }
+ }
+
+ function getRequestFailureResaon() {
+ var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
+ switch (requestFailureReason) {
+ case WebEngineWebAuthUXRequest.Timeout:
+ return " Request Timeout";
+ case WebEngineWebAuthUXRequest.KeyNotRegistered:
+ return "Key not registered";
+ case WebEngineWebAuthUXRequest.KeyAlreadyRegistered:
+ return "You already registered this device. You don't have to register it again
+ Try agin with different key or device";
+ case WebEngineWebAuthUXRequest.SoftPINBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.
+ To unlock it, remove and reinsert it.";
+ case WebEngineWebAuthUXRequest.HardPINBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.
+ You'll need to reset the security key.";
+ case WebEngineWebAuthUXRequest.AuthenticatorRemovedDuringPINEntry:
+ return "Authenticator removed during verification. Please reinsert and try again";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingResidentKeys:
+ return "Authenticator doesn't have resident key support";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingUserVerification:
+ return "Authenticator missing user verification";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingLargeBlob:
+ return "Authenticator missing Large Blob support";
+ case WebEngineWebAuthUXRequest.NoCommonAlgorithms:
+ return "No common Algorithms";
+ case WebEngineWebAuthUXRequest.StorageFull:
+ return "Storage full";
+ case WebEngineWebAuthUXRequest.UserConsentDenied:
+ return "User consent denied";
+ case WebEngineWebAuthUXRequest.WinUserCancelled:
+ return "User cancelled request";
+ }
+ }
+
+ function setupFinishCollectToken() {
+ heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
+ description.text = "Touch your security key again to complete the request.";
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupErrorUI() {
+ heading.text = "Something went wrong";
+ description.text = getRequestFailureResaon();
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Close"
+ }
+}
diff --git a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
index e99642661..21313c1a4 100644
--- a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
+++ b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
@@ -143,6 +143,30 @@
\skipto Escape
\printuntil /^\ {4}\}/
+ \section1 Handling WebAuth/FIDO UX Requests
+
+ We use the \c onWebAuthUXRequested() signal handler to handle requests for
+ WebAuth/FIDO UX. The \c request parameter is an instance of WebEngineWebAuthUXRequest
+ which contains UX request details and APIs required to process the request.
+ We use it to construct WebAuthUX dialog and initiates the UX request flow.
+
+ \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml
+ \skipto onWebAuthUXRequested
+ \printuntil }
+
+ The \l WebEngineWebAuthUXRequest object periodically emits the \l
+ {WebEngineWebAuthUXRequest::}{stateChanged} signal to notify potential
+ observers of the current WebAuth UX states. The observers update the WebAuth
+ dialog accordingly. We use onStateChanged() signal handler to handle
+ state change requests. See \c WebAuthDialog.qml for an example
+ of how these signals can be handled.
+
+ \quotefromfile webenginequick/quicknanobrowser/WebAuthDialog.qml
+ \skipto Connections
+ \printuntil }
+ \skipto function init(request)
+ \printuntil }
+
\section1 Files and Attributions
The example uses icons from the Tango Icon Library:
diff --git a/examples/webenginequick/quicknanobrowser/resources.qrc b/examples/webenginequick/quicknanobrowser/resources.qrc
index 9d1f927d3..0a0b42bbb 100644
--- a/examples/webenginequick/quicknanobrowser/resources.qrc
+++ b/examples/webenginequick/quicknanobrowser/resources.qrc
@@ -6,6 +6,7 @@
<file>DownloadView.qml</file>
<file>FindBar.qml</file>
<file>FullScreenNotification.qml</file>
+ <file>WebAuthDialog.qml</file>
</qresource>
<qresource prefix="/icons">
<file alias="go-next.png">icons/3rdparty/go-next.png</file>