mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	beginning of #98, new multistep wizard, db creation after user enters username and password
This commit is contained in:
		
							
								
								
									
										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,23 +1,48 @@ | |||||||
| 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(); |  | ||||||
|  |     this.setupNewDocument = ko.observable(false); | ||||||
|  |     this.setupSyncFromDesktop = ko.observable(false); | ||||||
|  |     this.setupSyncFromServer = ko.observable(false); | ||||||
|  |  | ||||||
|  |     this.username = ko.observable(); | ||||||
|  |     this.password1 = ko.observable(); | ||||||
|  |     this.password2 = ko.observable(); | ||||||
|  |  | ||||||
|  |     this.setupTypeSelected = this.getSetupType = () => | ||||||
|  |         this.setupNewDocument() | ||||||
|  |         || this.setupSyncFromDesktop() | ||||||
|  |         || this.setupSyncFromServer(); | ||||||
|  |  | ||||||
|  |     this.selectSetupType = () => { | ||||||
|  |         this.step(this.getSetupType()); | ||||||
|  |         this.setupType(this.getSetupType()); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     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) { |             if (!username) { | ||||||
|                 showAlert("Username can't be empty"); |                 showAlert("Username can't be empty"); | ||||||
|         return false; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (!password1) { |             if (!password1) { | ||||||
|                 showAlert("Password can't be empty"); |                 showAlert("Password can't be empty"); | ||||||
|         return false; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (password1 !== password2) { |             if (password1 !== password2) { | ||||||
|                 showAlert("Both password fields need be identical."); |                 showAlert("Both password fields need be identical."); | ||||||
|         return false; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             server.post('setup', { |             server.post('setup', { | ||||||
| @@ -26,11 +51,13 @@ $("#setup-form").submit(() => { | |||||||
|             }).then(() => { |             }).then(() => { | ||||||
|                 window.location.replace("/"); |                 window.location.replace("/"); | ||||||
|             }); |             }); | ||||||
|  |         } | ||||||
|     return false; |     }; | ||||||
| }); | } | ||||||
|  |  | ||||||
| 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'"> | ||||||
|  |         <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> |             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> | ||||||
		Reference in New Issue
	
	Block a user