1
0
mirror of https://github.com/bitwarden/web synced 2026-01-02 00:23:16 +00:00

Apply Prettier (#1347)

This commit is contained in:
Oscar Hinton
2021-12-17 15:57:11 +01:00
committed by GitHub
parent 2b0a9d995e
commit 56477eb39c
414 changed files with 33390 additions and 26857 deletions

View File

@@ -1,22 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="HandheldFriendly" content="true" />
<title>Bitwarden Captcha Connector</title>
</head>
</head>
<body class="layout_frontend">
<body class="layout_frontend">
<div class="row justify-content-md-center mt-5">
<div>
<img src="..//images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
<p id="captchaRequired" class="lead text-center mx-4 mb-4">Captcha Required</p>
<div id="captcha"></div>
</div>
<div>
<img src="..//images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden" />
<p id="captchaRequired" class="lead text-center mx-4 mb-4">Captcha Required</p>
<div id="captcha"></div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -1,16 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="HandheldFriendly" content="true" />
<title>Bitwarden Captcha Connector</title>
</head>
</head>
<body>
<body>
<div id="captcha"></div>
</body>
</body>
</html>

View File

@@ -1,6 +1,6 @@
body {
min-width: 0px !important;
padding: 0;
margin: 0;
background: transparent;
min-width: 0px !important;
padding: 0;
margin: 0;
background: transparent;
}

View File

@@ -1,17 +1,17 @@
import { b64Decode, getQsParam } from './common';
import { b64Decode, getQsParam } from "./common";
declare var hcaptcha: any;
if (window.location.pathname.includes('mobile')) {
// tslint:disable-next-line
require('./captcha-mobile.scss');
if (window.location.pathname.includes("mobile")) {
// tslint:disable-next-line
require("./captcha-mobile.scss");
} else {
// tslint:disable-next-line
require('./captcha.scss');
// tslint:disable-next-line
require("./captcha.scss");
}
document.addEventListener('DOMContentLoaded', () => {
init();
document.addEventListener("DOMContentLoaded", () => {
init();
});
(window as any).captchaSuccess = captchaSuccess;
@@ -23,120 +23,123 @@ let mobileResponse: boolean = null;
let sentSuccess = false;
async function init() {
await start();
onMessage();
await start();
onMessage();
}
async function start() {
sentSuccess = false;
sentSuccess = false;
const data = getQsParam('data');
if (!data) {
error('No data.');
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;
}
parentUrl = getQsParam("parent");
if (!parentUrl) {
error("No parent.");
return;
} else {
parentUrl = decodeURIComponent(parentUrl);
parentOrigin = new URL(parentUrl).origin;
}
let decodedData: any;
try {
decodedData = JSON.parse(b64Decode(data));
}
catch (e) {
error('Cannot parse data.');
return;
}
mobileResponse = decodedData.callbackUri != null || decodedData.mobile === true;
let decodedData: any;
try {
decodedData = JSON.parse(b64Decode(data));
} catch (e) {
error("Cannot parse data.");
return;
}
mobileResponse = decodedData.callbackUri != null || decodedData.mobile === true;
let src = 'https://hcaptcha.com/1/api.js?render=explicit';
let src = "https://hcaptcha.com/1/api.js?render=explicit";
// Set language code
if (decodedData.locale) {
src += `&hl=${encodeURIComponent(decodedData.locale) ?? 'en'}`;
}
// Set language code
if (decodedData.locale) {
src += `&hl=${encodeURIComponent(decodedData.locale) ?? "en"}`;
}
// Set captchaRequired subtitle for mobile
const subtitleEl = document.getElementById('captchaRequired');
if (decodedData.captchaRequiredText && subtitleEl) {
subtitleEl.textContent = decodedData.captchaRequiredText;
}
// Set captchaRequired subtitle for mobile
const subtitleEl = document.getElementById("captchaRequired");
if (decodedData.captchaRequiredText && subtitleEl) {
subtitleEl.textContent = decodedData.captchaRequiredText;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
script.defer = true;
script.addEventListener('load', e => {
hcaptcha.render('captcha', {
sitekey: encodeURIComponent(decodedData.siteKey),
callback: 'captchaSuccess',
'error-callback': 'captchaError',
});
watchHeight();
const script = document.createElement("script");
script.src = src;
script.async = true;
script.defer = true;
script.addEventListener("load", (e) => {
hcaptcha.render("captcha", {
sitekey: encodeURIComponent(decodedData.siteKey),
callback: "captchaSuccess",
"error-callback": "captchaError",
});
document.head.appendChild(script);
watchHeight();
});
document.head.appendChild(script);
}
function captchaSuccess(response: string) {
if (mobileResponse) {
document.location.replace('bitwarden://captcha-callback?token=' + encodeURIComponent(response));
} else {
success(response);
}
if (mobileResponse) {
document.location.replace("bitwarden://captcha-callback?token=" + encodeURIComponent(response));
} else {
success(response);
}
}
function captchaError() {
error('An error occurred with the captcha. Try again.');
error("An error occurred with the captcha. Try again.");
}
function onMessage() {
window.addEventListener('message', event => {
if (!event.origin || event.origin === '' || event.origin !== parentOrigin) {
return;
}
window.addEventListener(
"message",
(event) => {
if (!event.origin || event.origin === "" || event.origin !== parentOrigin) {
return;
}
if (event.data === 'start') {
start();
}
}, false);
if (event.data === "start") {
start();
}
},
false
);
}
function error(message: string) {
parent.postMessage('error|' + message, parentUrl);
parent.postMessage("error|" + message, parentUrl);
}
function success(data: string) {
if (sentSuccess) {
return;
}
parent.postMessage('success|' + data, parentUrl);
sentSuccess = true;
if (sentSuccess) {
return;
}
parent.postMessage("success|" + data, parentUrl);
sentSuccess = true;
}
function info(message: string | object) {
parent.postMessage('info|' + JSON.stringify(message), parentUrl);
parent.postMessage("info|" + JSON.stringify(message), parentUrl);
}
async function watchHeight() {
const imagesDiv = document.body.lastChild as HTMLElement;
while (true) {
info({
height: imagesDiv.style.visibility === 'hidden' ?
document.documentElement.offsetHeight :
document.documentElement.scrollHeight,
width: document.documentElement.scrollWidth,
});
await sleep(100);
}
const imagesDiv = document.body.lastChild as HTMLElement;
while (true) {
info({
height:
imagesDiv.style.visibility === "hidden"
? document.documentElement.offsetHeight
: document.documentElement.scrollHeight,
width: document.documentElement.scrollWidth,
});
await sleep(100);
}
}
async function sleep(ms: number) {
await new Promise(r => setTimeout(r, ms));
await new Promise((r) => setTimeout(r, ms));
}

View File

@@ -1,70 +1,70 @@
export function buildDataString(assertedCredential: PublicKeyCredential) {
const response = assertedCredential.response as AuthenticatorAssertionResponse;
const response = assertedCredential.response as AuthenticatorAssertionResponse;
const authData = new Uint8Array(response.authenticatorData);
const clientDataJSON = new Uint8Array(response.clientDataJSON);
const rawId = new Uint8Array(assertedCredential.rawId);
const sig = new Uint8Array(response.signature);
const authData = new Uint8Array(response.authenticatorData);
const clientDataJSON = new Uint8Array(response.clientDataJSON);
const rawId = new Uint8Array(assertedCredential.rawId);
const sig = new Uint8Array(response.signature);
const data = {
id: assertedCredential.id,
rawId: coerceToBase64Url(rawId),
type: assertedCredential.type,
extensions: assertedCredential.getClientExtensionResults(),
response: {
authenticatorData: coerceToBase64Url(authData),
clientDataJson: coerceToBase64Url(clientDataJSON),
signature: coerceToBase64Url(sig),
},
};
const data = {
id: assertedCredential.id,
rawId: coerceToBase64Url(rawId),
type: assertedCredential.type,
extensions: assertedCredential.getClientExtensionResults(),
response: {
authenticatorData: coerceToBase64Url(authData),
clientDataJson: coerceToBase64Url(clientDataJSON),
signature: coerceToBase64Url(sig),
},
};
return JSON.stringify(data);
return JSON.stringify(data);
}
export function parseWebauthnJson(jsonString: string) {
const json = JSON.parse(jsonString);
const json = JSON.parse(jsonString);
const challenge = json.challenge.replace(/-/g, '+').replace(/_/g, '/');
json.challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
const challenge = json.challenge.replace(/-/g, "+").replace(/_/g, "/");
json.challenge = Uint8Array.from(atob(challenge), (c) => c.charCodeAt(0));
json.allowCredentials.forEach((listItem: any) => {
const fixedId = listItem.id.replace(/\_/g, '/').replace(/\-/g, '+');
listItem.id = Uint8Array.from(atob(fixedId), c => c.charCodeAt(0));
});
json.allowCredentials.forEach((listItem: any) => {
const fixedId = listItem.id.replace(/\_/g, "/").replace(/\-/g, "+");
listItem.id = Uint8Array.from(atob(fixedId), (c) => c.charCodeAt(0));
});
return json;
return json;
}
// From https://github.com/abergs/fido2-net-lib/blob/b487a1d47373ea18cd752b4988f7262035b7b54e/Demo/wwwroot/js/helpers.js#L34
// License: https://github.com/abergs/fido2-net-lib/blob/master/LICENSE.txt
function coerceToBase64Url(thing: any) {
// Array or ArrayBuffer to Uint8Array
if (Array.isArray(thing)) {
thing = Uint8Array.from(thing);
// Array or ArrayBuffer to Uint8Array
if (Array.isArray(thing)) {
thing = Uint8Array.from(thing);
}
if (thing instanceof ArrayBuffer) {
thing = new Uint8Array(thing);
}
// Uint8Array to base64
if (thing instanceof Uint8Array) {
let str = "";
const len = thing.byteLength;
for (let i = 0; i < len; i++) {
str += String.fromCharCode(thing[i]);
}
thing = window.btoa(str);
}
if (thing instanceof ArrayBuffer) {
thing = new Uint8Array(thing);
}
if (typeof thing !== "string") {
throw new Error("could not coerce to string");
}
// Uint8Array to base64
if (thing instanceof Uint8Array) {
let str = '';
const len = thing.byteLength;
// base64 to base64url
// NOTE: "=" at the end of challenge is optional, strip it off here
thing = thing.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");
for (let i = 0; i < len; i++) {
str += String.fromCharCode(thing[i]);
}
thing = window.btoa(str);
}
if (typeof thing !== 'string') {
throw new Error('could not coerce to string');
}
// base64 to base64url
// NOTE: "=" at the end of challenge is optional, strip it off here
thing = thing.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/g, '');
return thing;
return thing;
}

View File

@@ -1,21 +1,25 @@
export function getQsParam(name: string) {
const url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(url);
const url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
const results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return '';
}
if (!results) {
return null;
}
if (!results[2]) {
return "";
}
return decodeURIComponent(results[2].replace(/\+/g, ' '));
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
export function b64Decode(str: string) {
return decodeURIComponent(Array.prototype.map.call(atob(str), (c: string) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return decodeURIComponent(
Array.prototype.map
.call(atob(str), (c: string) => {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
}

View File

@@ -1,12 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"
/>
<title>Bitwarden Duo Connector</title>
</head>
<body></body>
</head>
<body></body>
</html>

View File

@@ -1,17 +1,18 @@
html, body {
margin: 0;
padding: 0;
html,
body {
margin: 0;
padding: 0;
}
body {
background: #efeff4 url('../images/loading.svg') 0 0 no-repeat;
background: #efeff4 url("../images/loading.svg") 0 0 no-repeat;
}
iframe {
display: block;
width: 100%;
height: 400px;
border: none;
margin: 0;
padding: 0;
display: block;
width: 100%;
height: 400px;
border: none;
margin: 0;
padding: 0;
}

View File

@@ -1,44 +1,47 @@
import * as DuoWebSDK from 'duo_web_sdk';
import { getQsParam } from './common';
import * as DuoWebSDK from "duo_web_sdk";
import { getQsParam } from "./common";
// tslint:disable-next-line
require('./duo.scss');
require("./duo.scss");
document.addEventListener('DOMContentLoaded', event => {
const frameElement = document.createElement('iframe');
frameElement.setAttribute('id', 'duo_iframe');
setFrameHeight();
document.body.appendChild(frameElement);
document.addEventListener("DOMContentLoaded", (event) => {
const frameElement = document.createElement("iframe");
frameElement.setAttribute("id", "duo_iframe");
setFrameHeight();
document.body.appendChild(frameElement);
const hostParam = getQsParam('host');
const requestParam = getQsParam('request');
const hostParam = getQsParam("host");
const requestParam = getQsParam("request");
const hostUrl = new URL('https://' + hostParam);
if (!hostUrl.hostname.endsWith('.duosecurity.com') && !hostUrl.hostname.endsWith('.duofederal.com')) {
return;
}
const hostUrl = new URL("https://" + hostParam);
if (
!hostUrl.hostname.endsWith(".duosecurity.com") &&
!hostUrl.hostname.endsWith(".duofederal.com")
) {
return;
}
DuoWebSDK.init({
iframe: 'duo_iframe',
host: hostParam,
sig_request: requestParam,
submit_callback: (form: any) => {
invokeCSCode(form.elements.sig_response.value);
},
});
DuoWebSDK.init({
iframe: "duo_iframe",
host: hostParam,
sig_request: requestParam,
submit_callback: (form: any) => {
invokeCSCode(form.elements.sig_response.value);
},
});
window.onresize = setFrameHeight;
window.onresize = setFrameHeight;
function setFrameHeight() {
frameElement.style.height = window.innerHeight + 'px';
}
function setFrameHeight() {
frameElement.style.height = window.innerHeight + "px";
}
});
function invokeCSCode(data: string) {
try {
(window as any).invokeCSharpAction(data);
} catch (err) {
// tslint:disable-next-line
console.log(err);
}
try {
(window as any).invokeCSharpAction(data);
} catch (err) {
// tslint:disable-next-line
console.log(err);
}
}

View File

@@ -1,31 +1,33 @@
<!DOCTYPE html>
<html class="theme_light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1010">
<meta name="theme-color" content="#175DDC">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1010" />
<meta name="theme-color" content="#175DDC" />
<title>Bitwarden</title>
<link rel="apple-touch-icon" sizes="180x180" href="../images/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="../images/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="../images/icons/favicon-16x16.png">
<link rel="mask-icon" href="../images/icons/safari-pinned-tab.svg" color="#175DDC">
<link rel="manifest" href="../manifest.json">
</head>
<link rel="apple-touch-icon" sizes="180x180" href="../images/icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="../images/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="../images/icons/favicon-16x16.png" />
<link rel="mask-icon" href="../images/icons/safari-pinned-tab.svg" color="#175DDC" />
<link rel="manifest" href="../manifest.json" />
</head>
<body class="layout_frontend">
<body class="layout_frontend">
<div class="mt-5 d-flex justify-content-center">
<div>
<img src="../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
<div id="content">
<p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="Loading" aria-hidden="true"></i>
</p>
</div>
<div>
<img src="../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
<div id="content">
<p class="text-center">
<i
class="fa fa-spinner fa-spin fa-2x text-muted"
title="Loading"
aria-hidden="true"
></i>
</p>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -1,44 +1,48 @@
import { getQsParam } from './common';
import { getQsParam } from "./common";
// tslint:disable-next-line
require('./sso.scss');
require("./sso.scss");
document.addEventListener('DOMContentLoaded', event => {
const code = getQsParam('code');
const state = getQsParam('state');
document.addEventListener("DOMContentLoaded", (event) => {
const code = getQsParam("code");
const state = getQsParam("state");
if (state != null && state.includes(':clientId=browser')) {
initiateBrowserSso(code, state);
if (state != null && state.includes(":clientId=browser")) {
initiateBrowserSso(code, state);
} else {
window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state;
// Match any characters between "_returnUri='" and the next "'"
const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')");
if (returnUri) {
window.location.href = window.location.origin + `/#${returnUri}`;
} else {
window.location.href = window.location.origin + '/#/sso?code=' + code + '&state=' + state;
// Match any characters between "_returnUri='" and the next "'"
const returnUri = extractFromRegex(state, '(?<=_returnUri=\')(.*)(?=\')');
if (returnUri) {
window.location.href = window.location.origin + `/#${returnUri}`;
} else {
window.location.href = window.location.origin + '/#/sso?code=' + code + '&state=' + state;
}
window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state;
}
}
});
function initiateBrowserSso(code: string, state: string) {
window.postMessage({ command: 'authResult', code: code, state: state }, '*');
const handOffMessage = ('; ' + document.cookie).split('; ssoHandOffMessage=').pop().split(';').shift();
document.cookie = 'ssoHandOffMessage=;SameSite=strict;max-age=0';
const content = document.getElementById('content');
content.innerHTML = '';
const p = document.createElement('p');
p.innerText = handOffMessage;
content.appendChild(p);
window.postMessage({ command: "authResult", code: code, state: state }, "*");
const handOffMessage = ("; " + document.cookie)
.split("; ssoHandOffMessage=")
.pop()
.split(";")
.shift();
document.cookie = "ssoHandOffMessage=;SameSite=strict;max-age=0";
const content = document.getElementById("content");
content.innerHTML = "";
const p = document.createElement("p");
p.innerText = handOffMessage;
content.appendChild(p);
}
function extractFromRegex(s: string, regexString: string) {
const regex = new RegExp(regexString);
const results = regex.exec(s);
const regex = new RegExp(regexString);
const results = regex.exec(s);
if (!results) {
return null;
}
if (!results) {
return null;
}
return results[0];
return results[0];
}

View File

@@ -1,11 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<title>Bitwarden U2F Connector</title>
</head>
<body></body>
</head>
<body></body>
</html>

View File

@@ -1,140 +1,150 @@
import * as u2f from 'u2f';
import * as u2f from "u2f";
document.addEventListener('DOMContentLoaded', function (event) {
init();
document.addEventListener("DOMContentLoaded", function (event) {
init();
});
var parentUrl = null,
parentOrigin = null,
version = null,
stop = false,
sentSuccess = false;
parentOrigin = null,
version = null,
stop = false,
sentSuccess = false;
function init() {
start();
onMessage();
info('ready');
start();
onMessage();
info("ready");
}
function start() {
sentSuccess = false;
sentSuccess = false;
if (!u2f.isSupported) {
error('U2F is not supported in this browser.');
return;
}
if (!u2f.isSupported) {
error("U2F is not supported in this browser.");
return;
}
var data = getQsParam('data');
if (!data) {
error('No data.');
return;
}
var data = getQsParam("data");
if (!data) {
error("No data.");
return;
}
parentUrl = getQsParam('parent');
if (!parentUrl) {
error('No parent.');
return;
}
else {
var link = document.createElement('a');
link.href = parentUrl;
parentOrigin = link.origin;
}
parentUrl = getQsParam("parent");
if (!parentUrl) {
error("No parent.");
return;
} else {
var link = document.createElement("a");
link.href = parentUrl;
parentOrigin = link.origin;
}
var versionQs = getQsParam('v');
if (!versionQs) {
error('No version.');
return;
}
var versionQs = getQsParam("v");
if (!versionQs) {
error("No version.");
return;
}
try {
version = parseInt(versionQs);
var jsonString = b64Decode(data);
var json = JSON.parse(jsonString);
}
catch (e) {
error('Cannot parse data.');
return;
}
try {
version = parseInt(versionQs);
var jsonString = b64Decode(data);
var json = JSON.parse(jsonString);
} catch (e) {
error("Cannot parse data.");
return;
}
if (!json.appId || !json.challenge || !json.keys || !json.keys.length) {
error('Invalid data parameters.');
return;
}
if (!json.appId || !json.challenge || !json.keys || !json.keys.length) {
error("Invalid data parameters.");
return;
}
stop = false
initU2f(json);
stop = false;
initU2f(json);
}
function initU2f(obj) {
if (stop) {
return;
}
if (stop) {
return;
}
u2f.sign(obj.appId, obj.challenge, obj.keys, function (data) {
if (data.errorCode) {
if (data.errorCode !== 5) {
error('U2F Error: ' + data.errorCode);
setTimeout(function () {
initU2f(obj);
}, 1000)
}
else {
initU2f(obj);
}
return;
u2f.sign(
obj.appId,
obj.challenge,
obj.keys,
function (data) {
if (data.errorCode) {
if (data.errorCode !== 5) {
error("U2F Error: " + data.errorCode);
setTimeout(function () {
initU2f(obj);
}, 1000);
} else {
initU2f(obj);
}
success(data);
}, 10);
return;
}
success(data);
},
10
);
}
function onMessage() {
window.addEventListener('message', function (event) {
if (!event.origin || event.origin === '' || event.origin !== parentOrigin) {
return;
}
window.addEventListener(
"message",
function (event) {
if (!event.origin || event.origin === "" || event.origin !== parentOrigin) {
return;
}
if (event.data === 'stop') {
stop = true;
}
else if (event.data === 'start' && stop) {
start();
}
}, false);
if (event.data === "stop") {
stop = true;
} else if (event.data === "start" && stop) {
start();
}
},
false
);
}
function error(message) {
parent.postMessage('error|' + message, parentUrl);
parent.postMessage("error|" + message, parentUrl);
}
function success(data) {
if (sentSuccess) {
return;
}
if (sentSuccess) {
return;
}
var dataString = JSON.stringify(data);
parent.postMessage('success|' + dataString, parentUrl);
sentSuccess = true;
var dataString = JSON.stringify(data);
parent.postMessage("success|" + dataString, parentUrl);
sentSuccess = true;
}
function info(message) {
parent.postMessage('info|' + message, parentUrl);
parent.postMessage("info|" + message, parentUrl);
}
function getQsParam(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return "";
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function b64Decode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return decodeURIComponent(
Array.prototype.map
.call(atob(str), function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
}

View File

@@ -1,36 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<title>Bitwarden WebAuthn Connector</title>
</head>
</head>
<body class="layout_frontend">
<body class="layout_frontend">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-5">
<img src="../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
<div id="spinner">
<p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="Loading" aria-hidden="true"></i>
</p>
</div>
<div id="content" class="card mt-4 d-none">
<div class="card-body ng-star-inserted">
<p id="msg" class="text-center"></p>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember" id="remember-label"></label>
</div>
<hr>
<p class="text-center mb-0">
<button id="webauthn-button" class="btn btn-primary btn-lg"></button>
</p>
</div>
</div>
<div class="row justify-content-center mt-5">
<div class="col-5">
<img src="../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
<div id="spinner">
<p class="text-center">
<i
class="fa fa-spinner fa-spin fa-2x text-muted"
title="Loading"
aria-hidden="true"
></i>
</p>
</div>
<div id="content" class="card mt-4 d-none">
<div class="card-body ng-star-inserted">
<p id="msg" class="text-center"></p>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember" />
<label class="form-check-label" for="remember" id="remember-label"></label>
</div>
<hr />
<p class="text-center mb-0">
<button id="webauthn-button" class="btn btn-primary btn-lg"></button>
</p>
</div>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -1,168 +1,167 @@
import { b64Decode, getQsParam } from './common';
import { buildDataString, parseWebauthnJson } from './common-webauthn';
import { b64Decode, getQsParam } from "./common";
import { buildDataString, parseWebauthnJson } from "./common-webauthn";
// tslint:disable-next-line
require('./webauthn.scss');
require("./webauthn.scss");
let parsed = false;
let webauthnJson: any;
let parentUrl: string = null;
let parentOrigin: string = null;
let sentSuccess = false;
let locale: string = 'en';
let locale: string = "en";
let locales: any = {};
function parseParameters() {
if (parsed) {
return;
}
if (parsed) {
return;
}
parentUrl = getQsParam('parent');
if (!parentUrl) {
error('No parent.');
return;
} else {
parentUrl = decodeURIComponent(parentUrl);
parentOrigin = new URL(parentUrl).origin;
}
parentUrl = getQsParam("parent");
if (!parentUrl) {
error("No parent.");
return;
} else {
parentUrl = decodeURIComponent(parentUrl);
parentOrigin = new URL(parentUrl).origin;
}
locale = getQsParam('locale').replace('-', '_');
locale = getQsParam("locale").replace("-", "_");
const version = getQsParam('v');
const version = getQsParam("v");
if (version === '1') {
parseParametersV1();
} else {
parseParametersV2();
}
parsed = true;
if (version === "1") {
parseParametersV1();
} else {
parseParametersV2();
}
parsed = true;
}
function parseParametersV1() {
const data = getQsParam('data');
if (!data) {
error('No data.');
return;
}
const data = getQsParam("data");
if (!data) {
error("No data.");
return;
}
webauthnJson = b64Decode(data);
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;
}
let dataObj: { data: any; btnText: string } = null;
try {
dataObj = JSON.parse(b64Decode(getQsParam("data")));
} catch (e) {
error("Cannot parse data.");
return;
}
webauthnJson = dataObj.data;
webauthnJson = dataObj.data;
}
document.addEventListener('DOMContentLoaded', async () => {
document.addEventListener("DOMContentLoaded", async () => {
parseParameters();
try {
locales = await loadLocales(locale);
} catch {
// tslint:disable-next-line:no-console
console.error("Failed to load the locale", locale);
locales = await loadLocales("en");
}
parseParameters();
try {
locales = await loadLocales(locale);
} catch {
// tslint:disable-next-line:no-console
console.error('Failed to load the locale', locale);
locales = await loadLocales('en');
}
document.getElementById("msg").innerText = translate("webAuthnFallbackMsg");
document.getElementById("remember-label").innerText = translate("rememberMe");
document.getElementById('msg').innerText = translate('webAuthnFallbackMsg');
document.getElementById('remember-label').innerText = translate('rememberMe');
const button = document.getElementById("webauthn-button");
button.innerText = translate("webAuthnAuthenticate");
button.onclick = start;
const button = document.getElementById('webauthn-button');
button.innerText = translate('webAuthnAuthenticate');
button.onclick = start;
document.getElementById('spinner').classList.add('d-none');
const content = document.getElementById('content');
content.classList.add('d-block');
content.classList.remove('d-none');
document.getElementById("spinner").classList.add("d-none");
const content = document.getElementById("content");
content.classList.add("d-block");
content.classList.remove("d-none");
});
async function loadLocales(newLocale: string) {
const filePath = `locales/${newLocale}/messages.json?cache=${process.env.CACHE_TAG}`;
const localesResult = await fetch(filePath);
return await localesResult.json();
const filePath = `locales/${newLocale}/messages.json?cache=${process.env.CACHE_TAG}`;
const localesResult = await fetch(filePath);
return await localesResult.json();
}
function translate(id: string) {
return locales[id]?.message || '';
return locales[id]?.message || "";
}
function start() {
if (sentSuccess) {
return;
}
if (sentSuccess) {
return;
}
if (!('credentials' in navigator)) {
error(translate('webAuthnNotSupported'));
return;
}
if (!("credentials" in navigator)) {
error(translate("webAuthnNotSupported"));
return;
}
parseParameters();
if (!webauthnJson) {
error('No data.');
return;
}
parseParameters();
if (!webauthnJson) {
error("No data.");
return;
}
let json: any;
try {
json = parseWebauthnJson(webauthnJson);
}
catch (e) {
error('Cannot parse data.');
return;
}
let json: any;
try {
json = parseWebauthnJson(webauthnJson);
} catch (e) {
error("Cannot parse data.");
return;
}
initWebAuthn(json);
initWebAuthn(json);
}
async function initWebAuthn(obj: any) {
try {
const assertedCredential = await navigator.credentials.get({ publicKey: obj }) as PublicKeyCredential;
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);
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');
resetMsgBox(el);
el.textContent = message;
el.classList.add('alert');
el.classList.add('alert-danger');
const el = document.getElementById("msg");
resetMsgBox(el);
el.textContent = message;
el.classList.add("alert");
el.classList.add("alert-danger");
}
function success(message: string) {
(document.getElementById('webauthn-button') as HTMLButtonElement).disabled = true;
(document.getElementById("webauthn-button") as HTMLButtonElement).disabled = true;
const el = document.getElementById('msg');
resetMsgBox(el);
el.textContent = message;
el.classList.add('alert');
el.classList.add('alert-success');
const el = document.getElementById("msg");
resetMsgBox(el);
el.textContent = message;
el.classList.add("alert");
el.classList.add("alert-success");
}
function resetMsgBox(el: HTMLElement) {
el.classList.remove('alert');
el.classList.remove('alert-danger');
el.classList.remove('alert-success');
el.classList.remove("alert");
el.classList.remove("alert-danger");
el.classList.remove("alert-success");
}

View File

@@ -1,25 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="HandheldFriendly" content="true" />
<title>Bitwarden WebAuthn Connector</title>
</head>
</head>
<body style="background: transparent;">
<body style="background: transparent">
<div class="row justify-content-md-center mt-5">
<div>
<img src="../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
<p id="webauthn-header" class="lead text-center mx-4 mb-4"></p>
<img src="../images/u2fkey-mobile.jpg" class="rounded img-fluid">
<div class="text-center mt-4">
<button id="webauthn-button" class="btn btn-primary btn-lg"></button>
</div>
<div>
<img src="../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden" />
<p id="webauthn-header" class="lead text-center mx-4 mb-4"></p>
<img src="../images/u2fkey-mobile.jpg" class="rounded img-fluid" />
<div class="text-center mt-4">
<button id="webauthn-button" class="btn btn-primary btn-lg"></button>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -1,16 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8" />
<title>Bitwarden WebAuthn Connector</title>
</head>
</head>
<body style="background: transparent;">
<img src="../images/u2fkey.jpg" class="rounded img-fluid mb-3">
<body style="background: transparent">
<img src="../images/u2fkey.jpg" class="rounded img-fluid mb-3" />
<div class="text-center">
<button id="webauthn-button" class="btn btn-primary"></button>
<button id="webauthn-button" class="btn btn-primary"></button>
</div>
</body>
</body>
</html>

View File

@@ -1,5 +1,5 @@
@import "../scss/styles.scss";
body {
min-width: 0px !important;
min-width: 0px !important;
}

View File

@@ -1,10 +1,10 @@
import { b64Decode, getQsParam } from './common';
import { buildDataString, parseWebauthnJson } from './common-webauthn';
import { b64Decode, getQsParam } from "./common";
import { buildDataString, parseWebauthnJson } from "./common-webauthn";
// tslint:disable-next-line
require('./webauthn.scss');
require("./webauthn.scss");
const mobileCallbackUri = 'bitwarden://webauthn-callback';
const mobileCallbackUri = "bitwarden://webauthn-callback";
let parsed = false;
let webauthnJson: any;
@@ -18,181 +18,184 @@ let stopWebAuthn = false;
let sentSuccess = false;
let obj: any = null;
document.addEventListener('DOMContentLoaded', () => {
init();
document.addEventListener("DOMContentLoaded", () => {
init();
parseParameters();
if (headerText) {
const header = document.getElementById('webauthn-header');
header.innerText = decodeURI(headerText);
}
if (btnText) {
const button = document.getElementById('webauthn-button');
button.innerText = decodeURI(btnText);
button.onclick = executeWebAuthn;
}
parseParameters();
if (headerText) {
const header = document.getElementById("webauthn-header");
header.innerText = decodeURI(headerText);
}
if (btnText) {
const button = document.getElementById("webauthn-button");
button.innerText = decodeURI(btnText);
button.onclick = executeWebAuthn;
}
});
function init() {
start();
onMessage();
info('ready');
start();
onMessage();
info("ready");
}
function parseParameters() {
if (parsed) {
return;
}
if (parsed) {
return;
}
parentUrl = getQsParam('parent');
if (!parentUrl) {
error('No parent.');
return;
} else {
parentUrl = decodeURIComponent(parentUrl);
parentOrigin = new URL(parentUrl).origin;
}
parentUrl = getQsParam("parent");
if (!parentUrl) {
error("No parent.");
return;
} else {
parentUrl = decodeURIComponent(parentUrl);
parentOrigin = new URL(parentUrl).origin;
}
const version = getQsParam('v');
const version = getQsParam("v");
if (version === '1') {
parseParametersV1();
} else {
parseParametersV2();
}
parsed = true;
if (version === "1") {
parseParametersV1();
} else {
parseParametersV2();
}
parsed = true;
}
function parseParametersV1() {
const data = getQsParam('data');
if (!data) {
error('No data.');
return;
}
const data = getQsParam("data");
if (!data) {
error("No data.");
return;
}
webauthnJson = b64Decode(data);
headerText = getQsParam('headerText');
btnText = getQsParam('btnText');
btnReturnText = getQsParam('btnReturnText');
webauthnJson = b64Decode(data);
headerText = getQsParam("headerText");
btnText = getQsParam("btnText");
btnReturnText = getQsParam("btnReturnText");
}
function parseParametersV2() {
let dataObj: {
data: any,
headerText: string;
btnText: string;
btnReturnText: string;
callbackUri?: string;
mobile?: boolean;
} = null;
try {
dataObj = JSON.parse(b64Decode(getQsParam('data')));
}
catch (e) {
error('Cannot parse data.');
return;
}
let dataObj: {
data: any;
headerText: string;
btnText: string;
btnReturnText: string;
callbackUri?: string;
mobile?: boolean;
} = null;
try {
dataObj = JSON.parse(b64Decode(getQsParam("data")));
} catch (e) {
error("Cannot parse data.");
return;
}
mobileResponse = dataObj.callbackUri != null || dataObj.mobile === true;
webauthnJson = dataObj.data;
headerText = dataObj.headerText;
btnText = dataObj.btnText;
btnReturnText = dataObj.btnReturnText;
mobileResponse = dataObj.callbackUri != null || dataObj.mobile === true;
webauthnJson = dataObj.data;
headerText = dataObj.headerText;
btnText = dataObj.btnText;
btnReturnText = dataObj.btnReturnText;
}
function start() {
sentSuccess = false;
sentSuccess = false;
if (!('credentials' in navigator)) {
error('WebAuthn is not supported in this browser.');
return;
}
if (!("credentials" in navigator)) {
error("WebAuthn is not supported in this browser.");
return;
}
parseParameters();
if (!webauthnJson) {
error('No data.');
return;
}
parseParameters();
if (!webauthnJson) {
error("No data.");
return;
}
try {
obj = parseWebauthnJson(webauthnJson);
}
catch (e) {
error('Cannot parse webauthn data.');
return;
}
try {
obj = parseWebauthnJson(webauthnJson);
} catch (e) {
error("Cannot parse webauthn data.");
return;
}
stopWebAuthn = false;
stopWebAuthn = false;
if (mobileResponse || (navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1)) {
// Safari and mobile chrome blocks non-user initiated WebAuthn requests.
} else {
executeWebAuthn();
}
if (
mobileResponse ||
(navigator.userAgent.indexOf(" Safari/") !== -1 && navigator.userAgent.indexOf("Chrome") === -1)
) {
// Safari and mobile chrome blocks non-user initiated WebAuthn requests.
} else {
executeWebAuthn();
}
}
function executeWebAuthn() {
if (stopWebAuthn) {
return;
}
if (stopWebAuthn) {
return;
}
navigator.credentials.get({ publicKey: obj })
.then(success)
.catch(error);
navigator.credentials.get({ publicKey: obj }).then(success).catch(error);
}
function onMessage() {
window.addEventListener('message', event => {
if (!event.origin || event.origin === '' || event.origin !== parentOrigin) {
return;
}
window.addEventListener(
"message",
(event) => {
if (!event.origin || event.origin === "" || event.origin !== parentOrigin) {
return;
}
if (event.data === 'stop') {
stopWebAuthn = true;
}
else if (event.data === 'start' && stopWebAuthn) {
start();
}
}, false);
if (event.data === "stop") {
stopWebAuthn = true;
} else if (event.data === "start" && stopWebAuthn) {
start();
}
},
false
);
}
function error(message: string) {
if (mobileResponse) {
document.location.replace(mobileCallbackUri + '?error=' + encodeURIComponent(message));
returnButton(mobileCallbackUri + '?error=' + encodeURIComponent(message));
} else {
parent.postMessage('error|' + message, parentUrl);
}
if (mobileResponse) {
document.location.replace(mobileCallbackUri + "?error=" + encodeURIComponent(message));
returnButton(mobileCallbackUri + "?error=" + encodeURIComponent(message));
} else {
parent.postMessage("error|" + message, parentUrl);
}
}
function success(assertedCredential: PublicKeyCredential) {
if (sentSuccess) {
return;
}
if (sentSuccess) {
return;
}
const dataString = buildDataString(assertedCredential);
const dataString = buildDataString(assertedCredential);
if (mobileResponse) {
document.location.replace(mobileCallbackUri + '?data=' + encodeURIComponent(dataString));
returnButton(mobileCallbackUri + '?data=' + encodeURIComponent(dataString));
} else {
parent.postMessage('success|' + dataString, parentUrl);
sentSuccess = true;
}
if (mobileResponse) {
document.location.replace(mobileCallbackUri + "?data=" + encodeURIComponent(dataString));
returnButton(mobileCallbackUri + "?data=" + encodeURIComponent(dataString));
} else {
parent.postMessage("success|" + dataString, parentUrl);
sentSuccess = true;
}
}
function info(message: string) {
if (mobileResponse) {
return;
}
if (mobileResponse) {
return;
}
parent.postMessage('info|' + message, parentUrl);
parent.postMessage("info|" + message, parentUrl);
}
function returnButton(uri: string) {
// provides 'return' button in case scripted navigation is blocked
const button = document.getElementById('webauthn-button');
button.innerText = decodeURI(btnReturnText);
button.onclick = () => { document.location.replace(uri); };
// provides 'return' button in case scripted navigation is blocked
const button = document.getElementById("webauthn-button");
button.innerText = decodeURI(btnReturnText);
button.onclick = () => {
document.location.replace(uri);
};
}