mirror of
https://github.com/zadam/trilium.git
synced 2025-01-07 15:49:01 +08:00
beginning of #98, new multistep wizard, db creation after user enters username and password
This commit is contained in:
parent
3972c27e7a
commit
6235a3c886
8 changed files with 167 additions and 90 deletions
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",
|
||||
"package-forge": "electron-forge package",
|
||||
"make-forge": "electron-forge make",
|
||||
"publish-forge": "electron-forge publish",
|
||||
"build-pkg": "pkg . --targets node8-linux-x64 --output dist/trilium-linux-x64-server.elf"
|
||||
"publish-forge": "electron-forge publish"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.1.3",
|
||||
|
@ -119,7 +118,10 @@
|
|||
"assets": [
|
||||
"./db/**/*",
|
||||
"./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';
|
||||
|
||||
$("#setup-form").submit(() => {
|
||||
const username = $("#username").val();
|
||||
const password1 = $("#password1").val();
|
||||
const password2 = $("#password2").val();
|
||||
function SetupModel() {
|
||||
this.step = ko.observable("setup-type");
|
||||
this.setupType = ko.observable();
|
||||
|
||||
if (!username) {
|
||||
showAlert("Username can't be empty");
|
||||
return false;
|
||||
}
|
||||
this.setupNewDocument = ko.observable(false);
|
||||
this.setupSyncFromDesktop = ko.observable(false);
|
||||
this.setupSyncFromServer = ko.observable(false);
|
||||
|
||||
if (!password1) {
|
||||
showAlert("Password can't be empty");
|
||||
return false;
|
||||
}
|
||||
this.username = ko.observable();
|
||||
this.password1 = ko.observable();
|
||||
this.password2 = ko.observable();
|
||||
|
||||
if (password1 !== password2) {
|
||||
showAlert("Both password fields need be identical.");
|
||||
return false;
|
||||
}
|
||||
this.setupTypeSelected = this.getSetupType = () =>
|
||||
this.setupNewDocument()
|
||||
|| this.setupSyncFromDesktop()
|
||||
|| this.setupSyncFromServer();
|
||||
|
||||
server.post('setup', {
|
||||
username: username,
|
||||
password: password1
|
||||
}).then(() => {
|
||||
window.location.replace("/");
|
||||
});
|
||||
this.selectSetupType = () => {
|
||||
this.step(this.getSetupType());
|
||||
this.setupType(this.getSetupType());
|
||||
};
|
||||
|
||||
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) {
|
||||
$("#alert").html(message);
|
||||
$("#alert").show();
|
||||
}
|
||||
}
|
||||
|
||||
ko.applyBindings(new SetupModel(), document.getElementById('setup-dialog'));
|
|
@ -1,25 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
const optionService = require('../../services/options');
|
||||
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) {
|
||||
const { username, password } = req.body;
|
||||
|
||||
await optionService.setOption('username', username);
|
||||
|
||||
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();
|
||||
await sqlInit.createInitialDatabase(username, password);
|
||||
}
|
||||
|
||||
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) {
|
||||
const option = await repository.getOption(name);
|
||||
const option = await require('./repository').getOption(name);
|
||||
|
||||
if (!option) {
|
||||
throw new Error("Option " + name + " doesn't exist");
|
||||
|
@ -14,7 +9,7 @@ async function getOption(name) {
|
|||
}
|
||||
|
||||
async function setOption(name, value) {
|
||||
const option = await repository.getOption(name);
|
||||
const option = await require('./repository').getOption(name);
|
||||
|
||||
if (!option) {
|
||||
throw new Error(`Option ${name} doesn't exist`);
|
||||
|
@ -36,32 +31,8 @@ async function createOption(name, value, isSynced) {
|
|||
}).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 = {
|
||||
getOption,
|
||||
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 appInfo = require('./app_info');
|
||||
const sql = require('./sql');
|
||||
const options = require('./options');
|
||||
const cls = require('./cls');
|
||||
|
||||
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'");
|
||||
if (tableResults.length !== 1) {
|
||||
await createInitialDatabase();
|
||||
log.info("DB not found, please visit setup page to initialize Trilium.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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 ...");
|
||||
|
||||
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");
|
||||
|
||||
await require('./options').initOptions(startNoteId);
|
||||
await require('./options_init').initOptions(startNoteId, username, password);
|
||||
await require('./sync_table').fillAllSyncRows();
|
||||
});
|
||||
|
||||
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
|
||||
// the database
|
||||
setDbReadyAsResolved();
|
||||
}
|
||||
|
||||
function setDbReadyAsResolved() {
|
||||
|
@ -113,5 +115,6 @@ module.exports = {
|
|||
schemaReady,
|
||||
isUserInitialized,
|
||||
setDbReadyAsResolved,
|
||||
isDbUpToDate
|
||||
isDbUpToDate,
|
||||
createInitialDatabase
|
||||
};
|
|
@ -5,31 +5,60 @@
|
|||
<title>Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width: 500px; margin: auto;">
|
||||
<div id="setup-dialog" style="width: 500px; margin: auto;">
|
||||
<h1>Trilium Notes setup</h1>
|
||||
|
||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||
</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.
|
||||
This password is also used for generating encryption key which encrypts protected notes.</p>
|
||||
<div id="setup-type" data-bind="visible: step() == 'setup-type'">
|
||||
<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">
|
||||
<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 class="form-group">
|
||||
<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 class="form-group">
|
||||
<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>
|
||||
|
||||
<button type="submit" class="btn btn-default">Save</button>
|
||||
</form>
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
@ -47,6 +76,8 @@
|
|||
<link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<script src="libraries/bootstrap/js/bootstrap.js"></script>
|
||||
|
||||
<script src="/libraries/knockout.min.js"></script>
|
||||
|
||||
<script src="javascripts/setup.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue