From 649dc0fbbb4d5b8a3a7f1e78f0a98b48be1688fe Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 15 Oct 2017 16:32:49 -0400 Subject: [PATCH] node authentication --- node/app.js | 28 ++++++++++++++++++++++---- node/auth.js | 20 +++++++++++++++++++ node/package.json | 3 +++ node/routes/app.js | 3 ++- node/routes/audit.js | 3 ++- node/routes/login.js | 37 +++++++++++++++++++++++++++++++++++ node/routes/logout.js | 10 ++++++++++ node/routes/migration.js | 9 +++++++++ node/routes/note_history.js | 3 ++- node/routes/notes.js | 3 ++- node/routes/notes_move.js | 3 ++- node/routes/password.js | 3 ++- node/routes/recent_changes.js | 3 ++- node/routes/settings.js | 3 ++- node/routes/tree.js | 3 ++- node/routes/users.js | 8 -------- node/views/login.ejs | 8 ++++---- src/templates/login.html | 2 +- 18 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 node/auth.js create mode 100644 node/routes/login.js create mode 100644 node/routes/logout.js create mode 100644 node/routes/migration.js delete mode 100644 node/routes/users.js diff --git a/node/app.js b/node/app.js index 1e92cf326..46f08fff2 100644 --- a/node/app.js +++ b/node/app.js @@ -4,9 +4,15 @@ const favicon = require('serve-favicon'); const logger = require('morgan'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); +const helmet = require('helmet'); +const session = require('express-session'); const appRoute = require('./routes/app'); -const usersRoute = require('./routes/users'); +const loginRoute = require('./routes/login'); +const logoutRoute = require('./routes/logout'); +const migrationRoute = require('./routes/migration'); + +// API routes const treeRoute = require('./routes/tree'); const notesRoute = require('./routes/notes'); const notesMoveRoute = require('./routes/notes_move'); @@ -28,16 +34,30 @@ const app = express(); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); -// uncomment after placing your favicon in /public -//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(helmet()); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.use(cookieParser()); app.use(express.static(path.join(__dirname, '../static'))); +app.use(session({ + secret: "sdhkjhdsklajf", // FIXME: need to use the DB one + resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request. + saveUninitialized: false, // true forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified. + cookie: { + // path: "/", + httpOnly: true, + maxAge: 1800000 + } +})); +// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use('/app', appRoute); -app.use('/users', usersRoute); +app.use('/login', loginRoute); +app.use('/logout', logoutRoute); +app.use('/migration', migrationRoute); + app.use('/api/tree', treeRoute); app.use('/api/notes', notesRoute); app.use('/api/notes', notesMoveRoute); diff --git a/node/auth.js b/node/auth.js new file mode 100644 index 000000000..e0fa34aa7 --- /dev/null +++ b/node/auth.js @@ -0,0 +1,20 @@ +function checkAuth(req, res, next) { + if (!req.session.loggedIn) { + res.redirect("login"); + } else { + next(); + } +} + +function checkApiAuth(req, res, next) { + if (!req.session.loggedIn) { + res.sendStatus(401); + } else { + next(); + } +} + +module.exports = { + checkAuth, + checkApiAuth +}; \ No newline at end of file diff --git a/node/package.json b/node/package.json index 617cab67d..0925c8738 100644 --- a/node/package.json +++ b/node/package.json @@ -11,7 +11,10 @@ "debug": "~3.1.0", "ejs": "~2.5.7", "express": "~4.16.2", + "express-session": "^1.15.6", + "express-sessions": "^1.0.6", "fs-extra": "^4.0.2", + "helmet": "^3.9.0", "ini": "^1.3.4", "morgan": "~1.9.0", "scrypt": "^6.0.3", diff --git a/node/routes/app.js b/node/routes/app.js index 382a9c3af..91887dbfc 100644 --- a/node/routes/app.js +++ b/node/routes/app.js @@ -1,7 +1,8 @@ const express = require('express'); const router = express.Router(); +const auth = require('../auth'); -router.get('', function(req, res, next) { +router.get('', auth.checkAuth, (req, res, next) => { res.render('app', {}); }); diff --git a/node/routes/audit.js b/node/routes/audit.js index a62105808..a0f029c30 100644 --- a/node/routes/audit.js +++ b/node/routes/audit.js @@ -1,8 +1,9 @@ const express = require('express'); const router = express.Router(); const sql = require('../sql'); +const auth = require('../auth'); -router.get('/:full_load_time', async (req, res, next) => { +router.get('/:full_load_time', auth.checkApiAuth, async (req, res, next) => { const fullLoadTime = req.params.full_load_time; const browserId = req.get('x-browser-id'); diff --git a/node/routes/login.js b/node/routes/login.js new file mode 100644 index 000000000..f17c0f90c --- /dev/null +++ b/node/routes/login.js @@ -0,0 +1,37 @@ +const express = require('express'); +const router = express.Router(); +const utils = require('../utils'); +const sql = require('../sql'); +const my_scrypt = require('../my_scrypt'); + +router.get('', (req, res, next) => { + res.render('login', { 'failedAuth': false }); +}); + +router.post('', async (req, res, next) => { + const userName = await sql.getOption('username'); + + const guessedPassword = req.body.password; + + if (req.body.username === userName && await verifyPassword(guessedPassword)) { + const rememberMe = req.body.rememberme; + + req.session.loggedIn = true; + + return res.redirect('app'); + } + else { + res.render('login', {'failedAuth': true}); + } +}); + + +async function verifyPassword(guessed_password) { + const hashed_password = utils.fromBase64(await sql.getOption('password_verification_hash')); + + const guess_hashed = await my_scrypt.getVerificationHash(guessed_password); + + return guess_hashed.equals(hashed_password); +} + +module.exports = router; diff --git a/node/routes/logout.js b/node/routes/logout.js new file mode 100644 index 000000000..b566ef60f --- /dev/null +++ b/node/routes/logout.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); + +router.post('', async (req, res, next) => { + req.session.loggedIn = false; + + res.redirect('login'); +}); + +module.exports = router; diff --git a/node/routes/migration.js b/node/routes/migration.js new file mode 100644 index 000000000..104eb8d06 --- /dev/null +++ b/node/routes/migration.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const auth = require('../auth'); + +router.get('', auth.checkAuth, (req, res, next) => { + res.render('migration', {}); +}); + +module.exports = router; diff --git a/node/routes/note_history.js b/node/routes/note_history.js index b521a44d9..a7f7d18c4 100644 --- a/node/routes/note_history.js +++ b/node/routes/note_history.js @@ -1,8 +1,9 @@ const express = require('express'); const router = express.Router(); const sql = require('../sql'); +const auth = require('../auth'); -router.get('/:noteId', async (req, res, next) => { +router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { const noteId = req.params.noteId; const history = await sql.getResults("select * from notes_history where note_id = ? order by date_modified desc", [noteId]); diff --git a/node/routes/notes.js b/node/routes/notes.js index 14f285b78..6682e0e37 100644 --- a/node/routes/notes.js +++ b/node/routes/notes.js @@ -3,8 +3,9 @@ const router = express.Router(); const sql = require('../sql'); const utils = require('../utils'); const audit_category = require('../audit_category'); +const auth = require('../auth'); -router.get('/:noteId', async (req, res, next) => { +router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { let noteId = req.params.noteId; await sql.execute("update options set opt_value = ? where opt_name = 'start_node'", [noteId]); diff --git a/node/routes/notes_move.js b/node/routes/notes_move.js index 452c03c91..b794ca264 100644 --- a/node/routes/notes_move.js +++ b/node/routes/notes_move.js @@ -3,8 +3,9 @@ const router = express.Router(); const sql = require('../sql'); const utils = require('../utils'); const audit_category = require('../audit_category'); +const auth = require('../auth'); -router.put('/:noteId/moveTo/:parentId', async (req, res, next) => { +router.put('/:noteId/moveTo/:parentId', auth.checkApiAuth, async (req, res, next) => { let noteId = req.params.noteId; let parentId = req.params.parentId; diff --git a/node/routes/password.js b/node/routes/password.js index 723363d57..2336caa29 100644 --- a/node/routes/password.js +++ b/node/routes/password.js @@ -2,8 +2,9 @@ const express = require('express'); const router = express.Router(); const sql = require('../sql'); const changePassword = require('../change_password'); +const auth = require('../auth'); -router.post('/change', async (req, res, next) => { +router.post('/change', auth.checkApiAuth, async (req, res, next) => { const result = await changePassword.changePassword(req.body['current_password'], req.body['new_password']); res.send(result); diff --git a/node/routes/recent_changes.js b/node/routes/recent_changes.js index d28fa70d7..497aefa24 100644 --- a/node/routes/recent_changes.js +++ b/node/routes/recent_changes.js @@ -1,8 +1,9 @@ const express = require('express'); const router = express.Router(); const sql = require('../sql'); +const auth = require('../auth'); -router.get('/', async (req, res, next) => { +router.get('/', auth.checkApiAuth, async (req, res, next) => { const recentChanges = await sql.getResults("select * from notes_history order by date_modified desc limit 1000"); res.send(recentChanges); diff --git a/node/routes/settings.js b/node/routes/settings.js index b82d75a5d..3cb70674e 100644 --- a/node/routes/settings.js +++ b/node/routes/settings.js @@ -2,10 +2,11 @@ const express = require('express'); const router = express.Router(); const sql = require('../sql'); const audit_category = require('../audit_category'); +const auth = require('../auth'); const ALLOWED_OPTIONS = ['encryption_session_timeout', 'history_snapshot_time_interval']; -router.get('/', async (req, res, next) => { +router.get('/', auth.checkApiAuth, async (req, res, next) => { const dict = {}; const settings = await sql.getResults("SELECT opt_name, opt_value FROM options WHERE opt_name IN (" diff --git a/node/routes/tree.js b/node/routes/tree.js index 98a367146..d9cb4ddae 100644 --- a/node/routes/tree.js +++ b/node/routes/tree.js @@ -3,8 +3,9 @@ const router = express.Router(); const sql = require('../sql'); const utils = require('../utils'); const backup = require('../backup'); +const auth = require('../auth'); -router.get('/', async (req, res, next) => { +router.get('/', auth.checkApiAuth, async (req, res, next) => { await backup.regularBackup(); const notes = await sql.getResults("select " diff --git a/node/routes/users.js b/node/routes/users.js deleted file mode 100644 index bad390028..000000000 --- a/node/routes/users.js +++ /dev/null @@ -1,8 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -router.get('/', function(req, res, next) { - res.send('respond with a resource'); -}); - -module.exports = router; diff --git a/node/views/login.ejs b/node/views/login.ejs index bfd5e8c36..81cee5630 100644 --- a/node/views/login.ejs +++ b/node/views/login.ejs @@ -10,11 +10,11 @@

Trilium login

- {% if failedAuth %} + <% if (failedAuth) { %>
Username and / or password are incorrect. Please try again.
- {% endif %} + <% } %>
@@ -44,7 +44,7 @@
- - + + \ No newline at end of file diff --git a/src/templates/login.html b/src/templates/login.html index bfd5e8c36..b85514719 100644 --- a/src/templates/login.html +++ b/src/templates/login.html @@ -32,7 +32,7 @@