Merge pull request #694 from himanish-star/feature-change-password

Passwords can now be changed (#687)
This commit is contained in:
Danny Coates 2018-01-18 11:46:11 -08:00 committed by GitHub
commit 9688dde1a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 15 deletions

View File

@ -173,9 +173,9 @@ export default function(state, emitter) {
} }
}); });
emitter.on('password', async ({ password, file }) => { emitter.on('password', async ({ existingPassword, password, file }) => {
try { try {
await FileSender.setPassword(password, file); await FileSender.setPassword(existingPassword, password, file);
metrics.addedPassword({ size: file.size }); metrics.addedPassword({ size: file.size });
file.password = password; file.password = password;
state.storage.writeFiles(); state.storage.writeFiles();

View File

@ -19,11 +19,9 @@ async function sendPassword(file, authKey, rawAuth) {
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) { if (xhr.status === 200) {
return resolve(xhr.response);
}
if (xhr.status === 401) {
const nonce = xhr.getResponseHeader('WWW-Authenticate').split(' ')[1]; const nonce = xhr.getResponseHeader('WWW-Authenticate').split(' ')[1];
file.nonce = nonce; file.nonce = nonce;
return resolve(xhr.response);
} }
reject(new Error(xhr.status)); reject(new Error(xhr.status));
} }
@ -262,7 +260,7 @@ export default class FileSender extends Nanobus {
return this.uploadFile(encrypted, metadata, new Uint8Array(rawAuth)); return this.uploadFile(encrypted, metadata, new Uint8Array(rawAuth));
} }
static async setPassword(password, file) { static async setPassword(existingPassword, password, file) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const secretKey = await window.crypto.subtle.importKey( const secretKey = await window.crypto.subtle.importKey(
'raw', 'raw',
@ -293,6 +291,28 @@ export default class FileSender extends Nanobus {
false, false,
['deriveKey'] ['deriveKey']
); );
const oldPwdkey = await window.crypto.subtle.importKey(
'raw',
encoder.encode(existingPassword),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const oldAuthKey = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode(file.url),
iterations: 100,
hash: 'SHA-256'
},
oldPwdkey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
);
const newAuthKey = await window.crypto.subtle.deriveKey( const newAuthKey = await window.crypto.subtle.deriveKey(
{ {
name: 'PBKDF2', name: 'PBKDF2',
@ -309,11 +329,12 @@ export default class FileSender extends Nanobus {
['sign'] ['sign']
); );
const rawAuth = await window.crypto.subtle.exportKey('raw', newAuthKey); const rawAuth = await window.crypto.subtle.exportKey('raw', newAuthKey);
const aKey = existingPassword ? oldAuthKey : authKey;
try { try {
await sendPassword(file, authKey, rawAuth); await sendPassword(file, aKey, rawAuth);
} catch (e) { } catch (e) {
if (e.message === '401' && file.nonce !== e.nonce) { if (e.message === '401' && file.nonce !== e.nonce) {
await sendPassword(file, authKey, rawAuth); await sendPassword(file, aKey, rawAuth);
} else { } else {
throw e; throw e;
} }

View File

@ -10,8 +10,25 @@ function passwordComplete(state, password) {
const el = html([ const el = html([
`<div class="selectPassword">${state.translate('passwordResult', { `<div class="selectPassword">${state.translate('passwordResult', {
password: '<pre></pre>' password: '<pre></pre>'
})}</div>` })}
<button id="resetButton">${state.translate('changePasswordButton')}</button>
<form id='reset-form' class="setPassword hidden" data-no-csrf>
<input id="unlock-reset-input"
class="unlock-input input-no-btn"
maxlength="64"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}">
<input type="submit"
id="unlock-reset-btn"
class="btn btn-hidden"
value="Reset Password"/>
</form>
</div>`
]); ]);
el.querySelector('#resetButton').onclick = toggleResetInput;
el.querySelector('#unlock-reset-input').oninput = inputChanged;
const passwordOriginal = document.createElement('div'); const passwordOriginal = document.createElement('div');
passwordOriginal.className = 'passwordOriginal'; passwordOriginal.className = 'passwordOriginal';
passwordOriginal.innerText = password; passwordOriginal.innerText = password;
@ -19,11 +36,35 @@ function passwordComplete(state, password) {
const passwordStar = document.createElement('div'); const passwordStar = document.createElement('div');
passwordStar.className = 'passwordStar'; passwordStar.className = 'passwordStar';
passwordStar.innerText = password.replace(/./g, '●'); passwordStar.innerText = password.replace(/./g, '●');
el.lastElementChild.appendChild(passwordOriginal);
el.lastElementChild.appendChild(passwordStar); el.firstElementChild.appendChild(passwordOriginal);
el.firstElementChild.appendChild(passwordStar);
return el; return el;
} }
function inputChanged() {
const resetInput = document.getElementById('unlock-reset-input');
const resetBtn = document.getElementById('unlock-reset-btn');
if (resetInput.value.length > 0) {
resetBtn.classList.remove('btn-hidden');
resetInput.classList.remove('input-no-btn');
} else {
resetBtn.classList.add('btn-hidden');
resetInput.classList.add('input-no-btn');
}
}
function toggleResetInput(event) {
const form = event.target.parentElement.querySelector('form');
if (form.style.visibility === 'hidden' || form.style.visibility === '') {
form.style.visibility = 'visible';
} else {
form.style.visibility = 'hidden';
}
inputChanged();
}
function expireInfo(file, translate, emit) { function expireInfo(file, translate, emit) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60); const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html([ const el = html([
@ -99,6 +140,21 @@ module.exports = function(state, emit) {
</div> </div>
`; `;
if (div.querySelector('#reset-form'))
div.querySelector('#reset-form').onsubmit = resetPassword;
function resetPassword(event) {
event.preventDefault();
const existingPassword = document.querySelector('.passwordOriginal')
.innerText;
const password = document.querySelector('#unlock-reset-input').value;
if (password.length > 0) {
document.getElementById('copy').classList.remove('wait-password');
document.getElementById('copy-btn').disabled = false;
emit('password', { existingPassword, password, file });
}
}
function showPopup() { function showPopup() {
const popupText = document.querySelector('.popuptext'); const popupText = document.querySelector('.popuptext');
popupText.classList.add('show'); popupText.classList.add('show');

View File

@ -52,11 +52,12 @@ module.exports = function(state, emit) {
function setPassword(event) { function setPassword(event) {
event.preventDefault(); event.preventDefault();
const existingPassword = null;
const password = document.getElementById('unlock-input').value; const password = document.getElementById('unlock-input').value;
if (password.length > 0) { if (password.length > 0) {
document.getElementById('copy').classList.remove('wait-password'); document.getElementById('copy').classList.remove('wait-password');
document.getElementById('copy-btn').disabled = false; document.getElementById('copy-btn').disabled = false;
emit('password', { password, file }); emit('password', { existingPassword, password, file });
} }
} }

View File

@ -648,6 +648,25 @@ tbody {
background: #efeff1; background: #efeff1;
} }
#resetButton {
width: 80px;
height: 30px;
background: #fff;
border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 5px;
font-size: 15px;
margin-top: 5px;
margin-left: 15px;
margin-bottom: 12px;
line-height: 24px;
cursor: pointer;
color: #313131;
}
#resetButton:hover {
background: #efeff1;
}
.send-new { .send-new {
font-size: 15px; font-size: 15px;
margin: auto; margin: auto;
@ -857,7 +876,8 @@ tbody {
padding-right: 10px; padding-right: 10px;
} }
#unlock-btn { #unlock-btn,
#unlock-reset-btn {
flex: 0 1 165px; flex: 0 1 165px;
background: #0297f8; background: #0297f8;
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
@ -874,7 +894,8 @@ tbody {
white-space: nowrap; white-space: nowrap;
} }
#unlock-btn:hover { #unlock-btn:hover,
#unlock-reset-btn:hover {
background-color: #0287e8; background-color: #0287e8;
} }
@ -1163,7 +1184,8 @@ tbody {
} }
#copy-btn, #copy-btn,
#unlock-btn { #unlock-btn,
#unlock-reset-btn {
border-radius: 0 0 6px 6px; border-radius: 0 0 6px 6px;
flex: 0 1 65px; flex: 0 1 65px;
} }

View File

@ -95,6 +95,7 @@ footerLinkTerms = Terms
footerLinkCookies = Cookies footerLinkCookies = Cookies
requirePasswordCheckbox = Require a password to download this file requirePasswordCheckbox = Require a password to download this file
addPasswordButton = Add password addPasswordButton = Add password
changePasswordButton = Change
passwordTryAgain = Incorrect password. Try again. passwordTryAgain = Incorrect password. Try again.
// This label is followed by the password needed to download a file // This label is followed by the password needed to download a file
passwordResult = Password: { $password } passwordResult = Password: { $password }