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", | ||||
|     "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> | ||||
		Reference in New Issue
	
	Block a user