2021-07-23 21:30:04 +02:00
|
|
|
import { b64Decode, getQsParam } from './common';
|
|
|
|
import { buildDataString, parseWebauthnJson } from './common-webauthn';
|
2021-03-16 17:44:31 +01:00
|
|
|
|
|
|
|
// tslint:disable-next-line
|
|
|
|
require('./webauthn.scss');
|
|
|
|
|
2021-07-23 21:30:04 +02:00
|
|
|
let parsed = false;
|
|
|
|
let webauthnJson: any;
|
2021-03-16 17:44:31 +01:00
|
|
|
let parentUrl: string = null;
|
|
|
|
let parentOrigin: string = null;
|
|
|
|
let sentSuccess = false;
|
2021-07-23 21:30:04 +02:00
|
|
|
let locale: string = 'en';
|
2021-03-16 17:44:31 +01:00
|
|
|
|
|
|
|
let locales: any = {};
|
|
|
|
|
2021-07-23 21:30:04 +02:00
|
|
|
function parseParameters() {
|
|
|
|
if (parsed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
parentUrl = getQsParam('parent');
|
|
|
|
if (!parentUrl) {
|
|
|
|
error('No parent.');
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
parentUrl = decodeURIComponent(parentUrl);
|
|
|
|
parentOrigin = new URL(parentUrl).origin;
|
|
|
|
}
|
|
|
|
|
|
|
|
locale = getQsParam('locale').replace('-', '_');
|
|
|
|
|
|
|
|
const version = getQsParam('v');
|
|
|
|
|
|
|
|
if (version === '1') {
|
|
|
|
parseParametersV1();
|
|
|
|
} else {
|
|
|
|
parseParametersV2();
|
|
|
|
}
|
|
|
|
parsed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseParametersV1() {
|
|
|
|
const data = getQsParam('data');
|
|
|
|
if (!data) {
|
|
|
|
error('No data.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
webauthnJson = b64Decode(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseParametersV2() {
|
|
|
|
let dataObj: { data: any, btnText: string; } = null;
|
|
|
|
try {
|
|
|
|
dataObj = JSON.parse(b64Decode(getQsParam('data')));
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
error('Cannot parse data.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
webauthnJson = dataObj.data;
|
|
|
|
}
|
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
|
2021-07-23 21:30:04 +02:00
|
|
|
parseParameters();
|
2021-05-21 13:07:33 +02:00
|
|
|
try {
|
|
|
|
locales = await loadLocales(locale);
|
|
|
|
} catch {
|
2021-05-26 01:43:54 +02:00
|
|
|
// tslint:disable-next-line:no-console
|
2021-05-21 13:07:33 +02:00
|
|
|
console.error('Failed to load the locale', locale);
|
2021-05-26 01:43:54 +02:00
|
|
|
locales = await loadLocales('en');
|
2021-05-21 13:07:33 +02:00
|
|
|
}
|
2021-03-16 17:44:31 +01:00
|
|
|
|
|
|
|
document.getElementById('msg').innerText = translate('webAuthnFallbackMsg');
|
|
|
|
document.getElementById('remember-label').innerText = translate('rememberMe');
|
2021-05-12 17:19:20 +02:00
|
|
|
|
|
|
|
const button = document.getElementById('webauthn-button');
|
|
|
|
button.innerText = translate('webAuthnAuthenticate');
|
|
|
|
button.onclick = start;
|
2021-03-16 17:44:31 +01:00
|
|
|
|
|
|
|
document.getElementById('spinner').classList.add('d-none');
|
|
|
|
const content = document.getElementById('content');
|
|
|
|
content.classList.add('d-block');
|
|
|
|
content.classList.remove('d-none');
|
|
|
|
});
|
|
|
|
|
2021-07-23 21:30:04 +02:00
|
|
|
async function loadLocales(newLocale: string) {
|
|
|
|
const filePath = `locales/${newLocale}/messages.json?cache=${process.env.CACHE_TAG}`;
|
2021-05-21 13:07:33 +02:00
|
|
|
const localesResult = await fetch(filePath);
|
|
|
|
return await localesResult.json();
|
|
|
|
}
|
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
function translate(id: string) {
|
|
|
|
return locales[id]?.message || '';
|
|
|
|
}
|
|
|
|
|
|
|
|
function start() {
|
|
|
|
if (sentSuccess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!('credentials' in navigator)) {
|
|
|
|
error(translate('webAuthnNotSupported'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-23 21:30:04 +02:00
|
|
|
parseParameters();
|
|
|
|
if (!webauthnJson) {
|
2021-03-16 17:44:31 +01:00
|
|
|
error('No data.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let json: any;
|
|
|
|
try {
|
2021-07-23 21:30:04 +02:00
|
|
|
json = parseWebauthnJson(webauthnJson);
|
2021-03-16 17:44:31 +01:00
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
error('Cannot parse data.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
initWebAuthn(json);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function initWebAuthn(obj: any) {
|
|
|
|
try {
|
|
|
|
const assertedCredential = await navigator.credentials.get({ publicKey: obj }) as PublicKeyCredential;
|
|
|
|
|
|
|
|
if (sentSuccess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dataString = buildDataString(assertedCredential);
|
|
|
|
const remember = (document.getElementById('remember') as HTMLInputElement).checked;
|
|
|
|
window.postMessage({ command: 'webAuthnResult', data: dataString, remember: remember }, '*');
|
|
|
|
|
|
|
|
sentSuccess = true;
|
|
|
|
success(translate('webAuthnSuccess'));
|
|
|
|
} catch (err) {
|
|
|
|
error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function error(message: string) {
|
|
|
|
const el = document.getElementById('msg');
|
2021-04-05 22:37:02 +02:00
|
|
|
resetMsgBox(el);
|
2021-06-09 21:58:07 +02:00
|
|
|
el.textContent = message;
|
2021-03-16 17:44:31 +01:00
|
|
|
el.classList.add('alert');
|
|
|
|
el.classList.add('alert-danger');
|
|
|
|
}
|
|
|
|
|
|
|
|
function success(message: string) {
|
|
|
|
(document.getElementById('webauthn-button') as HTMLButtonElement).disabled = true;
|
|
|
|
|
|
|
|
const el = document.getElementById('msg');
|
2021-04-05 22:37:02 +02:00
|
|
|
resetMsgBox(el);
|
2021-06-09 21:58:07 +02:00
|
|
|
el.textContent = message;
|
2021-03-16 17:44:31 +01:00
|
|
|
el.classList.add('alert');
|
|
|
|
el.classList.add('alert-success');
|
|
|
|
}
|
2021-04-05 22:37:02 +02:00
|
|
|
|
|
|
|
function resetMsgBox(el: HTMLElement) {
|
|
|
|
el.classList.remove('alert');
|
|
|
|
el.classList.remove('alert-danger');
|
|
|
|
el.classList.remove('alert-success');
|
|
|
|
}
|