diff --git a/app/fileManager.js b/app/fileManager.js
index 8e9b3f40..0dd46d21 100644
--- a/app/fileManager.js
+++ b/app/fileManager.js
@@ -173,9 +173,9 @@ export default function(state, emitter) {
}
});
- emitter.on('password', async ({ password, file }) => {
+ emitter.on('password', async ({ existingPassword, password, file }) => {
try {
- await FileSender.setPassword(password, file);
+ await FileSender.setPassword(existingPassword, password, file);
metrics.addedPassword({ size: file.size });
file.password = password;
state.storage.writeFiles();
diff --git a/app/fileSender.js b/app/fileSender.js
index 5b04b884..3965edf5 100644
--- a/app/fileSender.js
+++ b/app/fileSender.js
@@ -19,11 +19,9 @@ async function sendPassword(file, authKey, rawAuth) {
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
- return resolve(xhr.response);
- }
- if (xhr.status === 401) {
const nonce = xhr.getResponseHeader('WWW-Authenticate').split(' ')[1];
file.nonce = nonce;
+ return resolve(xhr.response);
}
reject(new Error(xhr.status));
}
@@ -262,7 +260,7 @@ export default class FileSender extends Nanobus {
return this.uploadFile(encrypted, metadata, new Uint8Array(rawAuth));
}
- static async setPassword(password, file) {
+ static async setPassword(existingPassword, password, file) {
const encoder = new TextEncoder();
const secretKey = await window.crypto.subtle.importKey(
'raw',
@@ -293,6 +291,28 @@ export default class FileSender extends Nanobus {
false,
['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(
{
name: 'PBKDF2',
@@ -309,11 +329,12 @@ export default class FileSender extends Nanobus {
['sign']
);
const rawAuth = await window.crypto.subtle.exportKey('raw', newAuthKey);
+ const aKey = existingPassword ? oldAuthKey : authKey;
try {
- await sendPassword(file, authKey, rawAuth);
+ await sendPassword(file, aKey, rawAuth);
} catch (e) {
if (e.message === '401' && file.nonce !== e.nonce) {
- await sendPassword(file, authKey, rawAuth);
+ await sendPassword(file, aKey, rawAuth);
} else {
throw e;
}
diff --git a/app/templates/share.js b/app/templates/share.js
index 622d7f90..291f2ff9 100644
--- a/app/templates/share.js
+++ b/app/templates/share.js
@@ -10,8 +10,25 @@ function passwordComplete(state, password) {
const el = html([
`
${state.translate('passwordResult', {
password: '
'
- })}
`
+ })}
+
+
+ `
]);
+
+ el.querySelector('#resetButton').onclick = toggleResetInput;
+ el.querySelector('#unlock-reset-input').oninput = inputChanged;
+
const passwordOriginal = document.createElement('div');
passwordOriginal.className = 'passwordOriginal';
passwordOriginal.innerText = password;
@@ -19,11 +36,35 @@ function passwordComplete(state, password) {
const passwordStar = document.createElement('div');
passwordStar.className = 'passwordStar';
passwordStar.innerText = password.replace(/./g, '●');
- el.lastElementChild.appendChild(passwordOriginal);
- el.lastElementChild.appendChild(passwordStar);
+
+ el.firstElementChild.appendChild(passwordOriginal);
+ el.firstElementChild.appendChild(passwordStar);
+
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) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html([
@@ -99,6 +140,21 @@ module.exports = function(state, emit) {
`;
+ 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() {
const popupText = document.querySelector('.popuptext');
popupText.classList.add('show');
diff --git a/app/templates/uploadPassword.js b/app/templates/uploadPassword.js
index 0df27f05..e2e72d9d 100644
--- a/app/templates/uploadPassword.js
+++ b/app/templates/uploadPassword.js
@@ -52,11 +52,12 @@ module.exports = function(state, emit) {
function setPassword(event) {
event.preventDefault();
+ const existingPassword = null;
const password = document.getElementById('unlock-input').value;
if (password.length > 0) {
document.getElementById('copy').classList.remove('wait-password');
document.getElementById('copy-btn').disabled = false;
- emit('password', { password, file });
+ emit('password', { existingPassword, password, file });
}
}
diff --git a/assets/main.css b/assets/main.css
index 3048c86d..81b7873b 100644
--- a/assets/main.css
+++ b/assets/main.css
@@ -648,6 +648,25 @@ tbody {
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 {
font-size: 15px;
margin: auto;
@@ -857,7 +876,8 @@ tbody {
padding-right: 10px;
}
-#unlock-btn {
+#unlock-btn,
+#unlock-reset-btn {
flex: 0 1 165px;
background: #0297f8;
border-radius: 0 6px 6px 0;
@@ -874,7 +894,8 @@ tbody {
white-space: nowrap;
}
-#unlock-btn:hover {
+#unlock-btn:hover,
+#unlock-reset-btn:hover {
background-color: #0287e8;
}
@@ -1163,7 +1184,8 @@ tbody {
}
#copy-btn,
- #unlock-btn {
+ #unlock-btn,
+ #unlock-reset-btn {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
diff --git a/public/locales/en-US/send.ftl b/public/locales/en-US/send.ftl
index a2c8367a..765b5bb1 100644
--- a/public/locales/en-US/send.ftl
+++ b/public/locales/en-US/send.ftl
@@ -95,6 +95,7 @@ footerLinkTerms = Terms
footerLinkCookies = Cookies
requirePasswordCheckbox = Require a password to download this file
addPasswordButton = Add password
+changePasswordButton = Change
passwordTryAgain = Incorrect password. Try again.
// This label is followed by the password needed to download a file
passwordResult = Password: { $password }