mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-07 03:43:48 -05:00
the next url comes from sign in via a query param, and needs to go to the POST /webauthn/authenticate endpoint. That endpoint logs the user in and returns the redirect to the browser, and will take the next from the request query params to get there. also moving the window mocks to beforeEach/afterEach ensures that promise callbacks from previous tests aren't still associated in future tests to ensure good test isolation. unfortunately i couldn't get mocking location for a single js test to work, but by changing the global config i was able to add some query params that i can expect to be passed through. Don't love this at all but not quite sure of a good way round this. I think we're not practicing very good hygiene and best practices with our mocking and it's really confounding me here.
78 lines
2.9 KiB
JavaScript
78 lines
2.9 KiB
JavaScript
(function (window) {
|
|
"use strict";
|
|
|
|
window.GOVUK.Modules.AuthenticateSecurityKey = function () {
|
|
this.start = function (component) {
|
|
$(component)
|
|
.on('click', function (event) {
|
|
event.preventDefault();
|
|
|
|
fetch('/webauthn/authenticate')
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw Error(response.statusText);
|
|
}
|
|
|
|
return response.arrayBuffer();
|
|
})
|
|
.then(data => {
|
|
var options = window.CBOR.decode(data);
|
|
// triggers browser dialogue to login with authenticator
|
|
return window.navigator.credentials.get(options);
|
|
})
|
|
.then(credential => {
|
|
const currentURL = new URL(window.location.href);
|
|
|
|
// create authenticateURL from admin hostname plus /webauthn/authenticate path
|
|
const authenticateURL = new URL('/webauthn/authenticate', window.location.href);
|
|
|
|
const nextUrl = currentURL.searchParams.get('next');
|
|
if (nextUrl) {
|
|
// takes nextUrl from the query string on the current browser URL
|
|
// (which should be /two-factor-webauthn) and pass it through to
|
|
// the POST. put it in a query string so it's consistent with how
|
|
// the other login flows manage it
|
|
authenticateURL.searchParams.set('next', nextUrl);
|
|
}
|
|
|
|
return fetch(authenticateURL, {
|
|
method: 'POST',
|
|
headers: { 'X-CSRFToken': component.data('csrfToken') },
|
|
body: window.CBOR.encode({
|
|
credentialId: new Uint8Array(credential.rawId),
|
|
authenticatorData: new Uint8Array(credential.response.authenticatorData),
|
|
signature: new Uint8Array(credential.response.signature),
|
|
clientDataJSON: new Uint8Array(credential.response.clientDataJSON),
|
|
})
|
|
});
|
|
})
|
|
.then(response => {
|
|
if (response.status === 403) {
|
|
// flask will have `flash`ed an error message up
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
|
|
return response.arrayBuffer()
|
|
.then(cbor => {
|
|
return Promise.resolve(window.CBOR.decode(cbor));
|
|
})
|
|
.catch(() => {
|
|
throw Error(response.statusText);
|
|
})
|
|
.then(data => {
|
|
window.location.assign(data.redirect_url);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error(error);
|
|
// some browsers will show an error dialogue for some
|
|
// errors; to be safe we always pop up an alert
|
|
var message = error.message || error;
|
|
alert('Error during authentication.\n\n' + message);
|
|
});
|
|
});
|
|
};
|
|
};
|
|
}) (window);
|