6745 |
20 May 22 |
nicklas |
'use strict'; |
6743 |
19 May 22 |
nicklas |
2 |
|
6743 |
19 May 22 |
nicklas |
var WebAuthn = function() |
6743 |
19 May 22 |
nicklas |
4 |
{ |
6743 |
19 May 22 |
nicklas |
var wa = {}; |
6745 |
20 May 22 |
nicklas |
var debug = 1; |
6747 |
23 May 22 |
nicklas |
var hasCreatedHiddenFormElements = false; |
6743 |
19 May 22 |
nicklas |
8 |
|
6743 |
19 May 22 |
nicklas |
wa.initPage = function() |
6743 |
19 May 22 |
nicklas |
10 |
{ |
6743 |
19 May 22 |
nicklas |
// Disable WebAuthn fields on extended properties tab |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnSerial').disabled = true; |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnUserHandle').disabled = true; |
6786 |
04 Aug 22 |
nicklas |
Doc.element('ep.webAuthnPasswordLess.false').disabled = true; |
6786 |
04 Aug 22 |
nicklas |
Doc.element('ep.webAuthnPasswordLess.true').disabled = true; |
6743 |
19 May 22 |
nicklas |
16 |
|
6743 |
19 May 22 |
nicklas |
Events.addEventHandler('webAuthnSerial', 'blur', wa.copySerial); |
6743 |
19 May 22 |
nicklas |
18 |
|
6743 |
19 May 22 |
nicklas |
Buttons.addClickHandler('btnWebAuthnRegister', wa.startWebAuthnRegistration); |
6747 |
23 May 22 |
nicklas |
Buttons.addClickHandler('btnWebAuthnRemove', wa.removeWebAuthn); |
6743 |
19 May 22 |
nicklas |
21 |
|
6743 |
19 May 22 |
nicklas |
TabControl.addTabActivateListener('settings.webauthn-tab', wa.autoFocus); |
6743 |
19 May 22 |
nicklas |
23 |
} |
6743 |
19 May 22 |
nicklas |
24 |
|
6743 |
19 May 22 |
nicklas |
// Set focus to the serial number field |
6743 |
19 May 22 |
nicklas |
wa.autoFocus = function() |
6743 |
19 May 22 |
nicklas |
27 |
{ |
6743 |
19 May 22 |
nicklas |
Doc.element('webAuthnSerial').focus(); |
6743 |
19 May 22 |
nicklas |
29 |
} |
6743 |
19 May 22 |
nicklas |
30 |
|
6743 |
19 May 22 |
nicklas |
/* Copy serial number to extended properties tab */ |
6743 |
19 May 22 |
nicklas |
wa.copySerial = function() |
6743 |
19 May 22 |
nicklas |
33 |
{ |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnSerial').value = Doc.element('webAuthnSerial').value; |
6743 |
19 May 22 |
nicklas |
35 |
} |
6747 |
23 May 22 |
nicklas |
36 |
|
6747 |
23 May 22 |
nicklas |
37 |
/* |
6747 |
23 May 22 |
nicklas |
Create the hidden form elements that are needed for storing WebAuthn |
6747 |
23 May 22 |
nicklas |
data. The form elements are never created more than once. |
6747 |
23 May 22 |
nicklas |
40 |
*/ |
6747 |
23 May 22 |
nicklas |
wa.createHiddenFormElements = function() |
6743 |
19 May 22 |
nicklas |
42 |
{ |
6747 |
23 May 22 |
nicklas |
if (hasCreatedHiddenFormElements) return; |
6747 |
23 May 22 |
nicklas |
wa.createHidden('webAuthnUserHandle'); |
6747 |
23 May 22 |
nicklas |
wa.createHidden('webAuthnCredentialId'); |
6747 |
23 May 22 |
nicklas |
wa.createHidden('webAuthnPublicKey'); |
6747 |
23 May 22 |
nicklas |
wa.createHidden('webAuthnSignatureCount'); |
6786 |
04 Aug 22 |
nicklas |
wa.createHidden('webAuthnPasswordLess'); |
6747 |
23 May 22 |
nicklas |
hasCreatedHiddenFormElements = true; |
6743 |
19 May 22 |
nicklas |
50 |
} |
6747 |
23 May 22 |
nicklas |
51 |
|
6747 |
23 May 22 |
nicklas |
wa.createHidden = function(id) |
6743 |
19 May 22 |
nicklas |
53 |
{ |
6747 |
23 May 22 |
nicklas |
var frm = document.forms['user']; |
6747 |
23 May 22 |
nicklas |
var hidden = frm.ownerDocument.createElement('input'); |
6747 |
23 May 22 |
nicklas |
hidden.setAttribute('type', 'hidden'); |
6747 |
23 May 22 |
nicklas |
hidden.setAttribute('name', 'ep.'+id); |
6747 |
23 May 22 |
nicklas |
hidden.setAttribute('id', id); |
6747 |
23 May 22 |
nicklas |
hidden.setAttribute('value', ''); |
6747 |
23 May 22 |
nicklas |
frm.appendChild(hidden); |
6743 |
19 May 22 |
nicklas |
61 |
} |
6747 |
23 May 22 |
nicklas |
62 |
|
6743 |
19 May 22 |
nicklas |
63 |
/* |
6743 |
19 May 22 |
nicklas |
Remove all WebAuthn values for this user. |
6743 |
19 May 22 |
nicklas |
Switch to the no-WebAuthn form |
6743 |
19 May 22 |
nicklas |
66 |
*/ |
6743 |
19 May 22 |
nicklas |
wa.removeWebAuthn = function() |
6743 |
19 May 22 |
nicklas |
68 |
{ |
6747 |
23 May 22 |
nicklas |
wa.createHiddenFormElements(); |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnUserHandle').value = ''; |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnUserHandle').value = ''; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnCredentialId').value = ''; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnPublicKey').value = ''; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnSignatureCount').value = ''; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnSerial').value = ''; |
6786 |
04 Aug 22 |
nicklas |
Doc.element('webAuthnPasswordLess').value = 'false'; |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnSerial').value = ''; |
6743 |
19 May 22 |
nicklas |
78 |
|
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnUserHandleHTML').innerHTML = '<i>No WebAuthn</i>'; |
6743 |
19 May 22 |
nicklas |
Doc.hide('has-webauthn'); |
6743 |
19 May 22 |
nicklas |
Doc.show('no-webauthn'); |
6743 |
19 May 22 |
nicklas |
82 |
} |
6743 |
19 May 22 |
nicklas |
83 |
|
6745 |
20 May 22 |
nicklas |
wa.handleError = function(error) |
6745 |
20 May 22 |
nicklas |
85 |
{ |
6745 |
20 May 22 |
nicklas |
console.log(error); |
6745 |
20 May 22 |
nicklas |
Doc.element('webauthn-error').innerHTML = Strings.encodeTags(error.toString ? error.toString() : error); |
6745 |
20 May 22 |
nicklas |
Doc.show('webauthn-error'); |
6745 |
20 May 22 |
nicklas |
89 |
} |
6745 |
20 May 22 |
nicklas |
90 |
|
6747 |
23 May 22 |
nicklas |
wa.hideError = function() |
6747 |
23 May 22 |
nicklas |
92 |
{ |
6747 |
23 May 22 |
nicklas |
Doc.element('webauthn-error').innerHTML = ''; |
6747 |
23 May 22 |
nicklas |
Doc.hide('webauthn-error'); |
6747 |
23 May 22 |
nicklas |
95 |
} |
6747 |
23 May 22 |
nicklas |
96 |
|
6745 |
20 May 22 |
nicklas |
// Step 1. Start the registration by sending some |
6745 |
20 May 22 |
nicklas |
// information about the user to the server |
6743 |
19 May 22 |
nicklas |
wa.startWebAuthnRegistration = function() |
6743 |
19 May 22 |
nicklas |
100 |
{ |
6747 |
23 May 22 |
nicklas |
wa.hideError(); |
6747 |
23 May 22 |
nicklas |
102 |
|
6743 |
19 May 22 |
nicklas |
var frm = document.forms['user']; |
6744 |
20 May 22 |
nicklas |
var userId = frm.item_id.value; |
6744 |
20 May 22 |
nicklas |
var login = frm.login.value; |
6744 |
20 May 22 |
nicklas |
var name = frm.name.value; |
6745 |
20 May 22 |
nicklas |
107 |
|
6744 |
20 May 22 |
nicklas |
var home = Data.get('webauthn-data', 'home'); |
6744 |
20 May 22 |
nicklas |
var url = home + '/WebAuthn.servlet?ID='+App.getSessionId(); |
6744 |
20 May 22 |
nicklas |
url += '&cmd=StartWebAuthnRegister'; |
6744 |
20 May 22 |
nicklas |
url += '&userId='+encodeURIComponent(userId); |
6744 |
20 May 22 |
nicklas |
url += '&login='+encodeURIComponent(login); |
6744 |
20 May 22 |
nicklas |
url += '&name='+encodeURIComponent(name); |
6786 |
04 Aug 22 |
nicklas |
url += '&enablePasswordLess='+(frm.passwordLess.checked ? 1 : 0); |
6744 |
20 May 22 |
nicklas |
115 |
|
6745 |
20 May 22 |
nicklas |
try |
6745 |
20 May 22 |
nicklas |
117 |
{ |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug('AJAX request: '+url); |
6745 |
20 May 22 |
nicklas |
var request = Ajax.getXmlHttpRequest(); |
6745 |
20 May 22 |
nicklas |
Ajax.setReadyStateHandler(request, wa.webAuthnRegistrationRequestRecieved, wa.webAuthnRegistrationRequestRecieved); |
6745 |
20 May 22 |
nicklas |
request.open("GET", url, true); |
6745 |
20 May 22 |
nicklas |
request.send(null); |
6745 |
20 May 22 |
nicklas |
123 |
} |
6745 |
20 May 22 |
nicklas |
catch (e) |
6745 |
20 May 22 |
nicklas |
125 |
{ |
6745 |
20 May 22 |
nicklas |
wa.handleError(e); |
6745 |
20 May 22 |
nicklas |
127 |
} |
6744 |
20 May 22 |
nicklas |
128 |
} |
6744 |
20 May 22 |
nicklas |
129 |
|
6745 |
20 May 22 |
nicklas |
// Step 2. Pass the response from the server to the |
6745 |
20 May 22 |
nicklas |
// navigator.credentials.create() API |
6744 |
20 May 22 |
nicklas |
wa.webAuthnRegistrationRequestRecieved = function(request) |
6744 |
20 May 22 |
nicklas |
133 |
{ |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug(request.responseText); |
6743 |
19 May 22 |
nicklas |
try |
6743 |
19 May 22 |
nicklas |
136 |
{ |
6745 |
20 May 22 |
nicklas |
var response = JSON.parse(request.responseText); |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug(response); |
6745 |
20 May 22 |
nicklas |
if (response.status != 'ok') |
6745 |
20 May 22 |
nicklas |
140 |
{ |
6745 |
20 May 22 |
nicklas |
throw new Error(response.message); |
6745 |
20 May 22 |
nicklas |
142 |
} |
6743 |
19 May 22 |
nicklas |
143 |
|
6756 |
01 Jun 22 |
nicklas |
var reg = response.registrationRequest.publicKey; |
6785 |
03 Aug 22 |
nicklas |
if (reg.authenticatorSelection.residentKey == 'required') |
6785 |
03 Aug 22 |
nicklas |
146 |
{ |
6785 |
03 Aug 22 |
nicklas |
// This is an older, deprecaged, attribute but is needed in some browser |
6785 |
03 Aug 22 |
nicklas |
// Otherwise the registration will not be saved on the security key |
6785 |
03 Aug 22 |
nicklas |
reg.authenticatorSelection.requireResidentKey = true; |
6785 |
03 Aug 22 |
nicklas |
150 |
} |
6785 |
03 Aug 22 |
nicklas |
151 |
|
6745 |
20 May 22 |
nicklas |
// Re-encode the registration request to what the navigator.credentials.create() requires |
6745 |
20 May 22 |
nicklas |
// Mainly, we need to convert Base64-encoded values to Uint8Array object |
6745 |
20 May 22 |
nicklas |
var publicKey = { |
6745 |
20 May 22 |
nicklas |
'publicKey': { |
6745 |
20 May 22 |
nicklas |
'attestation': reg.attestation, |
6785 |
03 Aug 22 |
nicklas |
'authenticatorSelection': reg.authenticatorSelection, |
6745 |
20 May 22 |
nicklas |
'challenge': WAUtils.base64ToUint8Array(reg.challenge), |
6745 |
20 May 22 |
nicklas |
'pubKeyCredParams': reg.pubKeyCredParams, |
6745 |
20 May 22 |
nicklas |
'rp': reg.rp, |
6745 |
20 May 22 |
nicklas |
'user': { |
6745 |
20 May 22 |
nicklas |
'id': WAUtils.base64ToUint8Array(reg.user.id), |
6745 |
20 May 22 |
nicklas |
'name': reg.user.name, |
6745 |
20 May 22 |
nicklas |
'displayName': reg.user.displayName |
6745 |
20 May 22 |
nicklas |
165 |
} |
6745 |
20 May 22 |
nicklas |
166 |
}}; |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug(publicKey); |
6743 |
19 May 22 |
nicklas |
168 |
|
6745 |
20 May 22 |
nicklas |
// Send create() request to the security key |
6745 |
20 May 22 |
nicklas |
navigator.credentials.create(publicKey) |
6745 |
20 May 22 |
nicklas |
.then(wa.webAuthnRegistrationRequestCreated) |
6745 |
20 May 22 |
nicklas |
.catch(wa.handleError); |
6745 |
20 May 22 |
nicklas |
173 |
} |
6745 |
20 May 22 |
nicklas |
catch (e) |
6745 |
20 May 22 |
nicklas |
175 |
{ |
6745 |
20 May 22 |
nicklas |
wa.handleError(e); |
6745 |
20 May 22 |
nicklas |
177 |
} |
6745 |
20 May 22 |
nicklas |
178 |
} |
6745 |
20 May 22 |
nicklas |
179 |
|
6745 |
20 May 22 |
nicklas |
// Step 3. Handle the response from the security key and send it to the server |
6745 |
20 May 22 |
nicklas |
wa.webAuthnRegistrationRequestCreated = function(publicKey) |
6745 |
20 May 22 |
nicklas |
182 |
{ |
6745 |
20 May 22 |
nicklas |
if (debug) |
6745 |
20 May 22 |
nicklas |
184 |
{ |
6745 |
20 May 22 |
nicklas |
App.debug(publicKey); |
6745 |
20 May 22 |
nicklas |
console.log('rawId:'+WAUtils.byteArrayToBase64(publicKey.rawId)); |
6745 |
20 May 22 |
nicklas |
console.log('attestationObject:'+WAUtils.byteArrayToBase64(publicKey.response.attestationObject)); |
6745 |
20 May 22 |
nicklas |
console.log('clientDataJSON:'+WAUtils.byteArrayToBase64(publicKey.response.clientDataJSON)); |
6745 |
20 May 22 |
nicklas |
189 |
} |
6745 |
20 May 22 |
nicklas |
try |
6745 |
20 May 22 |
nicklas |
191 |
{ |
6745 |
20 May 22 |
nicklas |
// Convert and encode the response from the security key to what is expected by the server |
6745 |
20 May 22 |
nicklas |
var reg = { |
6745 |
20 May 22 |
nicklas |
'id': publicKey.id, |
6745 |
20 May 22 |
nicklas |
'response': |
6745 |
20 May 22 |
nicklas |
196 |
{ |
6745 |
20 May 22 |
nicklas |
'attestationObject': WAUtils.byteArrayToBase64(publicKey.response.attestationObject), |
6745 |
20 May 22 |
nicklas |
'clientDataJSON': WAUtils.byteArrayToBase64(publicKey.response.clientDataJSON) |
6745 |
20 May 22 |
nicklas |
199 |
}, |
6745 |
20 May 22 |
nicklas |
'clientExtensionResults': {}, |
6745 |
20 May 22 |
nicklas |
'type': 'public-key' |
6745 |
20 May 22 |
nicklas |
202 |
}; |
6745 |
20 May 22 |
nicklas |
203 |
|
6745 |
20 May 22 |
nicklas |
var home = Data.get('webauthn-data', 'home'); |
6745 |
20 May 22 |
nicklas |
var url = home + '/WebAuthn.servlet?ID='+App.getSessionId(); |
6745 |
20 May 22 |
nicklas |
url += '&cmd=FinalizeWebAuthnRegister'; |
6745 |
20 May 22 |
nicklas |
207 |
|
6745 |
20 May 22 |
nicklas |
if (debug) |
6743 |
19 May 22 |
nicklas |
209 |
{ |
6745 |
20 May 22 |
nicklas |
App.debug('AJAX request: '+url); |
6745 |
20 May 22 |
nicklas |
App.debug(reg); |
6743 |
19 May 22 |
nicklas |
212 |
} |
6745 |
20 May 22 |
nicklas |
213 |
|
6745 |
20 May 22 |
nicklas |
var request = Ajax.getXmlHttpRequest(); |
6745 |
20 May 22 |
nicklas |
Ajax.setReadyStateHandler(request, wa.webAuthnRegistrationCompleted, wa.webAuthnRegistrationCompleted); |
6745 |
20 May 22 |
nicklas |
request.open("POST", url, true); |
6745 |
20 May 22 |
nicklas |
request.send(JSON.stringify(reg)); |
6743 |
19 May 22 |
nicklas |
218 |
} |
6743 |
19 May 22 |
nicklas |
catch (e) |
6743 |
19 May 22 |
nicklas |
220 |
{ |
6745 |
20 May 22 |
nicklas |
wa.handleError(e); |
6743 |
19 May 22 |
nicklas |
222 |
} |
6745 |
20 May 22 |
nicklas |
223 |
} |
6745 |
20 May 22 |
nicklas |
224 |
|
6745 |
20 May 22 |
nicklas |
// Step 4. Check the response from the server |
6745 |
20 May 22 |
nicklas |
wa.webAuthnRegistrationCompleted = function(request) |
6745 |
20 May 22 |
nicklas |
227 |
{ |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug(request.responseText); |
6745 |
20 May 22 |
nicklas |
try |
6743 |
19 May 22 |
nicklas |
230 |
{ |
6745 |
20 May 22 |
nicklas |
var response = JSON.parse(request.responseText); |
6745 |
20 May 22 |
nicklas |
if (debug) App.debug(response); |
6745 |
20 May 22 |
nicklas |
if (response.status != 'ok') |
6745 |
20 May 22 |
nicklas |
234 |
{ |
6745 |
20 May 22 |
nicklas |
throw new Error(response.message); |
6745 |
20 May 22 |
nicklas |
236 |
} |
6745 |
20 May 22 |
nicklas |
237 |
|
6745 |
20 May 22 |
nicklas |
// Update form elements and switch to the has-webauthn form |
6747 |
23 May 22 |
nicklas |
wa.createHiddenFormElements(); |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnUserHandle').value = response.userHandle; |
6747 |
23 May 22 |
nicklas |
Doc.element('ep.webAuthnUserHandle').value = response.userHandle; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnCredentialId').value = response.credentialId; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnPublicKey').value = response.publicKey; |
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnSignatureCount').value = response.signatureCount; |
6786 |
04 Aug 22 |
nicklas |
Doc.element('webAuthnPasswordLess').value = response.passwordLess; |
6747 |
23 May 22 |
nicklas |
246 |
|
6747 |
23 May 22 |
nicklas |
Doc.element('webAuthnUserHandleHTML').innerHTML = Strings.encodeTags(response.userHandle); |
6745 |
20 May 22 |
nicklas |
Doc.show('has-webauthn'); |
6747 |
23 May 22 |
nicklas |
Doc.hide('no-webauthn'); |
6745 |
20 May 22 |
nicklas |
Doc.element('webAuthnSerial').focus(); |
6743 |
19 May 22 |
nicklas |
251 |
} |
6745 |
20 May 22 |
nicklas |
catch (e) |
6745 |
20 May 22 |
nicklas |
253 |
{ |
6745 |
20 May 22 |
nicklas |
wa.handleError(e); |
6745 |
20 May 22 |
nicklas |
255 |
} |
6743 |
19 May 22 |
nicklas |
256 |
} |
6745 |
20 May 22 |
nicklas |
257 |
|
6743 |
19 May 22 |
nicklas |
return wa; |
6743 |
19 May 22 |
nicklas |
259 |
}(); |
6743 |
19 May 22 |
nicklas |
260 |
|
6743 |
19 May 22 |
nicklas |
Doc.onLoad(WebAuthn.initPage); |
6743 |
19 May 22 |
nicklas |
262 |
|