mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 18:23:31 +00:00
WebAuthn (#633)
This commit is contained in:
118
src/connectors/webauthn-fallback.ts
Normal file
118
src/connectors/webauthn-fallback.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { getQsParam } from './common';
|
||||
import { b64Decode, buildDataString } from './common-webauthn';
|
||||
|
||||
// tslint:disable-next-line
|
||||
require('./webauthn.scss');
|
||||
|
||||
let parentUrl: string = null;
|
||||
let parentOrigin: string = null;
|
||||
let sentSuccess = false;
|
||||
|
||||
let locales: any = {};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const locale = getQsParam('locale');
|
||||
|
||||
const filePath = `locales/${locale}/messages.json?cache=${process.env.CACHE_TAG}`;
|
||||
const localesResult = await fetch(filePath);
|
||||
locales = await localesResult.json();
|
||||
|
||||
document.getElementById('msg').innerText = translate('webAuthnFallbackMsg');
|
||||
document.getElementById('remember-label').innerText = translate('rememberMe');
|
||||
document.getElementById('webauthn-button').innerText = translate('webAuthnAuthenticate');
|
||||
|
||||
document.getElementById('spinner').classList.add('d-none');
|
||||
const content = document.getElementById('content');
|
||||
content.classList.add('d-block');
|
||||
content.classList.remove('d-none');
|
||||
});
|
||||
|
||||
function translate(id: string) {
|
||||
return locales[id]?.message || '';
|
||||
}
|
||||
|
||||
(window as any).init = () => {
|
||||
start();
|
||||
};
|
||||
|
||||
function start() {
|
||||
if (sentSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('credentials' in navigator)) {
|
||||
error(translate('webAuthnNotSupported'));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getQsParam('data');
|
||||
if (!data) {
|
||||
error('No data.');
|
||||
return;
|
||||
}
|
||||
|
||||
parentUrl = getQsParam('parent');
|
||||
if (!parentUrl) {
|
||||
error('No parent.');
|
||||
return;
|
||||
} else {
|
||||
parentUrl = decodeURIComponent(parentUrl);
|
||||
parentOrigin = new URL(parentUrl).origin;
|
||||
}
|
||||
|
||||
let json: any;
|
||||
try {
|
||||
const jsonString = b64Decode(data);
|
||||
json = JSON.parse(jsonString);
|
||||
}
|
||||
catch (e) {
|
||||
error('Cannot parse data.');
|
||||
return;
|
||||
}
|
||||
|
||||
initWebAuthn(json);
|
||||
}
|
||||
|
||||
async function initWebAuthn(obj: any) {
|
||||
const challenge = obj.challenge.replace(/-/g, '+').replace(/_/g, '/');
|
||||
obj.challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
|
||||
|
||||
// fix escaping. Change this to coerce
|
||||
obj.allowCredentials.forEach((listItem: any) => {
|
||||
const fixedId = listItem.id.replace(/\_/g, '/').replace(/\-/g, '+');
|
||||
listItem.id = Uint8Array.from(atob(fixedId), c => c.charCodeAt(0));
|
||||
});
|
||||
|
||||
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');
|
||||
el.innerHTML = message;
|
||||
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');
|
||||
el.innerHTML = message;
|
||||
el.classList.add('alert');
|
||||
el.classList.add('alert-success');
|
||||
}
|
||||
Reference in New Issue
Block a user