mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
beginning of #98, new multistep wizard, db creation after user enters username and password
This commit is contained in:
parent
3972c27e7a
commit
6235a3c886
16
bin/build-pkg.sh
Executable file
16
bin/build-pkg.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
PKG_DIR=dist/trilium-linux-x64-server
|
||||||
|
|
||||||
|
mkdir $PKG_DIR
|
||||||
|
|
||||||
|
pkg . --targets node8-linux-x64 --output ${PKG_DIR}/trilium
|
||||||
|
|
||||||
|
chmod +x ${PKG_DIR}/trilium
|
||||||
|
|
||||||
|
cp node_modules/sqlite3/lib/binding/node-v57-linux-x64/node_sqlite3.node ${PKG_DIR}/
|
||||||
|
cp node_modules/scrypt/build/Release/scrypt.node ${PKG_DIR}/
|
||||||
|
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
7z a trilium-linux-x64-server.7z trilium-linux-x64-server
|
@ -20,8 +20,7 @@
|
|||||||
"start-forge": "electron-forge start",
|
"start-forge": "electron-forge start",
|
||||||
"package-forge": "electron-forge package",
|
"package-forge": "electron-forge package",
|
||||||
"make-forge": "electron-forge make",
|
"make-forge": "electron-forge make",
|
||||||
"publish-forge": "electron-forge publish",
|
"publish-forge": "electron-forge publish"
|
||||||
"build-pkg": "pkg . --targets node8-linux-x64 --output dist/trilium-linux-x64-server.elf"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-mutex": "^0.1.3",
|
"async-mutex": "^0.1.3",
|
||||||
@ -119,7 +118,10 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
"./db/**/*",
|
"./db/**/*",
|
||||||
"./src/public/**/*",
|
"./src/public/**/*",
|
||||||
"./src/views/**/*"
|
"./src/views/**/*",
|
||||||
|
"./node_modules/mozjpeg/vendor/*",
|
||||||
|
"./node_modules/pngquant-bin/vendor/*",
|
||||||
|
"./node_modules/giflossy/vendor/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,63 @@
|
|||||||
import server from './services/server.js';
|
import server from './services/server.js';
|
||||||
|
|
||||||
$("#setup-form").submit(() => {
|
function SetupModel() {
|
||||||
const username = $("#username").val();
|
this.step = ko.observable("setup-type");
|
||||||
const password1 = $("#password1").val();
|
this.setupType = ko.observable();
|
||||||
const password2 = $("#password2").val();
|
|
||||||
|
|
||||||
if (!username) {
|
this.setupNewDocument = ko.observable(false);
|
||||||
showAlert("Username can't be empty");
|
this.setupSyncFromDesktop = ko.observable(false);
|
||||||
return false;
|
this.setupSyncFromServer = ko.observable(false);
|
||||||
}
|
|
||||||
|
|
||||||
if (!password1) {
|
this.username = ko.observable();
|
||||||
showAlert("Password can't be empty");
|
this.password1 = ko.observable();
|
||||||
return false;
|
this.password2 = ko.observable();
|
||||||
}
|
|
||||||
|
|
||||||
if (password1 !== password2) {
|
this.setupTypeSelected = this.getSetupType = () =>
|
||||||
showAlert("Both password fields need be identical.");
|
this.setupNewDocument()
|
||||||
return false;
|
|| this.setupSyncFromDesktop()
|
||||||
}
|
|| this.setupSyncFromServer();
|
||||||
|
|
||||||
server.post('setup', {
|
this.selectSetupType = () => {
|
||||||
username: username,
|
this.step(this.getSetupType());
|
||||||
password: password1
|
this.setupType(this.getSetupType());
|
||||||
}).then(() => {
|
};
|
||||||
window.location.replace("/");
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
this.back = () => this.step("setup-type");
|
||||||
});
|
|
||||||
|
this.finish = () => {
|
||||||
|
if (this.setupNewDocument()) {
|
||||||
|
const username = this.username();
|
||||||
|
const password1 = this.password1();
|
||||||
|
const password2 = this.password2();
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
showAlert("Username can't be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password1) {
|
||||||
|
showAlert("Password can't be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password1 !== password2) {
|
||||||
|
showAlert("Both password fields need be identical.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.post('setup', {
|
||||||
|
username: username,
|
||||||
|
password: password1
|
||||||
|
}).then(() => {
|
||||||
|
window.location.replace("/");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function showAlert(message) {
|
function showAlert(message) {
|
||||||
$("#alert").html(message);
|
$("#alert").html(message);
|
||||||
$("#alert").show();
|
$("#alert").show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ko.applyBindings(new SetupModel(), document.getElementById('setup-dialog'));
|
@ -1,25 +1,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const optionService = require('../../services/options');
|
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
const utils = require('../../services/utils');
|
|
||||||
const myScryptService = require('../../services/my_scrypt');
|
|
||||||
const passwordEncryptionService = require('../../services/password_encryption');
|
|
||||||
|
|
||||||
async function setup(req) {
|
async function setup(req) {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
await optionService.setOption('username', username);
|
await sqlInit.createInitialDatabase(username, password);
|
||||||
|
|
||||||
await optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32));
|
|
||||||
await optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
|
|
||||||
|
|
||||||
const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password));
|
|
||||||
await optionService.setOption('passwordVerificationHash', passwordVerificationKey);
|
|
||||||
|
|
||||||
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
|
|
||||||
|
|
||||||
sqlInit.setDbReadyAsResolved();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
const repository = require('./repository');
|
|
||||||
const utils = require('./utils');
|
|
||||||
const dateUtils = require('./date_utils');
|
|
||||||
const appInfo = require('./app_info');
|
|
||||||
|
|
||||||
async function getOption(name) {
|
async function getOption(name) {
|
||||||
const option = await repository.getOption(name);
|
const option = await require('./repository').getOption(name);
|
||||||
|
|
||||||
if (!option) {
|
if (!option) {
|
||||||
throw new Error("Option " + name + " doesn't exist");
|
throw new Error("Option " + name + " doesn't exist");
|
||||||
@ -14,7 +9,7 @@ async function getOption(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setOption(name, value) {
|
async function setOption(name, value) {
|
||||||
const option = await repository.getOption(name);
|
const option = await require('./repository').getOption(name);
|
||||||
|
|
||||||
if (!option) {
|
if (!option) {
|
||||||
throw new Error(`Option ${name} doesn't exist`);
|
throw new Error(`Option ${name} doesn't exist`);
|
||||||
@ -36,32 +31,8 @@ async function createOption(name, value, isSynced) {
|
|||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initOptions(startNotePath) {
|
|
||||||
await createOption('documentId', utils.randomSecureToken(16), false);
|
|
||||||
await createOption('documentSecret', utils.randomSecureToken(16), false);
|
|
||||||
|
|
||||||
await createOption('username', '', true);
|
|
||||||
await createOption('passwordVerificationHash', '', true);
|
|
||||||
await createOption('passwordVerificationSalt', '', true);
|
|
||||||
await createOption('passwordDerivedKeySalt', '', true);
|
|
||||||
await createOption('encryptedDataKey', '', true);
|
|
||||||
await createOption('encryptedDataKeyIv', '', true);
|
|
||||||
|
|
||||||
await createOption('startNotePath', startNotePath, false);
|
|
||||||
await createOption('protectedSessionTimeout', 600, true);
|
|
||||||
await createOption('noteRevisionSnapshotTimeInterval', 600, true);
|
|
||||||
await createOption('lastBackupDate', dateUtils.nowDate(), false);
|
|
||||||
await createOption('dbVersion', appInfo.dbVersion, false);
|
|
||||||
|
|
||||||
await createOption('lastSyncedPull', appInfo.dbVersion, false);
|
|
||||||
await createOption('lastSyncedPush', 0, false);
|
|
||||||
|
|
||||||
await createOption('zoomFactor', 1.0, false);
|
|
||||||
await createOption('theme', 'white', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getOption,
|
getOption,
|
||||||
setOption,
|
setOption,
|
||||||
initOptions
|
createOption
|
||||||
};
|
};
|
41
src/services/options_init.js
Normal file
41
src/services/options_init.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const optionService = require('./options');
|
||||||
|
const passwordEncryptionService = require('./password_encryption');
|
||||||
|
const myScryptService = require('./my_scrypt');
|
||||||
|
const appInfo = require('./app_info');
|
||||||
|
const utils = require('./utils');
|
||||||
|
const dateUtils = require('./date_utils');
|
||||||
|
|
||||||
|
async function initOptions(startNotePath, username, password) {
|
||||||
|
await optionService.createOption('documentId', utils.randomSecureToken(16), false);
|
||||||
|
await optionService.createOption('documentSecret', utils.randomSecureToken(16), false);
|
||||||
|
|
||||||
|
await optionService.createOption('startNotePath', startNotePath, false);
|
||||||
|
await optionService.createOption('protectedSessionTimeout', 600, true);
|
||||||
|
await optionService.createOption('noteRevisionSnapshotTimeInterval', 600, true);
|
||||||
|
await optionService.createOption('lastBackupDate', dateUtils.nowDate(), false);
|
||||||
|
await optionService.createOption('dbVersion', appInfo.dbVersion, false);
|
||||||
|
|
||||||
|
await optionService.createOption('lastSyncedPull', appInfo.dbVersion, false);
|
||||||
|
await optionService.createOption('lastSyncedPush', 0, false);
|
||||||
|
|
||||||
|
await optionService.createOption('zoomFactor', 1.0, false);
|
||||||
|
await optionService.createOption('theme', 'white', false);
|
||||||
|
|
||||||
|
await optionService.createOption('username', username);
|
||||||
|
|
||||||
|
await optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32));
|
||||||
|
await optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
|
||||||
|
|
||||||
|
const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password));
|
||||||
|
await optionService.createOption('passwordVerificationHash', passwordVerificationKey);
|
||||||
|
|
||||||
|
// passwordEncryptionService expects these options to already exist
|
||||||
|
await optionService.createOption('encryptedDataKey', '');
|
||||||
|
await optionService.createOption('encryptedDataKeyIv', '');
|
||||||
|
|
||||||
|
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initOptions
|
||||||
|
};
|
@ -5,6 +5,7 @@ const sqlite = require('sqlite');
|
|||||||
const resourceDir = require('./resource_dir');
|
const resourceDir = require('./resource_dir');
|
||||||
const appInfo = require('./app_info');
|
const appInfo = require('./app_info');
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
const options = require('./options');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
|
||||||
async function createConnection() {
|
async function createConnection() {
|
||||||
@ -30,7 +31,9 @@ const dbReady = new Promise((resolve, reject) => {
|
|||||||
|
|
||||||
const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'");
|
const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'");
|
||||||
if (tableResults.length !== 1) {
|
if (tableResults.length !== 1) {
|
||||||
await createInitialDatabase();
|
log.info("DB not found, please visit setup page to initialize Trilium.");
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaReadyResolve();
|
schemaReadyResolve();
|
||||||
@ -52,7 +55,7 @@ const dbReady = new Promise((resolve, reject) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createInitialDatabase() {
|
async function createInitialDatabase(username, password) {
|
||||||
log.info("Connected to db, but schema doesn't exist. Initializing schema ...");
|
log.info("Connected to db, but schema doesn't exist. Initializing schema ...");
|
||||||
|
|
||||||
const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8');
|
const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8');
|
||||||
@ -70,14 +73,13 @@ async function createInitialDatabase() {
|
|||||||
|
|
||||||
const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition");
|
const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition");
|
||||||
|
|
||||||
await require('./options').initOptions(startNoteId);
|
await require('./options_init').initOptions(startNoteId, username, password);
|
||||||
await require('./sync_table').fillAllSyncRows();
|
await require('./sync_table').fillAllSyncRows();
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup.");
|
log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup.");
|
||||||
|
|
||||||
// we don't resolve dbReady promise because user needs to setup the username and password to initialize
|
setDbReadyAsResolved();
|
||||||
// the database
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDbReadyAsResolved() {
|
function setDbReadyAsResolved() {
|
||||||
@ -113,5 +115,6 @@ module.exports = {
|
|||||||
schemaReady,
|
schemaReady,
|
||||||
isUserInitialized,
|
isUserInitialized,
|
||||||
setDbReadyAsResolved,
|
setDbReadyAsResolved,
|
||||||
isDbUpToDate
|
isDbUpToDate,
|
||||||
|
createInitialDatabase
|
||||||
};
|
};
|
@ -5,31 +5,60 @@
|
|||||||
<title>Setup</title>
|
<title>Setup</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div style="width: 500px; margin: auto;">
|
<div id="setup-dialog" style="width: 500px; margin: auto;">
|
||||||
<h1>Trilium Notes setup</h1>
|
<h1>Trilium Notes setup</h1>
|
||||||
|
|
||||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>You're almost done with the setup. That last thing is to choose username and password using which you'll login to the application.
|
<div id="setup-type" data-bind="visible: step() == 'setup-type'">
|
||||||
This password is also used for generating encryption key which encrypts protected notes.</p>
|
<div class="radio">
|
||||||
|
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupNewDocument">
|
||||||
|
I'm a new user and I want to create new Trilium document for my notes</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupSyncFromDesktop">
|
||||||
|
I have server instance up and I want to setup sync with it</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupSyncFromServer">
|
||||||
|
I have desktop instance already and I want to setup sync with it</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-bind="visible: step() == 'new-document'">
|
||||||
|
<p>You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application.
|
||||||
|
This password is also used for generating encryption key which encrypts protected notes.</p>
|
||||||
|
|
||||||
<form id="setup-form">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<input type="text" class="form-control" id="username" placeholder="Arbitrary string">
|
<input type="text" class="form-control" data-bind="value: username" placeholder="Arbitrary string">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password1">Password</label>
|
<label for="password1">Password</label>
|
||||||
<input type="password" class="form-control" id="password1" placeholder="Password">
|
<input type="password" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password2">Repeat password</label>
|
<label for="password2">Repeat password</label>
|
||||||
<input type="password" class="form-control" id="password2" placeholder="Password">
|
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-default">Save</button>
|
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
|
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-bind="visible: step() == 'sync-from-desktop'">
|
||||||
|
sync from desktop
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-bind="visible: step() == 'sync-from-server'">
|
||||||
|
sync from server
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -47,6 +76,8 @@
|
|||||||
<link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet">
|
<link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||||
<script src="libraries/bootstrap/js/bootstrap.js"></script>
|
<script src="libraries/bootstrap/js/bootstrap.js"></script>
|
||||||
|
|
||||||
|
<script src="/libraries/knockout.min.js"></script>
|
||||||
|
|
||||||
<script src="javascripts/setup.js" type="module"></script>
|
<script src="javascripts/setup.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user