resetting/setting password from options

This commit is contained in:
zadam 2021-12-30 22:54:08 +01:00
parent f92016f9ec
commit 8120f1bf25
10 changed files with 137 additions and 73 deletions

View File

@ -3,14 +3,16 @@ import protectedSessionHolder from "../../services/protected_session_holder.js";
import toastService from "../../services/toast.js";
const TPL = `
<h3>Change password</h3>
<h3 id="password-heading"></h3>
<div class="alert alert-warning" role="alert" style="font-weight: bold; color: red !important;">
Please take care to remember your new password. Password is used to encrypt protected notes. If you forget your password, then all your protected notes are forever lost with no recovery options.
Please take care to remember your new password. Password is used to encrypt protected notes.
If you forget your password, then all your protected notes are forever lost.
In case you did forget your password, <a id="reset-password-button" href="javascript:">click here to reset it</a>.
</div>
<form id="change-password-form">
<div class="form-group">
<div class="form-group" id="old-password-form-group">
<label for="old-password">Old password</label>
<input class="form-control" id="old-password" type="password">
</div>
@ -25,22 +27,41 @@ const TPL = `
<input class="form-control" id="new-password2" type="password">
</div>
<button class="btn btn-primary">Change password</button>
<button class="btn btn-primary" id="save-password-button">Change password</button>
</form>`;
export default class ChangePasswordOptions {
constructor() {
$("#options-credentials").html(TPL);
this.$passwordHeading = $("#password-heading");
this.$form = $("#change-password-form");
this.$oldPassword = $("#old-password");
this.$newPassword1 = $("#new-password1");
this.$newPassword2 = $("#new-password2");
this.$savePasswordButton = $("#save-password-button");
this.$resetPasswordButton = $("#reset-password-button");
this.$resetPasswordButton.on("click", async () => {
if (confirm("By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?")) {
await server.post("password/reset?really=yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes");
const options = await server.get('options');
this.optionsLoaded(options);
alert("Password has been reset. Please set new password");
}
});
this.$form.on('submit', () => this.save());
}
optionsLoaded(options) {
const isPasswordSet = options.isPasswordSet === 'true';
$("#old-password-form-group").toggle(isPasswordSet);
this.$passwordHeading.text(isPasswordSet ? 'Change password' : 'Set password');
this.$savePasswordButton.text(isPasswordSet ? 'Change password' : 'Set password');
}
save() {

View File

@ -67,6 +67,8 @@ function getOptions() {
}
}
resultMap['isPasswordSet'] = !!optionMap['passwordVerificationHash'] ? 'true' : 'false';
return resultMap;
}

View File

@ -1,11 +1,26 @@
"use strict";
const changePasswordService = require('../../services/change_password');
const passwordService = require('../../services/password.js');
function changePassword(req) {
return changePasswordService.changePassword(req.body.current_password, req.body.new_password);
if (passwordService.isPasswordSet()) {
return passwordService.changePassword(req.body.current_password, req.body.new_password);
}
else {
return passwordService.setPassword(req.body.new_password);
}
}
function resetPassword(req) {
// protection against accidental call (not a security measure)
if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") {
return [400, "Incorrect password reset confirmation"];
}
return passwordService.resetPassword();
}
module.exports = {
changePassword
changePassword,
resetPassword
};

View File

@ -4,8 +4,7 @@ const utils = require('../services/utils');
const optionService = require('../services/options');
const myScryptService = require('../services/my_scrypt');
const log = require('../services/log');
const sqlInit = require("../services/sql_init.js");
const optionsInitService = require("../services/options_init.js");
const passwordService = require("../services/password.js");
function loginPage(req, res) {
res.render('login', { failedAuth: false });
@ -16,7 +15,7 @@ function setPasswordPage(req, res) {
}
function setPassword(req, res) {
if (sqlInit.isPasswordSet()) {
if (passwordService.isPasswordSet()) {
return [400, "Password has been already set"];
}
@ -37,7 +36,7 @@ function setPassword(req, res) {
return;
}
optionsInitService.initPassword(password1);
passwordService.setPassword(password1);
res.redirect('login');
}

View File

@ -290,6 +290,7 @@ function register(app) {
apiRoute(GET, '/api/options/user-themes', optionsApiRoute.getUserThemes);
apiRoute(POST, '/api/password/change', passwordApiRoute.changePassword);
apiRoute(POST, '/api/password/reset', passwordApiRoute.resetPassword);
apiRoute(POST, '/api/sync/test', syncApiRoute.testSync);
apiRoute(POST, '/api/sync/now', syncApiRoute.syncNow);

View File

@ -5,8 +5,8 @@ const log = require('./log');
const sqlInit = require('./sql_init');
const utils = require('./utils');
const passwordEncryptionService = require('./password_encryption');
const optionService = require('./options');
const config = require('./config');
const passwordService = require("./password.js");
const noAuthentication = config.General && config.General.noAuthentication === true;
@ -15,7 +15,7 @@ function checkAuth(req, res, next) {
res.redirect("setup");
}
else if (!req.session.loggedIn && !utils.isElectron() && !noAuthentication) {
if (sqlInit.isPasswordSet()) {
if (passwordService.isPasswordSet()) {
res.redirect("login");
} else {
res.redirect("set-password");
@ -56,7 +56,7 @@ function checkAppInitialized(req, res, next) {
}
function checkPasswordSet(req, res, next) {
if (!utils.isElectron() && !sqlInit.isPasswordSet()) {
if (!utils.isElectron() && !passwordService.isPasswordSet()) {
res.redirect("set-password");
} else {
next();

View File

@ -1,37 +0,0 @@
"use strict";
const sql = require('./sql');
const optionService = require('./options');
const myScryptService = require('./my_scrypt');
const utils = require('./utils');
const passwordEncryptionService = require('./password_encryption');
function changePassword(currentPassword, newPassword) {
if (!passwordEncryptionService.verifyPassword(currentPassword)) {
return {
success: false,
message: "Given current password doesn't match hash"
};
}
sql.transactional(() => {
const decryptedDataKey = passwordEncryptionService.getDataKey(currentPassword);
optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32));
optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword));
passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
});
return {
success: true
};
}
module.exports = {
changePassword
};

View File

@ -1,6 +1,4 @@
const optionService = require('./options');
const passwordEncryptionService = require('./password_encryption');
const myScryptService = require('./my_scrypt');
const appInfo = require('./app_info');
const utils = require('./utils');
const log = require('./log');
@ -12,19 +10,6 @@ function initDocumentOptions() {
optionService.createOption('documentSecret', utils.randomSecureToken(16), false);
}
function initPassword(password) {
optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true);
optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true);
const passwordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(password), true);
optionService.createOption('passwordVerificationHash', passwordVerificationKey, true);
// passwordEncryptionService expects these options to already exist
optionService.createOption('encryptedDataKey', '', true);
passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
}
function initNotSyncedOptions(initialized, opts = {}) {
optionService.createOption('openTabs', JSON.stringify([
{
@ -127,7 +112,6 @@ function getKeyboardDefaultOptions() {
module.exports = {
initDocumentOptions,
initPassword,
initNotSyncedOptions,
initStartupOptions
};

83
src/services/password.js Normal file
View File

@ -0,0 +1,83 @@
"use strict";
const sql = require('./sql');
const optionService = require('./options');
const myScryptService = require('./my_scrypt');
const utils = require('./utils');
const passwordEncryptionService = require('./password_encryption');
function isPasswordSet() {
return !!sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'");
}
function changePassword(currentPassword, newPassword) {
if (!isPasswordSet()) {
throw new Error("Password has not been set yet, so it cannot be changed. Use 'setPassword' instead.");
}
if (!passwordEncryptionService.verifyPassword(currentPassword)) {
return {
success: false,
message: "Given current password doesn't match hash"
};
}
sql.transactional(() => {
const decryptedDataKey = passwordEncryptionService.getDataKey(currentPassword);
optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32));
optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword));
passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
});
return {
success: true
};
}
function setPassword(password) {
if (isPasswordSet()) {
throw new Error("Password is set already. Either change it or perform 'reset password' first.");
}
optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true);
optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true);
const passwordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(password), true);
optionService.createOption('passwordVerificationHash', passwordVerificationKey, true);
// passwordEncryptionService expects these options to already exist
optionService.createOption('encryptedDataKey', '', true);
passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
return {
success: true
};
}
function resetPassword() {
// user forgot the password,
sql.transactional(() => {
optionService.setOption('passwordVerificationSalt', '');
optionService.setOption('passwordDerivedKeySalt', '');
optionService.setOption('encryptedDataKey', '');
optionService.setOption('passwordVerificationHash', '');
});
return {
success: true
};
}
module.exports = {
isPasswordSet,
changePassword,
setPassword,
resetPassword
};

View File

@ -30,10 +30,6 @@ function isDbInitialized() {
return initialized === 'true';
}
function isPasswordSet() {
return !!sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'");
}
async function initDbConnection() {
if (!isDbInitialized()) {
log.info(`DB not initialized, please visit setup page` +
@ -93,6 +89,7 @@ async function createInitialDatabase() {
optionsInitService.initDocumentOptions();
optionsInitService.initNotSyncedOptions(true, {});
optionsInitService.initStartupOptions();
require("./password").resetPassword();
});
log.info("Importing demo content ...");
@ -175,6 +172,5 @@ module.exports = {
isDbInitialized,
createInitialDatabase,
createDatabaseForSync,
setDbAsInitialized,
isPasswordSet
setDbAsInitialized
};