107 lines
3.5 KiB
JavaScript
107 lines
3.5 KiB
JavaScript
|
/*
|
||
|
* Copyright 2002-2024 the original author or authors.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
import "./bootstrap.js";
|
||
|
import { expect } from "chai";
|
||
|
import { setupLogin } from "../lib/webauthn-login.js";
|
||
|
import webauthn from "../lib/webauthn-core.js";
|
||
|
import { assert, fake, match, stub } from "sinon";
|
||
|
|
||
|
describe("webauthn-login", () => {
|
||
|
describe("bootstrap", () => {
|
||
|
let authenticateStub;
|
||
|
let isConditionalMediationAvailableStub;
|
||
|
let signinButton;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
isConditionalMediationAvailableStub = stub(webauthn, "isConditionalMediationAvailable").resolves(false);
|
||
|
authenticateStub = stub(webauthn, "authenticate").resolves("/success");
|
||
|
signinButton = {
|
||
|
addEventListener: fake(),
|
||
|
};
|
||
|
|
||
|
global.console = {
|
||
|
error: stub(),
|
||
|
};
|
||
|
global.window = {
|
||
|
location: {
|
||
|
href: {},
|
||
|
},
|
||
|
};
|
||
|
});
|
||
|
|
||
|
afterEach(() => {
|
||
|
authenticateStub.restore();
|
||
|
isConditionalMediationAvailableStub.restore();
|
||
|
});
|
||
|
|
||
|
it("sets up a click event listener on the signin button", async () => {
|
||
|
await setupLogin({}, "/some/path", signinButton);
|
||
|
|
||
|
assert.calledOnceWithMatch(signinButton.addEventListener, "click", match.typeOf("function"));
|
||
|
});
|
||
|
|
||
|
// FIXME: conditional mediation triggers browser crashes
|
||
|
// See: https://github.com/rwinch/spring-security-webauthn/issues/73
|
||
|
xit("uses conditional mediation when available", async () => {
|
||
|
isConditionalMediationAvailableStub.resolves(true);
|
||
|
|
||
|
const headers = { "x-header": "value" };
|
||
|
const contextPath = "/some/path";
|
||
|
|
||
|
await setupLogin(headers, contextPath, signinButton);
|
||
|
|
||
|
assert.calledOnceWithExactly(authenticateStub, headers, contextPath, true);
|
||
|
expect(global.window.location.href).to.equal("/success");
|
||
|
});
|
||
|
|
||
|
it("does not call authenticate when conditional mediation is not available", async () => {
|
||
|
await setupLogin({}, "/", signinButton);
|
||
|
|
||
|
assert.notCalled(authenticateStub);
|
||
|
});
|
||
|
|
||
|
it("calls authenticate when the signin button is clicked", async () => {
|
||
|
const headers = { "x-header": "value" };
|
||
|
const contextPath = "/some/path";
|
||
|
|
||
|
await setupLogin(headers, contextPath, signinButton);
|
||
|
|
||
|
// Call the event listener
|
||
|
await signinButton.addEventListener.firstCall.lastArg();
|
||
|
|
||
|
assert.calledOnceWithExactly(authenticateStub, headers, contextPath, false);
|
||
|
expect(global.window.location.href).to.equal("/success");
|
||
|
});
|
||
|
|
||
|
it("handles authentication errors", async () => {
|
||
|
authenticateStub.rejects(new Error("Authentication failed"));
|
||
|
await setupLogin({}, "/some/path", signinButton);
|
||
|
|
||
|
// Call the event listener
|
||
|
await signinButton.addEventListener.firstCall.lastArg();
|
||
|
|
||
|
expect(global.window.location.href).to.equal(`/some/path/login?error`);
|
||
|
assert.calledOnceWithMatch(
|
||
|
global.console.error,
|
||
|
match.instanceOf(Error).and(match.has("message", "Authentication failed")),
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|