2
0

Merge branch 'web-enhancements' into 'master'

Web client refactor and enhancements

See merge request MasterPassword/MasterPassword!252
This commit is contained in:
Michael Ziminsky (Z) 2021-03-06 19:10:44 +00:00
commit c3dc4b6725
6 changed files with 263 additions and 9418 deletions

View File

@ -1,6 +1,12 @@
@import url(//fonts.googleapis.com/css?family=Flamenco:300|Exo+2:400,100,900); @import url(https://fonts.googleapis.com/css?family=Flamenco:300|Exo+2:400,100,900);
/**** BASE STYLE ****/ /**** BASE STYLE ****/
* {
font: inherit;
color: inherit;
text-align: center;
}
html { html {
background: radial-gradient(black 15%, transparent 16%) 0 0, background: radial-gradient(black 15%, transparent 16%) 0 0,
radial-gradient(black 15%, transparent 16%) 8px 8px, radial-gradient(black 15%, transparent 16%) 8px 8px,
@ -14,6 +20,7 @@ html {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
body { body {
background: radial-gradient(transparent 16%, black); background: radial-gradient(transparent 16%, black);
@ -25,88 +32,129 @@ body {
font-family: 'Exo 2', font-family: 'Exo 2',
"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica,
Arial, "Lucida Grande", sans-serif; Arial, "Lucida Grande", sans-serif;
font-weight: 300;
color: #DDD; color: #DDD;
text-align: center;
} }
h1, h2, h3, h4 {
margin: 0;
}
h1 {
font-size: 5em;
}
a {
font: inherit;
color: inherit;
}
input, select {
background: rgba(0, 0, 0, .3);
border: 1px solid #000;
border-radius: 4px;
width: 67%;
height: 2em;
margin: 1ex 0;
font: inherit;
color: inherit;
text-align: center;
}
input:focus, select:focus {
outline: none;
box-shadow: 0 0 50px #333;
}
input.half, select.half {
width: 33%;
}
input.minimal, select.minimal {
width: auto;
}
input[type="submit"], input[type="image"] {
background: transparent;
border: none;
width: auto;
font-size: 2em;
}
input[type="submit"] {
display: inline;
}
#identity.working input[type="submit"] {
display: none;
}
input[type="image"] {
display: none;
}
#identity.working input[type="image"] {
display: inline;
}
#error {
color: red;
}
header, section, footer {
overflow: hidden;
clear: both;
}
header, footer {
font-size: 0.8em;
}
section {
font-size: 2.5em;
}
/**** LAYOUT ****/ /**** LAYOUT ****/
section { .flex {
display: none; display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: space-between;
}
.flex-row {
flex-direction: row;
justify-content: space-evenly;
} }
section.active {
display: block; .flex-row > * {
width: 20%;
} }
.flex-row label {
display: flex;
flex-direction: column;
align-content: center;
align-items: center;
}
.flex-row label > input,
.flex-row label > select {
width: 100%;
}
.small { .small {
font-size: 0.5em; font-size: 1.25rem;
}
.form > button,
#spinner {
font-size: 8rem;
background: none;
border: none;
}
#main {
font-size: 2rem;
width: 67%;
font-weight: lighter;
}
.form,
.form > * {
width: 100%;
margin: 1ex 0;
}
#main .form {
display: none;
}
#main .form.active {
display: flex;
}
.form > h1 {
font-size: 6rem;
padding: 0;
margin: 0;
margin-bottom: .5ex;
}
header,
footer {
font-size: 0.8rem;
margin: .5rem 0;
}
input,
select {
background: rgba(0, 0, 0, .3);
border: 1px solid #000;
border-radius: 4px;
height: 2em;
}
input:focus,
select:focus,
button:focus,
button:hover {
outline: none;
box-shadow: 0 0 50px #333;
}
option {
background: black;
}
#spinner {
display: none;
height: 1.5em;
}
#main.working .form > button {
display: none;
}
#main.working #spinner {
display: block;
}
#sitePassword {
margin: 0;
font-size: 4rem;
padding: 1rem;
border: solid 1px gray;
}
#sitePassword:empty {
display: none;
}
#error {
color: red;
} }

View File

@ -9,62 +9,77 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/main.css?2"> <link rel="stylesheet" href="css/main.css?2">
</head> </head>
<body> <body class="flex">
<header> <header>
<p><strong>BETA</strong> - This site may not yet work on your browser.<br> <p>
Passwords are generated locally, your master password is not sent to any server. See <a href="https://gitlab.com/MasterPassword/MasterPassword/tree/master/platform-independent/web">the source</a>.</p> <strong>BETA</strong> - This site may not yet work on your browser.<br>
Passwords are generated locally, your master password is not sent to any server. See <a href="https://gitlab.com/MasterPassword/MasterPassword/tree/master/platform-independent/web">the source</a>.
</p>
<span id="error"></span> <span id="error"></span>
</header> </header>
<section id="identity" class="active"><form action="#">
<h1>Identity</h1>
<p>
<input id="userName" type="text" placeholder="Your Full Name" /><br>
<input id="masterPassword" type="password" placeholder="Your Master Password" /><br>
<input type="submit" value="⏎" /><br>
<input type="image" src="img/spinner.svg" />
<select id="version" class="minimal">
<option value="0">V0</option>
<option value="1">V1</option>
<option value="2">V2</option>
<option value="3" selected>V3</option>
</select>
</p>
</form></section>
<section id="site"> <main id="main" class="flex">
<h1>Site</h1> <form action="#" id="identity" class="flex form active">
<p> <h1>Identity</h1>
<input id="siteName" type="text" placeholder="Site Name (eg. apple.com)" /><br> <input id="userName" type="text" placeholder="Your Full Name" required />
<span class="small"> <input id="masterPassword" type="password" placeholder="Your Master Password" required autocomplete="nope" />
<input id="siteCounter" type="number" placeholder="Site Counter" min="1" max="100" value="1" class="half" /> <span class="small flex flex-row">
<select id="siteType" class="half"> <label>
<option value="pin">PIN</option> <select id="version">
<option value="short">Short</option> <option value="0">V0</option>
<option value="basic">Basic</option> <option value="1">V1</option>
<option value="medium">Medium</option> <option value="2">V2</option>
<option value="long" selected>Long</option> <option value="3" selected>V3</option>
<option value="maximum">Maximum</option> </select>
<option value="name">Name</option> Algorithm
<option value="phrase">Phrase</option> </label>
</select> <label>
</span><br> <input type="checkbox" id="rememberName" />
<h2 id="sitePassword" title="Your password is ..."></h2><br> Remember Name?
<input id="logout" type="submit" value="⎋" /> </label>
<input type="image" src="img/spinner.svg" /> </span>
</p> <button type="submit"></button>
</section> </form>
<section id="site" class="flex form">
<h1>Site</h1>
<input id="siteName" type="text" placeholder="Site Name (eg. apple.com)" inputmode="url" />
<span class="small flex flex-row">
<label>
<select id="siteType">
<option value="pin">PIN</option>
<option value="short">Short</option>
<option value="basic">Basic</option>
<option value="medium">Medium</option>
<option value="long" selected>Long</option>
<option value="maximum">Maximum</option>
<option value="name">Name</option>
<option value="phrase">Phrase</option>
</select>
Type
</label>
<label>
<input id="siteCounter" type="number" placeholder="Site Counter" min="1" max="100" value="1" />
Counter
</label>
</span>
<button id="logout"></button>
</section>
<img id="spinner" src="img/spinner.svg" />
<h2 id="sitePassword" title="Your password is ..."></h2>
</main>
<footer> <footer>
<p>This page was made possible thanks to <a href="https://tomthorogood.co.uk/" title="Tom Thorogood">tmthrgd</a>'s <a href="https://github.com/tmthrgd/mpw-js">mpw-js</a>.<br> <p>
<em><a href="https://masterpassword.app">Master Password</a> is a security product and algorithm by <a href="http://www.lhunath.com" title="Maarten Billemont">Maarten Billemont</a>, <a href="http://www.lyndir.com">Lyndir</a> (&copy; 2011-2014).</em><br> This page was made possible thanks to <a href="https://tomthorogood.co.uk/" title="Tom Thorogood">tmthrgd</a>'s <a href="https://github.com/tmthrgd/mpw-js">mpw-js</a>.<br>
Usage implies agreement with our <a href="https://masterpassword.app/privacy.html">privacy policy and disclaimer</a>.</p> <em><a href="https://masterpassword.app">Master Password</a> is a security product and algorithm by <a href="http://www.lhunath.com" title="Maarten Billemont">Maarten Billemont</a>, <a href="http://www.lyndir.com">Lyndir</a> (&copy; 2011-2014).</em><br>
Usage implies agreement with our <a href="https://masterpassword.app/privacy.html">privacy policy and disclaimer</a>.
</p>
</footer> </footer>
<!-- Scripts --> <!-- Scripts -->
<script>document.write('<script src="js/vendor/jquery-2.1.1.min.js"><\/script>')</script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-2.1.1.js"><\/script>')</script>
<script src="js/dependencies.js?4"></script> <script src="js/dependencies.js?4"></script>
<script src="js/main.js?3"></script> <script src="js/main.js?3"></script>
</body> </body>
</html> </html>

View File

@ -1,115 +1,92 @@
var mpw, error; (function() {
const errorText = document.getElementById("error");
const sitePassword = document.getElementById("sitePassword");
function updateMPW() { const identity = document.getElementById("identity");
mpw = null; const userName = document.getElementById("userName");
startWork(); const masterPassword = document.getElementById("masterPassword");
mpw = new MPW( $('#userName')[0].value, $('#masterPassword')[0].value, $('#version')[0].value ); const version = document.getElementById("version");
mpw.key.then(
function() { const site = document.getElementById("site");
doneWork(); const siteName = document.getElementById("siteName");
}, const siteCounter = document.getElementById("siteCounter");
function(reason) { const siteType = document.getElementById("siteType");
error = reason;
let mpw;
function generateKey(e) {
e.preventDefault();
mpw = new MPW(userName.value, masterPassword.value, version.value);
update(() => {
identity.classList.remove('active');
site.classList.add('active');
siteName.select();
});
}
function generatePassword() {
update(() => mpw.generatePassword(siteName.value, siteCounter.valueAsNumber, siteType.value))
.then(pass => sitePassword.textContent = pass)
.catch(_ => sitePassword.textContent = null);
}
function logout() {
if (mpw) {
mpw.invalidate();
mpw = null; mpw = null;
doneWork();
} }
);
}
function startWork() {
update(true);
}
function doneWork() {
update(false);
}
function update(working) {
var screen = mpw? 'site': 'identity';
// Screen Name userName.value = null;
if (screen == 'identity') { masterPassword.value = null;
$('#identity').addClass('active'); siteName.value = null;
sitePassword.textContent = null;
if (!working) site.classList.remove('active');
$('#userName').focus(); identity.classList.add('active');
}
else {
$('#identity').removeClass('active');
$('#userName')[0].value = $('#masterPassword')[0].value = '';
} }
if (screen == 'site') { function update(accept, reject) {
$('#site').addClass('active'); errorText.textContent = null;
main.classList.add("working");
if (!working) return mpw.key
$('#siteName').focus(); .then(accept || (x => x))
} .catch(error => Promise.reject(errorText.textContent = error))
else { .catch(reject || (x => Promise.reject(x)))
$('#site').removeClass('active'); .finally(() => main.classList.remove("working"))
$('#siteName').val(null); ;
$('#sitePassword').text(null);
} }
// Working function selectText() {
if (working && screen == 'identity') if (document.body.createTextRange) { //ms
$('#identity').addClass('working').find('input, select').attr('disabled', 'disabled'); const range = document.body.createTextRange();
else range.moveToElementText(this);
$('#identity').removeClass('working').find('input, select').removeAttr('disabled'); range.select();
} else if (window.getSelection) { //all others
const range = document.createRange();
range.selectNodeContents(this);
if (working && screen == 'site') const selection = window.getSelection();
$('#site').addClass('working'); selection.removeAllRanges();
else selection.addRange(range);
$('#site').removeClass('working'); }
// Error
$('#error').text(error);
}
function updateSite() {
if (!mpw) {
doneWork();
return
} }
startWork(); identity.addEventListener('submit', generateKey);
mpw.generatePassword( $('#siteName')[0].value, $('#siteCounter')[0].valueAsNumber, $('#siteType')[0].value ) site.addEventListener('input', generatePassword);
.then( function (sitePassword) { document.getElementById("logout").addEventListener('click', logout);
$('#sitePassword').text(sitePassword);
doneWork();
}, function (reason) {
error = reason;
doneWork();
});
}
function selectText(element) {
var doc = document, range, selection;
if (doc.body.createTextRange) { //ms sitePassword.addEventListener('click', selectText);
range = doc.body.createTextRange();
range.moveToElementText(element); const rememberName = document.getElementById("rememberName");
range.select(); rememberName.addEventListener("change", e => userName.autocomplete = (e.target.checked ? "name" : "nope"));
} else if (window.getSelection) { //all others rememberName.dispatchEvent(new Event("change"));
selection = window.getSelection();
range = doc.createRange();
range.selectNodeContents(element); for (const input of document.querySelectorAll("input[placeholder]")) {
selection.removeAllRanges(); input.title = input.placeholder;
selection.addRange(range);
} }
}
// Clear values on page refresh
$(function() { logout();
$('#identity form').on('submit', function() { })();
updateMPW();
return false;
});
$('#site input, #site select').on('change input keyup', function() {
updateSite();
});
$('#logout').on('click', function() {
mpw = null;
doneWork();
});
$('#sitePassword').on('click', function() {
selectText(this);
});
doneWork();
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long