diff --git a/db/migrations/0189__delete_username_option.sql b/db/migrations/0189__delete_username_option.sql new file mode 100644 index 000000000..439a201e6 --- /dev/null +++ b/db/migrations/0189__delete_username_option.sql @@ -0,0 +1 @@ +DELETE FROM options WHERE name = 'username'; diff --git a/src/routes/api/login.js b/src/routes/api/login.js index 9e4cc318a..334ad7806 100644 --- a/src/routes/api/login.js +++ b/src/routes/api/login.js @@ -85,14 +85,10 @@ function logoutFromProtectedSession() { } function token(req) { - const username = req.body.username; const password = req.body.password; - const isUsernameValid = username === optionService.getOption('username'); - const isPasswordValid = passwordEncryptionService.verifyPassword(password); - - if (!isUsernameValid || !isPasswordValid) { - return [401, "Incorrect username/password"]; + if (!passwordEncryptionService.verifyPassword(password)) { + return [401, "Incorrect password"]; } const apiToken = new ApiToken({ diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 6ad5c5f0b..a2ff192ae 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -6,7 +6,6 @@ const searchService = require('../../services/search/services/search'); // options allowed to be updated directly in options dialog const ALLOWED_OPTIONS = new Set([ - 'username', // not exposed for update (not harmful anyway), needed for reading 'eraseEntitiesAfterTimeInSeconds', 'protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval', diff --git a/src/routes/api/setup.js b/src/routes/api/setup.js index e0ba3ef0a..dbaf44807 100644 --- a/src/routes/api/setup.js +++ b/src/routes/api/setup.js @@ -18,9 +18,9 @@ async function setupNewDocument() { } function setupSyncFromServer(req) { - const { syncServerHost, syncProxy, username, password } = req.body; + const { syncServerHost, syncProxy, password } = req.body; - return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, username, password); + return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password); } function saveSyncSeed(req) { diff --git a/src/routes/login.js b/src/routes/login.js index e2e670007..40581f24b 100644 --- a/src/routes/login.js +++ b/src/routes/login.js @@ -4,21 +4,48 @@ const utils = require('../services/utils'); const optionService = require('../services/options'); const myScryptService = require('../services/my_scrypt'); const log = require('../services/log'); +const sqlInit = require("../services/sql_init.js"); +const optionsInitService = require("../services/options_init.js"); function loginPage(req, res) { res.render('login', { failedAuth: false }); } function setPasswordPage(req, res) { - res.render('set_password', { failed: false }); + res.render('set_password', { error: false }); +} + +function setPassword(req, res) { + if (sqlInit.isPasswordSet()) { + return [400, "Password has been already set"]; + } + + let {password1, password2} = req.body; + password1 = password1.trim(); + password2 = password2.trim(); + + let error; + + if (password1 !== password2) { + error = "Entered passwords don't match."; + } else if (password1.length < 4) { + error = "Password must be at least 4 characters long."; + } + + if (error) { + res.render('set_password', { error }); + return; + } + + optionsInitService.initPassword(password1); + + res.redirect('login'); } function login(req, res) { - const userName = optionService.getOption('username'); - const guessedPassword = req.body.password; - if (req.body.username === userName && verifyPassword(guessedPassword)) { + if (verifyPassword(guessedPassword)) { const rememberMe = req.body.remember_me; req.session.regenerate(() => { @@ -34,7 +61,7 @@ function login(req, res) { } else { // note that logged IP address is usually meaningless since the traffic should come from a reverse proxy - log.info(`WARNING: Wrong username / password from ${req.ip}, rejecting.`); + log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`); res.render('login', {'failedAuth': true}); } @@ -60,6 +87,7 @@ function logout(req, res) { module.exports = { loginPage, setPasswordPage, + setPassword, login, logout }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 473048a7d..b76997d07 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -183,7 +183,7 @@ const uploadMiddleware = multer.single('upload'); function register(app) { route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index); route(GET, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); - route(GET, '/set_password', [auth.checkAppInitialized], loginRoute.setPasswordPage); + route(GET, '/set-password', [auth.checkAppInitialized], loginRoute.setPasswordPage); const loginRateLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes @@ -192,6 +192,7 @@ function register(app) { route(POST, '/login', [loginRateLimiter], loginRoute.login); route(POST, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout); + route(POST, '/set-password', [auth.checkAppInitialized], loginRoute.setPassword); route(GET, '/setup', [], setupRoute.setupPage); apiRoute(GET, '/api/tree', treeApiRoute.getTree); diff --git a/src/services/app_info.js b/src/services/app_info.js index 22a85062b..08c3c7a84 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -4,8 +4,8 @@ const build = require('./build'); const packageJson = require('../../package'); const {TRILIUM_DATA_DIR} = require('./data_dir'); -const APP_DB_VERSION = 188; -const SYNC_VERSION = 23; +const APP_DB_VERSION = 189; +const SYNC_VERSION = 24; const CLIPPER_PROTOCOL_VERSION = "1.0"; module.exports = { diff --git a/src/services/auth.js b/src/services/auth.js index d2ca55eff..05da7b7e7 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -18,7 +18,7 @@ function checkAuth(req, res, next) { if (sqlInit.isPasswordSet()) { res.redirect("login"); } else { - res.redirect("set_password"); + res.redirect("set-password"); } } else { @@ -57,7 +57,7 @@ function checkAppInitialized(req, res, next) { function checkPasswordSet(req, res, next) { if (!utils.isElectron() && !sqlInit.isPasswordSet()) { - res.redirect("set_password"); + res.redirect("set-password"); } else { next(); } @@ -99,10 +99,10 @@ function checkCredentials(req, res, next) { const auth = new Buffer.from(header, 'base64').toString(); const [username, password] = auth.split(/:/); - const dbUsername = optionService.getOption('username'); + // username is ignored - if (dbUsername !== username || !passwordEncryptionService.verifyPassword(password)) { - res.status(401).send('Incorrect username and/or password'); + if (!passwordEncryptionService.verifyPassword(password)) { + res.status(401).send('Incorrect password'); } else { next(); diff --git a/src/services/options_init.js b/src/services/options_init.js index 6760c4212..acc74a7e7 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -12,7 +12,7 @@ function initDocumentOptions() { optionService.createOption('documentSecret', utils.randomSecureToken(16), false); } -function initPassword(username, password) { +function initPassword(password) { optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true); optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true); diff --git a/src/services/request.js b/src/services/request.js index 3033ab0f7..30e7d9e09 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -38,7 +38,7 @@ function exec(opts) { }; if (opts.auth) { - headers['trilium-cred'] = Buffer.from(opts.auth.username + ":" + opts.auth.password).toString('base64'); + headers['trilium-cred'] = Buffer.from("dummy:" + opts.auth.password).toString('base64'); } const request = client.request({ diff --git a/src/services/setup.js b/src/services/setup.js index 271f8ce3c..eb176bebe 100644 --- a/src/services/setup.js +++ b/src/services/setup.js @@ -55,7 +55,7 @@ async function requestToSyncServer(method, path, body = null) { }), timeout); } -async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, password) { +async function setupSyncFromSyncServer(syncServerHost, syncProxy, password) { if (sqlInit.isDbInitialized()) { return { result: 'failure', @@ -70,10 +70,7 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, pass const resp = await request.exec({ method: 'get', url: syncServerHost + '/api/setup/sync-seed', - auth: { - username, - password - }, + auth: { password }, proxy: syncProxy, timeout: 30000 // seed request should not take long }); diff --git a/src/services/sql_init.js b/src/services/sql_init.js index 6412cceec..5393b11ea 100644 --- a/src/services/sql_init.js +++ b/src/services/sql_init.js @@ -31,11 +31,7 @@ function isDbInitialized() { } function isPasswordSet() { - const value = sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'"); - - console.log("AAAAAAAAAAAAEEEEEEEEE", value); - - return !!value; + return !!sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'"); } async function initDbConnection() { diff --git a/src/views/dialogs/options.ejs b/src/views/dialogs/options.ejs index 844f63706..9c602bd05 100644 --- a/src/views/dialogs/options.ejs +++ b/src/views/dialogs/options.ejs @@ -20,7 +20,7 @@ Code notes