mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01: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",
 | 
			
		||||
    "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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user