зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1415675 - Web Authentication - Support AbortSignal types r=jcj,smaug
Summary: This patch adds support for aborting WebAuthn requests via AbortSignals. https://w3c.github.io/webauthn/#abortoperation https://w3c.github.io/webauthn/#sample-aborting https://dom.spec.whatwg.org/#abortcontroller-api-integration It also adds a variety of request abortion/cancellation tests. To test request cancellation we can use USB tokens as those requests will never complete without a token and/or user interaction. A bonus here is that we'll have a little coverage for u2f-hid-rs. Reviewers: jcj, smaug Reviewed By: jcj, smaug Bug #: 1415675 Differential Revision: https://phabricator.services.mozilla.com/D245 --HG-- extra : amend_source : bd779d5c4c6a11dd8ce34c0cc86675825b799031
This commit is contained in:
Родитель
bfd41fe2bc
Коммит
73cfd2472a
|
@ -38,14 +38,14 @@ already_AddRefed<Promise>
|
|||
CredentialsContainer::Get(const CredentialRequestOptions& aOptions)
|
||||
{
|
||||
RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
|
||||
return mgr->GetAssertion(mParent, aOptions.mPublicKey);
|
||||
return mgr->GetAssertion(mParent, aOptions.mPublicKey, aOptions.mSignal);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CredentialsContainer::Create(const CredentialCreationOptions& aOptions)
|
||||
{
|
||||
RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
|
||||
return mgr->MakeCredential(mParent, aOptions.mPublicKey);
|
||||
return mgr->MakeCredential(mParent, aOptions.mPublicKey, aOptions.mSignal);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
|
|
|
@ -199,6 +199,7 @@ WebAuthnManager::ClearTransaction()
|
|||
}
|
||||
|
||||
mTransaction.reset();
|
||||
Unfollow();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -289,7 +290,8 @@ WebAuthnManager::Get()
|
|||
|
||||
already_AddRefed<Promise>
|
||||
WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
||||
const MakePublicKeyCredentialOptions& aOptions)
|
||||
const MakePublicKeyCredentialOptions& aOptions,
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aParent);
|
||||
|
@ -306,6 +308,12 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Abort the request if aborted flag is already set.
|
||||
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
nsString origin;
|
||||
nsCString rpId;
|
||||
rv = GetOrigin(aParent, origin, rpId);
|
||||
|
@ -475,11 +483,18 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
|||
|
||||
ListenForVisibilityEvents(aParent, this);
|
||||
|
||||
AbortSignal* signal = nullptr;
|
||||
if (aSignal.WasPassed()) {
|
||||
signal = &aSignal.Value();
|
||||
Follow(signal);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mTransaction.isNothing());
|
||||
mTransaction = Some(WebAuthnTransaction(aParent,
|
||||
promise,
|
||||
Move(info),
|
||||
Move(clientDataJSON)));
|
||||
Move(clientDataJSON),
|
||||
signal));
|
||||
|
||||
mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return promise.forget();
|
||||
|
@ -487,7 +502,8 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
|||
|
||||
already_AddRefed<Promise>
|
||||
WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
|
||||
const PublicKeyCredentialRequestOptions& aOptions)
|
||||
const PublicKeyCredentialRequestOptions& aOptions,
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aParent);
|
||||
|
@ -504,6 +520,12 @@ WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Abort the request if aborted flag is already set.
|
||||
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
nsString origin;
|
||||
nsCString rpId;
|
||||
rv = GetOrigin(aParent, origin, rpId);
|
||||
|
@ -624,11 +646,18 @@ WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
|
|||
|
||||
ListenForVisibilityEvents(aParent, this);
|
||||
|
||||
AbortSignal* signal = nullptr;
|
||||
if (aSignal.WasPassed()) {
|
||||
signal = &aSignal.Value();
|
||||
Follow(signal);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mTransaction.isNothing());
|
||||
mTransaction = Some(WebAuthnTransaction(aParent,
|
||||
promise,
|
||||
Move(info),
|
||||
Move(clientDataJSON)));
|
||||
Move(clientDataJSON),
|
||||
signal));
|
||||
|
||||
mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return promise.forget();
|
||||
|
@ -910,6 +939,12 @@ WebAuthnManager::HandleEvent(nsIDOMEvent* aEvent)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::Abort()
|
||||
{
|
||||
CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::ActorDestroyed()
|
||||
{
|
||||
|
|
|
@ -66,11 +66,13 @@ public:
|
|||
WebAuthnTransaction(nsPIDOMWindowInner* aParent,
|
||||
const RefPtr<Promise>& aPromise,
|
||||
const WebAuthnTransactionInfo&& aInfo,
|
||||
const nsAutoCString&& aClientData)
|
||||
const nsAutoCString&& aClientData,
|
||||
AbortSignal* aSignal)
|
||||
: mParent(aParent)
|
||||
, mPromise(aPromise)
|
||||
, mInfo(aInfo)
|
||||
, mClientData(aClientData)
|
||||
, mSignal(aSignal)
|
||||
, mId(NextId())
|
||||
{
|
||||
MOZ_ASSERT(mId > 0);
|
||||
|
@ -89,6 +91,9 @@ public:
|
|||
// Client data used to assemble reply objects.
|
||||
nsCString mClientData;
|
||||
|
||||
// An optional AbortSignal instance.
|
||||
RefPtr<AbortSignal> mSignal;
|
||||
|
||||
// Unique transaction id.
|
||||
uint64_t mId;
|
||||
|
||||
|
@ -103,6 +108,7 @@ private:
|
|||
};
|
||||
|
||||
class WebAuthnManager final : public nsIDOMEventListener
|
||||
, public AbortFollower
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
@ -113,11 +119,13 @@ public:
|
|||
|
||||
already_AddRefed<Promise>
|
||||
MakeCredential(nsPIDOMWindowInner* aParent,
|
||||
const MakePublicKeyCredentialOptions& aOptions);
|
||||
const MakePublicKeyCredentialOptions& aOptions,
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
GetAssertion(nsPIDOMWindowInner* aParent,
|
||||
const PublicKeyCredentialRequestOptions& aOptions);
|
||||
const PublicKeyCredentialRequestOptions& aOptions,
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Store(nsPIDOMWindowInner* aParent, const Credential& aCredential);
|
||||
|
@ -134,6 +142,8 @@ public:
|
|||
void
|
||||
RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
|
||||
void Abort() override;
|
||||
|
||||
void ActorDestroyed();
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
tab_webauthn_result.html
|
||||
tab_webauthn_success.html
|
||||
../cbor/*
|
||||
../pkijs/*
|
||||
../u2futil.js
|
||||
skip-if = !e10s
|
||||
|
||||
[browser_abort_visibility.js]
|
||||
[browser_webauthn_telemetry.js]
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_result.html";
|
||||
|
||||
async function assertStatus(tab, expected) {
|
||||
let actual = await ContentTask.spawn(tab.linkedBrowser, null, async function () {
|
||||
return content.document.getElementById("status").value;
|
||||
});
|
||||
is(actual, expected, "webauthn request " + expected);
|
||||
}
|
||||
|
||||
async function waitForStatus(tab, expected) {
|
||||
await ContentTask.spawn(tab.linkedBrowser, [expected], async function (expected) {
|
||||
return ContentTaskUtils.waitForCondition(() => {
|
||||
return content.document.getElementById("status").value == expected;
|
||||
});
|
||||
});
|
||||
|
||||
await assertStatus(tab, expected);
|
||||
}
|
||||
|
||||
function startMakeCredentialRequest(tab) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, null, async function () {
|
||||
const cose_alg_ECDSA_w_SHA256 = -7;
|
||||
|
||||
let publicKey = {
|
||||
rp: {id: content.document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: content.crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
};
|
||||
|
||||
let status = content.document.getElementById("status");
|
||||
|
||||
content.navigator.credentials.create({publicKey}).then(() => {
|
||||
status.value = "completed";
|
||||
}).catch(() => {
|
||||
status.value = "aborted";
|
||||
});
|
||||
|
||||
status.value = "pending";
|
||||
});
|
||||
}
|
||||
|
||||
function startGetAssertionRequest(tab) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, null, async function () {
|
||||
let newCredential = {
|
||||
type: "public-key",
|
||||
id: content.crypto.getRandomValues(new Uint8Array(16)),
|
||||
transports: ["usb"],
|
||||
};
|
||||
|
||||
let publicKey = {
|
||||
challenge: content.crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
rpId: content.document.domain,
|
||||
allowCredentials: [newCredential]
|
||||
};
|
||||
|
||||
let status = content.document.getElementById("status");
|
||||
|
||||
content.navigator.credentials.get({publicKey}).then(() => {
|
||||
status.value = "completed";
|
||||
}).catch(() => {
|
||||
status.value = "aborted";
|
||||
});
|
||||
|
||||
status.value = "pending";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Test that MakeCredential() and GetAssertion() requests
|
||||
// are aborted when the current tab loses its focus.
|
||||
add_task(async function test_abort() {
|
||||
// Enable the USB token.
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", false);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", true);
|
||||
|
||||
// Create a new tab for the MakeCredential() request.
|
||||
let tab_create = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Start the request.
|
||||
await startMakeCredentialRequest(tab_create);
|
||||
await assertStatus(tab_create, "pending");
|
||||
|
||||
// Open another tab and switch to it. The first will lose focus.
|
||||
let tab_get = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
await waitForStatus(tab_create, "aborted");
|
||||
|
||||
// Start a GetAssertion() request in the second tab.
|
||||
await startGetAssertionRequest(tab_get);
|
||||
await assertStatus(tab_get, "pending");
|
||||
|
||||
// Switch back to the first tab, the get() request is aborted.
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab_create);
|
||||
await waitForStatus(tab_get, "aborted");
|
||||
|
||||
// Close tabs.
|
||||
await BrowserTestUtils.removeTab(tab_create);
|
||||
await BrowserTestUtils.removeTab(tab_get);
|
||||
|
||||
// Cleanup.
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn");
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Generic W3C Web Authentication Test Result Page</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Generic W3C Web Authentication Test Result Page</h1>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1415675">Mozilla Bug 1415675</a>
|
||||
<input type="text" id="status" value="init" />
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -6,10 +6,12 @@ support-files =
|
|||
skip-if = !e10s
|
||||
scheme = https
|
||||
|
||||
[test_webauthn_abort_signal.html]
|
||||
[test_webauthn_loopback.html]
|
||||
[test_webauthn_no_token.html]
|
||||
[test_webauthn_make_credential.html]
|
||||
[test_webauthn_get_assertion.html]
|
||||
[test_webauthn_override_request.html]
|
||||
[test_webauthn_store_credential.html]
|
||||
[test_webauthn_sameorigin.html]
|
||||
[test_webauthn_isplatformauthenticatoravailable.html]
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for aborting W3C Web Authentication request</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Test for aborting W3C Web Authentication request</h1>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1415675">Mozilla Bug 1415675</a>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
function arrivingHereIsBad(aResult) {
|
||||
ok(false, "Bad result! Received a: " + aResult);
|
||||
}
|
||||
|
||||
function expectAbortError(aResult) {
|
||||
is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
|
||||
}
|
||||
|
||||
add_task(() => {
|
||||
// Enable USB tokens.
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true],
|
||||
]});
|
||||
});
|
||||
|
||||
// Start a new MakeCredential() request.
|
||||
function requestMakeCredential(signal) {
|
||||
let publicKey = {
|
||||
rp: {id: document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
};
|
||||
|
||||
return navigator.credentials.create({publicKey, signal});
|
||||
}
|
||||
|
||||
// Start a new GetAssertion() request.
|
||||
async function requestGetAssertion(signal) {
|
||||
let newCredential = {
|
||||
type: "public-key",
|
||||
id: crypto.getRandomValues(new Uint8Array(16)),
|
||||
transports: ["usb"],
|
||||
};
|
||||
|
||||
let publicKey = {
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
rpId: document.domain,
|
||||
allowCredentials: [newCredential]
|
||||
};
|
||||
|
||||
// Start the request, handle failures only.
|
||||
return navigator.credentials.get({publicKey, signal});
|
||||
}
|
||||
|
||||
// Create an AbortController and abort immediately.
|
||||
add_task(async () => {
|
||||
let ctrl = new AbortController();
|
||||
ctrl.abort();
|
||||
|
||||
// The event shouldn't fire.
|
||||
ctrl.signal.onabort = arrivingHereIsBad;
|
||||
|
||||
// MakeCredential() should abort immediately.
|
||||
await requestMakeCredential(ctrl.signal)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectAbortError);
|
||||
|
||||
// GetAssertion() should abort immediately.
|
||||
await requestGetAssertion(ctrl.signal)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectAbortError);
|
||||
});
|
||||
|
||||
// Request a new credential and abort the request.
|
||||
add_task(async () => {
|
||||
let aborted = false;
|
||||
let ctrl = new AbortController();
|
||||
|
||||
ctrl.signal.onabort = () => {
|
||||
ok(!aborted, "abort event fired once");
|
||||
aborted = true;
|
||||
};
|
||||
|
||||
// Request a new credential.
|
||||
let request = requestMakeCredential(ctrl.signal)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(err => {
|
||||
ok(aborted, "abort event was fired");
|
||||
expectAbortError(err);
|
||||
});
|
||||
|
||||
// Wait a tick for the statemachine to start.
|
||||
await Promise.resolve();
|
||||
|
||||
// Abort the request.
|
||||
ok(!aborted, "request still pending");
|
||||
ctrl.abort();
|
||||
ok(aborted, "request aborted");
|
||||
|
||||
// Wait for the request to terminate.
|
||||
await request;
|
||||
});
|
||||
|
||||
// Request a new assertion and abort the request.
|
||||
add_task(async () => {
|
||||
let aborted = false;
|
||||
let ctrl = new AbortController();
|
||||
|
||||
ctrl.signal.onabort = () => {
|
||||
ok(!aborted, "abort event fired once");
|
||||
aborted = true;
|
||||
};
|
||||
|
||||
// Request a new assertion.
|
||||
let request = requestGetAssertion(ctrl.signal)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(err => {
|
||||
ok(aborted, "abort event was fired");
|
||||
expectAbortError(err);
|
||||
});
|
||||
|
||||
// Wait a tick for the statemachine to start.
|
||||
await Promise.resolve();
|
||||
|
||||
// Abort the request.
|
||||
ok(!aborted, "request still pending");
|
||||
ctrl.abort();
|
||||
ok(aborted, "request aborted");
|
||||
|
||||
// Wait for the request to terminate.
|
||||
await request;
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for overriding W3C Web Authentication request</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Test for overriding W3C Web Authentication request</h1>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1415675">Mozilla Bug 1415675</a>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Last request status.
|
||||
let status = "";
|
||||
|
||||
add_task(() => {
|
||||
// Enable USB tokens.
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true],
|
||||
]});
|
||||
});
|
||||
|
||||
// Start a new MakeCredential() request.
|
||||
async function requestMakeCredential(status_value) {
|
||||
let publicKey = {
|
||||
rp: {id: document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
};
|
||||
|
||||
// Start the request, handle failures only.
|
||||
navigator.credentials.create({publicKey}).catch(() => {
|
||||
status = status_value;
|
||||
});
|
||||
|
||||
// Wait a tick to let the statemachine start.
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
// Start a new GetAssertion() request.
|
||||
async function requestGetAssertion(status_value) {
|
||||
let newCredential = {
|
||||
type: "public-key",
|
||||
id: crypto.getRandomValues(new Uint8Array(16)),
|
||||
transports: ["usb"],
|
||||
};
|
||||
|
||||
let publicKey = {
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
rpId: document.domain,
|
||||
allowCredentials: [newCredential]
|
||||
};
|
||||
|
||||
// Start the request, handle failures only.
|
||||
navigator.credentials.get({publicKey}).catch(() => {
|
||||
status = status_value;
|
||||
});
|
||||
|
||||
// Wait a tick to let the statemachine start.
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
// Test that .create() and .get() requests override any pending requests.
|
||||
add_task(async () => {
|
||||
// Request a new credential.
|
||||
await requestMakeCredential("aborted1");
|
||||
|
||||
// Request another credential, the first request will abort.
|
||||
await requestMakeCredential("aborted2");
|
||||
is(status, "aborted1", "first request aborted");
|
||||
|
||||
// Request an assertion, the second request will abort.
|
||||
await requestGetAssertion("aborted3");
|
||||
is(status, "aborted2", "second request aborted");
|
||||
|
||||
// Request another assertion, the third request will abort.
|
||||
await requestGetAssertion("aborted4");
|
||||
is(status, "aborted3", "third request aborted");
|
||||
|
||||
// Request another credential, the fourth request will abort.
|
||||
await requestMakeCredential("aborted5");
|
||||
is(status, "aborted4", "fourth request aborted");
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -22,8 +22,10 @@ interface CredentialsContainer {
|
|||
|
||||
dictionary CredentialRequestOptions {
|
||||
PublicKeyCredentialRequestOptions publicKey;
|
||||
AbortSignal signal;
|
||||
};
|
||||
|
||||
dictionary CredentialCreationOptions {
|
||||
MakePublicKeyCredentialOptions publicKey;
|
||||
AbortSignal signal;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче