mirror of
https://github.com/lukin/keywind.git
synced 2025-01-10 09:56:25 +00:00
implement webauthn and recovery codes from keycloak base
This commit is contained in:
parent
79b24d2b86
commit
0b1682031c
6 changed files with 558 additions and 1 deletions
176
theme/keywind/login/login-recovery-authn-code-config.ftl
Normal file
176
theme/keywind/login/login-recovery-authn-code-config.ftl
Normal file
|
@ -0,0 +1,176 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "components/button/primary.ftl" as buttonPrimary>
|
||||
<#import "components/button/secondary.ftl" as buttonSecondary>
|
||||
<#import "components/checkbox/primary.ftl" as checkboxPrimary>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
|
||||
<#if section = "header">
|
||||
${kcSanitize(msg("recovery-code-config-header"))}
|
||||
<#elseif section = "form">
|
||||
|
||||
<!-- warning -->
|
||||
<div class="bg-orange-100 text-orange-600 p-4 rounded-lg text-sm" role="alert">
|
||||
<p class="font-bold my-2">${kcSanitize(msg("recovery-code-config-warning-title"))}</p>
|
||||
<span>${kcSanitize(msg("recovery-code-config-warning-message"))}</span>
|
||||
</div>
|
||||
|
||||
<ol class="font-mono kc-recovery-codes-list" id="kc-recovery-codes-list">
|
||||
<#list recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList as code>
|
||||
<li><span class="font-bold">${code?counter}:</span> ${code[0..3]}-${code[4..7]}-${code[8..]}</li>
|
||||
</#list>
|
||||
</ol>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="flex items-stretch space-x-4 mb-4">
|
||||
<button id="printRecoveryCodes" type="button" class="bg-secondary-100 flex justify-center px-2 py-1 relative rounded text-xs text-secondary-600 w-full focus:outline-none focus:ring-2 focus:ring-secondary-600 focus:ring-offset-2 hover:bg-secondary-200 hover:text-secondary-900">
|
||||
${kcSanitize(msg("recovery-codes-print"))}
|
||||
</button>
|
||||
<button id="downloadRecoveryCodes" type="button" class="bg-secondary-100 flex justify-center px-2 py-1 relative rounded text-xs text-secondary-600 w-full focus:outline-none focus:ring-2 focus:ring-secondary-600 focus:ring-offset-2 hover:bg-secondary-200 hover:text-secondary-900">
|
||||
${kcSanitize(msg("recovery-codes-download"))}
|
||||
</button>
|
||||
<button id="copyRecoveryCodes" type="button" class="bg-secondary-100 flex justify-center px-2 py-1 relative rounded text-xs text-secondary-600 w-full focus:outline-none focus:ring-2 focus:ring-secondary-600 focus:ring-offset-2 hover:bg-secondary-200 hover:text-secondary-900">
|
||||
${kcSanitize(msg("recovery-codes-copy"))}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form action="${url.loginAction}" class="m-0 space-y-4m" method="post">
|
||||
<input type="hidden" name="generatedRecoveryAuthnCodes" value="${recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}" />
|
||||
<input type="hidden" name="generatedAt" value="${recoveryAuthnCodesConfigBean.generatedAt?c}" />
|
||||
<input type="hidden" id="userLabel" name="userLabel" value="${msg("recovery-codes-label-default")}" />
|
||||
|
||||
<!-- confirmation checkbox -->
|
||||
<@checkboxPrimary.kw id="kcRecoveryCodesConfirmationCheck" name="kcRecoveryCodesConfirmationCheck" required="required">
|
||||
${kcSanitize(msg("recovery-codes-confirmation-message"))}
|
||||
</@checkboxPrimary.kw>
|
||||
|
||||
<div class="flex flex-col space-y-2 mt-4">
|
||||
<#if isAppInitiatedAction??>
|
||||
<@buttonPrimary.kw type="submit" id="saveRecoveryAuthnCodesBtn">
|
||||
${kcSanitize(msg("recovery-codes-action-complete"))}
|
||||
</@buttonPrimary.kw>
|
||||
|
||||
<@buttonSecondary.kw type="submit" id="cancelRecoveryAuthnCodesBtn" name="cancel-aia" value="true" onclick="document.getElementById('kcRecoveryCodesConfirmationCheck').required=false;return true;">
|
||||
${kcSanitize(msg("recovery-codes-action-cancel"))}
|
||||
</@buttonSecondary.kw>
|
||||
<#else>
|
||||
<@buttonPrimary.kw type="submit" id="saveRecoveryAuthnCodesBtn">
|
||||
${kcSanitize(msg("recovery-codes-action-complete"))}
|
||||
</@buttonPrimary.kw>
|
||||
</#if>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
/* copy recovery codes */
|
||||
function copyRecoveryCodes() {
|
||||
var tmpTextarea = document.createElement("textarea");
|
||||
var codes = document.getElementById("kc-recovery-codes-list").getElementsByTagName("li");
|
||||
for (i = 0; i < codes.length; i++) {
|
||||
tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\n";
|
||||
}
|
||||
document.body.appendChild(tmpTextarea);
|
||||
tmpTextarea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tmpTextarea);
|
||||
}
|
||||
|
||||
var copyButton = document.getElementById("copyRecoveryCodes");
|
||||
copyButton && copyButton.addEventListener("click", function () {
|
||||
copyRecoveryCodes();
|
||||
});
|
||||
|
||||
/* download recovery codes */
|
||||
function formatCurrentDateTime() {
|
||||
var dt = new Date();
|
||||
var options = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
|
||||
return dt.toLocaleString('en-US', options);
|
||||
}
|
||||
|
||||
function parseRecoveryCodeList() {
|
||||
var recoveryCodes = document.querySelectorAll(".kc-recovery-codes-list li");
|
||||
var recoveryCodeList = "";
|
||||
|
||||
for (var i = 0; i < recoveryCodes.length; i++) {
|
||||
var recoveryCodeLiElement = recoveryCodes[i].innerText;
|
||||
recoveryCodeList += recoveryCodeLiElement + "\r\n";
|
||||
}
|
||||
|
||||
return recoveryCodeList;
|
||||
}
|
||||
|
||||
function buildDownloadContent() {
|
||||
var recoveryCodeList = parseRecoveryCodeList();
|
||||
var dt = new Date();
|
||||
var options = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
|
||||
return fileBodyContent =
|
||||
"${msg("recovery-codes-download-file-header")}\n\n" +
|
||||
recoveryCodeList + "\n" +
|
||||
"${msg("recovery-codes-download-file-description")}\n\n" +
|
||||
"${msg("recovery-codes-download-file-date")} " + formatCurrentDateTime();
|
||||
}
|
||||
|
||||
function setUpDownloadLinkAndDownload(filename, text) {
|
||||
var el = document.createElement('a');
|
||||
el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
el.setAttribute('download', filename);
|
||||
el.style.display = 'none';
|
||||
document.body.appendChild(el);
|
||||
el.click();
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
|
||||
function downloadRecoveryCodes() {
|
||||
setUpDownloadLinkAndDownload('kc-download-recovery-codes.txt', buildDownloadContent());
|
||||
}
|
||||
|
||||
var downloadButton = document.getElementById("downloadRecoveryCodes");
|
||||
downloadButton && downloadButton.addEventListener("click", downloadRecoveryCodes);
|
||||
|
||||
/* print recovery codes */
|
||||
function buildPrintContent() {
|
||||
var recoveryCodeListHTML = document.getElementById('kc-recovery-codes-list').innerHTML;
|
||||
var styles =
|
||||
`@page { size: auto; margin-top: 0; }
|
||||
body { width: 480px; }
|
||||
div { list-style-type: none; font-family: monospace }
|
||||
p:first-of-type { margin-top: 48px }`
|
||||
|
||||
return printFileContent =
|
||||
"<html><style>" + styles + "</style><body>" +
|
||||
"<title>kc-download-recovery-codes</title>" +
|
||||
"<p>${msg("recovery-codes-download-file-header")}</p>" +
|
||||
"<div>" + recoveryCodeListHTML + "</div>" +
|
||||
"<p>${msg("recovery-codes-download-file-description")}</p>" +
|
||||
"<p>${msg("recovery-codes-download-file-date")} " + formatCurrentDateTime() + "</p>" +
|
||||
"</body></html>";
|
||||
}
|
||||
|
||||
function printRecoveryCodes() {
|
||||
var w = window.open();
|
||||
w.document.write(buildPrintContent());
|
||||
w.print();
|
||||
//w.close();
|
||||
}
|
||||
|
||||
var printButton = document.getElementById("printRecoveryCodes");
|
||||
printButton && printButton.addEventListener("click", printRecoveryCodes);
|
||||
</script>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
26
theme/keywind/login/login-recovery-authn-code-input.ftl
Normal file
26
theme/keywind/login/login-recovery-authn-code-input.ftl
Normal file
|
@ -0,0 +1,26 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "components/button/primary.ftl" as buttonPrimary>
|
||||
<#import "components/input/primary.ftl" as inputPrimary>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section="header">
|
||||
${kcSanitize(msg("auth-recovery-code-header"))}
|
||||
<#elseif section = "form">
|
||||
<form class="m-0 space-y-4" action="${url.loginAction}" method="post">
|
||||
<@inputPrimary.kw
|
||||
autocomplete="off"
|
||||
autofocus=true
|
||||
invalid=["firstName"]
|
||||
name="recoveryCodeInput"
|
||||
type="text"
|
||||
value=(register.formData.firstName)!''
|
||||
>
|
||||
${msg("auth-recovery-code-prompt", recoveryAuthnCodesInputBean.codeNumber?c)}
|
||||
</@inputPrimary.kw>
|
||||
|
||||
<@buttonPrimary.kw type="submit" name="login">
|
||||
${kcSanitize(msg("doLogIn"))}
|
||||
</@buttonPrimary.kw>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
2
theme/keywind/login/resources/dist/index.css
vendored
2
theme/keywind/login/resources/dist/index.css
vendored
File diff suppressed because one or more lines are too long
130
theme/keywind/login/webauthn-authenticate.ftl
Normal file
130
theme/keywind/login/webauthn-authenticate.ftl
Normal file
|
@ -0,0 +1,130 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "components/button/primary.ftl" as buttonPrimary>
|
||||
<#import "components/button/secondary.ftl" as buttonSecondary>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section="header">
|
||||
${msg("webauthn-login-title")}
|
||||
|
||||
<#elseif section = "form">
|
||||
<div class="m-0">
|
||||
<form id="webauth" action="${url.loginAction}" method="post">
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||
<input type="hidden" id="authenticatorData" name="authenticatorData"/>
|
||||
<input type="hidden" id="signature" name="signature"/>
|
||||
<input type="hidden" id="credentialId" name="credentialId"/>
|
||||
<input type="hidden" id="userHandle" name="userHandle"/>
|
||||
<input type="hidden" id="error" name="error"/>
|
||||
</form>
|
||||
|
||||
<#if authenticators??>
|
||||
<form id="authn_select" class="m-0">
|
||||
<#list authenticators.authenticators as authenticator>
|
||||
<input type="hidden" name="authn_use_chk" value="${authenticator.credentialId}"/>
|
||||
</#list>
|
||||
</form>
|
||||
|
||||
<#if shouldDisplayAuthenticators?? && shouldDisplayAuthenticators>
|
||||
<#if authenticators.authenticators?size gt 1>
|
||||
<p class="font-bold py-2 text-xl">${kcSanitize(msg("webauthn-available-authenticators"))?no_esc}</p>
|
||||
</#if>
|
||||
|
||||
<#list authenticators.authenticators as authenticator>
|
||||
<div class="my-5">
|
||||
<p><span class="font-bold">${kcSanitize(msg('${authenticator.label}'))?no_esc}</span>
|
||||
<#--<#if authenticator.transports?? && authenticator.transports.displayNameProperties?has_content>
|
||||
(<#list authenticator.transports.displayNameProperties as nameProperty>
|
||||
${kcSanitize(msg('${nameProperty!}'))?no_esc}
|
||||
<#if nameProperty?has_next>, </#if>
|
||||
</#list>)
|
||||
</#if>-->
|
||||
</p>
|
||||
|
||||
<p>${kcSanitize(msg('webauthn-createdAt-label'))?no_esc}: <span class="font-bold">${kcSanitize(authenticator.createdAt)?no_esc}</span></p>
|
||||
</div>
|
||||
</#list>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
<@buttonPrimary.kw type="button" onclick="webAuthnAuthenticate()" autofocus="autofocus">
|
||||
${msg("webauthn-doAuthenticate")}
|
||||
</@buttonPrimary.kw>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${url.resourcesPath}/js/base64url.js"></script>
|
||||
<script type="text/javascript">
|
||||
function webAuthnAuthenticate() {
|
||||
let isUserIdentified = ${isUserIdentified};
|
||||
if (!isUserIdentified) {
|
||||
doAuthenticate([]);
|
||||
return;
|
||||
}
|
||||
checkAllowCredentials();
|
||||
}
|
||||
function checkAllowCredentials() {
|
||||
let allowCredentials = [];
|
||||
let authn_use = document.forms['authn_select'].authn_use_chk;
|
||||
if (authn_use !== undefined) {
|
||||
if (authn_use.length === undefined) {
|
||||
allowCredentials.push({
|
||||
id: base64url.decode(authn_use.value, {loose: true}),
|
||||
type: 'public-key',
|
||||
});
|
||||
} else {
|
||||
for (let i = 0; i < authn_use.length; i++) {
|
||||
allowCredentials.push({
|
||||
id: base64url.decode(authn_use[i].value, {loose: true}),
|
||||
type: 'public-key',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
doAuthenticate(allowCredentials);
|
||||
}
|
||||
function doAuthenticate(allowCredentials) {
|
||||
// Check if WebAuthn is supported by this browser
|
||||
if (!window.PublicKeyCredential) {
|
||||
jQuery("#error").val("${msg("webauthn-unsupported-browser-text")?no_esc}");
|
||||
jQuery("#webauth").submit();
|
||||
return;
|
||||
}
|
||||
let challenge = "${challenge}";
|
||||
let userVerification = "${userVerification}";
|
||||
let rpId = "${rpId}";
|
||||
let publicKey = {
|
||||
rpId : rpId,
|
||||
challenge: base64url.decode(challenge, { loose: true })
|
||||
};
|
||||
let createTimeout = ${createTimeout};
|
||||
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
|
||||
if (allowCredentials.length) {
|
||||
publicKey.allowCredentials = allowCredentials;
|
||||
}
|
||||
if (userVerification !== 'not specified') publicKey.userVerification = userVerification;
|
||||
navigator.credentials.get({publicKey})
|
||||
.then((result) => {
|
||||
window.result = result;
|
||||
let clientDataJSON = result.response.clientDataJSON;
|
||||
let authenticatorData = result.response.authenticatorData;
|
||||
let signature = result.response.signature;
|
||||
jQuery("#clientDataJSON").val(base64url.encode(new Uint8Array(clientDataJSON), { pad: false }));
|
||||
jQuery("#authenticatorData").val(base64url.encode(new Uint8Array(authenticatorData), { pad: false }));
|
||||
jQuery("#signature").val(base64url.encode(new Uint8Array(signature), { pad: false }));
|
||||
jQuery("#credentialId").val(result.id);
|
||||
if(result.response.userHandle) {
|
||||
jQuery("#userHandle").val(base64url.encode(new Uint8Array(result.response.userHandle), { pad: false }));
|
||||
}
|
||||
jQuery("#webauth").submit();
|
||||
})
|
||||
.catch((err) => {
|
||||
jQuery("#error").val(err);
|
||||
jQuery("#webauth").submit();
|
||||
})
|
||||
;
|
||||
}
|
||||
</script>
|
||||
<#elseif section = "info">
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
36
theme/keywind/login/webauthn-error.ftl
Normal file
36
theme/keywind/login/webauthn-error.ftl
Normal file
|
@ -0,0 +1,36 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "components/button/primary.ftl" as buttonPrimary>
|
||||
<#import "components/button/secondary.ftl" as buttonSecondary>
|
||||
|
||||
<@layout.registrationLayout displayMessage=true; section>
|
||||
<#if section = "header">
|
||||
${kcSanitize(msg("webauthn-error-title"))}
|
||||
<#elseif section = "form">
|
||||
|
||||
<script type="text/javascript">
|
||||
refreshPage = () => {
|
||||
document.getElementById('isSetRetry').value = 'retry';
|
||||
document.getElementById('executionValue').value = '${execution}';
|
||||
document.getElementById('kc-error-credential-form').submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form action="${url.loginAction}" method="post" class="m-0">
|
||||
<input type="hidden" id="executionValue" name="authenticationExecution"/>
|
||||
<input type="hidden" id="isSetRetry" name="isSetRetry"/>
|
||||
</form>
|
||||
|
||||
<@buttonPrimary.kw type="button" tabindex="4" onclick="refreshPage()">
|
||||
${kcSanitize(msg("doTryAgain"))}
|
||||
</@buttonPrimary.kw>
|
||||
|
||||
<#if isAppInitiatedAction??>
|
||||
<form action="${url.loginAction}" method="post" class="m-0">
|
||||
<@buttonSecondary.kw name="cancel-aia" type="submit" value="true">
|
||||
${kcSanitize(msg("doCancel"))}
|
||||
</@buttonSecondary.kw>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
189
theme/keywind/login/webauthn-register.ftl
Normal file
189
theme/keywind/login/webauthn-register.ftl
Normal file
|
@ -0,0 +1,189 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "components/button/primary.ftl" as buttonPrimary>
|
||||
<#import "components/button/secondary.ftl" as buttonSecondary>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section="header">
|
||||
${kcSanitize(msg("webauthn-registration-title"))}
|
||||
<#elseif section = "form">
|
||||
|
||||
<form id="register" action="${url.loginAction}" class="m-0 space-y-4" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||
<input type="hidden" id="attestationObject" name="attestationObject"/>
|
||||
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId"/>
|
||||
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel"/>
|
||||
<input type="hidden" id="transports" name="transports"/>
|
||||
<input type="hidden" id="error" name="error"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript" src="${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${url.resourcesPath}/js/base64url.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
function registerSecurityKey() {
|
||||
|
||||
// Check if WebAuthn is supported by this browser
|
||||
if (!window.PublicKeyCredential) {
|
||||
jQuery("#error").val("${msg("webauthn-unsupported-browser-text")?no_esc}");
|
||||
jQuery("#register").submit();
|
||||
return;
|
||||
}
|
||||
|
||||
// mandatory parameters
|
||||
let challenge = "${challenge}";
|
||||
let userid = "${userid}";
|
||||
let username = "${username}";
|
||||
|
||||
let signatureAlgorithms = "${signatureAlgorithms}";
|
||||
let pubKeyCredParams = getPubKeyCredParams(signatureAlgorithms);
|
||||
|
||||
let rpEntityName = "${rpEntityName}";
|
||||
let rp = {name: rpEntityName};
|
||||
|
||||
let publicKey = {
|
||||
challenge: base64url.decode(challenge, {loose: true}),
|
||||
rp: rp,
|
||||
user: {
|
||||
id: base64url.decode(userid, {loose: true}),
|
||||
name: username,
|
||||
displayName: username
|
||||
},
|
||||
pubKeyCredParams: pubKeyCredParams,
|
||||
};
|
||||
|
||||
// optional parameters
|
||||
let rpId = "${rpId}";
|
||||
publicKey.rp.id = rpId;
|
||||
|
||||
let attestationConveyancePreference = "${attestationConveyancePreference}";
|
||||
if (attestationConveyancePreference !== 'not specified') publicKey.attestation = attestationConveyancePreference;
|
||||
|
||||
let authenticatorSelection = {};
|
||||
let isAuthenticatorSelectionSpecified = false;
|
||||
|
||||
let authenticatorAttachment = "${authenticatorAttachment}";
|
||||
if (authenticatorAttachment !== 'not specified') {
|
||||
authenticatorSelection.authenticatorAttachment = authenticatorAttachment;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
let requireResidentKey = "${requireResidentKey}";
|
||||
if (requireResidentKey !== 'not specified') {
|
||||
if (requireResidentKey === 'Yes')
|
||||
authenticatorSelection.requireResidentKey = true;
|
||||
else
|
||||
authenticatorSelection.requireResidentKey = false;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
let userVerificationRequirement = "${userVerificationRequirement}";
|
||||
if (userVerificationRequirement !== 'not specified') {
|
||||
authenticatorSelection.userVerification = userVerificationRequirement;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
if (isAuthenticatorSelectionSpecified) publicKey.authenticatorSelection = authenticatorSelection;
|
||||
|
||||
let createTimeout = ${createTimeout};
|
||||
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
|
||||
|
||||
let excludeCredentialIds = "${excludeCredentialIds}";
|
||||
let excludeCredentials = getExcludeCredentials(excludeCredentialIds);
|
||||
if (excludeCredentials.length > 0) publicKey.excludeCredentials = excludeCredentials;
|
||||
|
||||
navigator.credentials.create({publicKey})
|
||||
.then(function (result) {
|
||||
window.result = result;
|
||||
let clientDataJSON = result.response.clientDataJSON;
|
||||
let attestationObject = result.response.attestationObject;
|
||||
let publicKeyCredentialId = result.rawId;
|
||||
|
||||
jQuery("#clientDataJSON").val(base64url.encode(new Uint8Array(clientDataJSON), {pad: false}));
|
||||
jQuery("#attestationObject").val(base64url.encode(new Uint8Array(attestationObject), {pad: false}));
|
||||
jQuery("#publicKeyCredentialId").val(base64url.encode(new Uint8Array(publicKeyCredentialId), {pad: false}));
|
||||
|
||||
if (typeof result.response.getTransports === "function") {
|
||||
let transports = result.response.getTransports();
|
||||
if (transports) {
|
||||
jQuery("#transports").val(getTransportsAsString(transports));
|
||||
}
|
||||
} else {
|
||||
console.log("Your browser is not able to recognize supported transport media for the authenticator.");
|
||||
}
|
||||
|
||||
let initLabel = "WebAuthn Authenticator (Default Label)";
|
||||
let labelResult = window.prompt("Please input your registered authenticator's label", initLabel);
|
||||
if (labelResult === null) labelResult = initLabel;
|
||||
jQuery("#authenticatorLabel").val(labelResult);
|
||||
|
||||
jQuery("#register").submit();
|
||||
|
||||
})
|
||||
.catch(function (err) {
|
||||
jQuery("#error").val(err);
|
||||
jQuery("#register").submit();
|
||||
});
|
||||
}
|
||||
|
||||
function getPubKeyCredParams(signatureAlgorithms) {
|
||||
let pubKeyCredParams = [];
|
||||
if (signatureAlgorithms === "") {
|
||||
pubKeyCredParams.push({type: "public-key", alg: -7});
|
||||
return pubKeyCredParams;
|
||||
}
|
||||
let signatureAlgorithmsList = signatureAlgorithms.split(',');
|
||||
|
||||
for (let i = 0; i < signatureAlgorithmsList.length; i++) {
|
||||
pubKeyCredParams.push({
|
||||
type: "public-key",
|
||||
alg: signatureAlgorithmsList[i]
|
||||
});
|
||||
}
|
||||
return pubKeyCredParams;
|
||||
}
|
||||
|
||||
function getExcludeCredentials(excludeCredentialIds) {
|
||||
let excludeCredentials = [];
|
||||
if (excludeCredentialIds === "") return excludeCredentials;
|
||||
|
||||
let excludeCredentialIdsList = excludeCredentialIds.split(',');
|
||||
|
||||
for (let i = 0; i < excludeCredentialIdsList.length; i++) {
|
||||
excludeCredentials.push({
|
||||
type: "public-key",
|
||||
id: base64url.decode(excludeCredentialIdsList[i],
|
||||
{loose: true})
|
||||
});
|
||||
}
|
||||
return excludeCredentials;
|
||||
}
|
||||
|
||||
function getTransportsAsString(transportsList) {
|
||||
if (transportsList === '' || transportsList.constructor !== Array) return "";
|
||||
|
||||
let transportsString = "";
|
||||
|
||||
for (let i = 0; i < transportsList.length; i++) {
|
||||
transportsString += transportsList[i] + ",";
|
||||
}
|
||||
|
||||
return transportsString.slice(0, -1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<@buttonPrimary.kw type="submit" onclick="registerSecurityKey()">
|
||||
${kcSanitize(msg("doRegister"))}
|
||||
</@buttonPrimary.kw>
|
||||
|
||||
<#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
|
||||
<form action="${url.loginAction}" method="post" class="m-0">
|
||||
<@buttonSecondary.kw name="cancel-aia" type="submit" value="true">
|
||||
${kcSanitize(msg("doCancel"))}
|
||||
</@buttonSecondary.kw>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
Loading…
Reference in a new issue