* Added fake site warning
* Fixed a bunch of style bugs for chrome browsers * Improved check box styles on desktop and mobile * Touch up tool tip styles. Only dark now. * Created a separate terms page * Added 2FA auth token options to login * Added tool tip displays to some buttons on editor * Added pinned and archived options to overflow menu * Changed shared note styles * Disabled Scroll into view * Made image display smaller when adding images to notes * Added a last used color option * Updated help page * Fixed spelling error on terms page * Added a big ass green label on the new note icon * Scratch pad now opens a note, which is the scratch pad * Added better 2fa guide * Added change password option * Added log out and log out all active sessions option * Added strict rate limiting on login and register actions * Added middleware to routes that force authentication to be accessed * Fixed bug that was causing shared notes to appear empty * Updated option now appears on shared notes after they are actually updated
This commit is contained in:
@@ -21,17 +21,17 @@ const port = 3000
|
||||
//
|
||||
// Request Rate Limiter
|
||||
//
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const rateLimit = require('express-rate-limit')
|
||||
//Limiter for the entire app
|
||||
const limiter = rateLimit({
|
||||
windowMs: 10 * 60 * 1000, // minutes
|
||||
max: 1000 // limit each IP to 100 requests per windowMs
|
||||
});
|
||||
windowMs: 10 * 60 * 1000, // 10 minutes
|
||||
max: 1000 // limit each IP to 1000 requests per windowMs
|
||||
})
|
||||
|
||||
// apply to all requests
|
||||
app.use(limiter);
|
||||
|
||||
|
||||
|
||||
var http = require('http').createServer(app);
|
||||
var io = require('socket.io')(http, {
|
||||
path:'/socket'
|
||||
@@ -222,6 +222,7 @@ app.use(express.json({limit: '5mb'}))
|
||||
app.use(function(req, res, next){
|
||||
|
||||
//Always null out master key, never allow it set from outside
|
||||
req.headers.userId = null
|
||||
req.headers.masterKey = null
|
||||
req.headers.sessionId = null
|
||||
|
||||
@@ -243,10 +244,7 @@ app.use(function(req, res, next){
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
console.log(error)
|
||||
|
||||
res.statusMessage = error //Throw 400 error if token is bad
|
||||
res.status(400).end()
|
||||
next('Unauthorized')
|
||||
})
|
||||
} else {
|
||||
next() //No token. Move along.
|
||||
@@ -260,13 +258,12 @@ let UserTest = require('@models/User')
|
||||
let NoteTest = require('@models/Note')
|
||||
let AuthTest = require('@helpers/Auth')
|
||||
|
||||
Auth.testTwoFactor()
|
||||
|
||||
Auth.test()
|
||||
UserTest.keyPairTest('genMan23', '1', printResults)
|
||||
UserTest.keyPairTest('genMan25', '1', printResults)
|
||||
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||
.then( message => {
|
||||
if(printResults) console.log(message)
|
||||
Auth.testTwoFactor()
|
||||
})
|
||||
|
||||
|
||||
@@ -303,4 +300,16 @@ app.use('/api/quick-note', quickNote)
|
||||
//Output running status
|
||||
app.listen(port, () => {
|
||||
// console.log(`Listening on port ${port}!`)
|
||||
})
|
||||
|
||||
//
|
||||
//Error handlers
|
||||
//
|
||||
//Default error handler just say unauthorized for everything
|
||||
app.use(function (err, req, res, next) {
|
||||
if (err) {
|
||||
res.status(401).send('Unauthorized')
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
@@ -449,8 +449,7 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
|
||||
//Shared notes use encrypted key - decrypt key then decrypt note
|
||||
const encryptedShareKey = rows[0][0].encrypted_share_password_key
|
||||
if(encryptedShareKey != null){
|
||||
masterKey = crypto.privateDecrypt(userPrivateKey,
|
||||
Buffer.from(encryptedShareKey, 'base64') )
|
||||
masterKey = crypto.privateDecrypt(userPrivateKey, Buffer.from(encryptedShareKey, 'base64') )
|
||||
}
|
||||
|
||||
let encryptedNoteText = ''
|
||||
@@ -475,10 +474,14 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
|
||||
for (var i = 0; i < rows[0].length; i++) {
|
||||
const otherNote = rows[0][i]
|
||||
//Re-encrypt for other user
|
||||
const updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet)
|
||||
let updatedSnippet = '' //Default to no snippet
|
||||
if(noteText.length > 500){
|
||||
updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet)
|
||||
}
|
||||
db.promise().query('UPDATE note SET snippet = ? WHERE id = ?', [updatedSnippet, otherNote.id])
|
||||
|
||||
SocketIo.to(otherNote['user_id']).emit('new_note_text_saved', {'noteId':otherNote.id, hash})
|
||||
.then((rows, fields) => {
|
||||
SocketIo.to(otherNote['user_id']).emit('new_note_text_saved', {'noteId':otherNote.id, hash})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -489,10 +492,13 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
|
||||
})
|
||||
.then( (rows, fields) => {
|
||||
|
||||
//Set openend time to a minute ago
|
||||
const theFuture = Math.round((+new Date)/1000) + 10
|
||||
|
||||
//Update other note attributes
|
||||
return db.promise()
|
||||
.query('UPDATE note SET pinned = ?, archived = ?, color = ?, snippet = ?, indexed = 0 WHERE id = ? AND user_id = ? LIMIT 1',
|
||||
[pinned, archived, color, noteSnippet, noteId, userId])
|
||||
.query('UPDATE note SET pinned = ?, archived = ?, color = ?, snippet = ?, indexed = 0, opened = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
||||
[pinned, archived, color, noteSnippet, theFuture, noteId, userId])
|
||||
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
@@ -750,7 +756,6 @@ Note.get = (userId, noteId, masterKey) => {
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
const nowTime = Math.round((+new Date)/1000)
|
||||
let noteLockedOut = false
|
||||
let noteData = rows[0][0]
|
||||
// const rawTextId = noteData['rawTextId']
|
||||
@@ -776,6 +781,7 @@ Note.get = (userId, noteId, masterKey) => {
|
||||
noteData.title = textObject[0]
|
||||
noteData.text = textObject[1]
|
||||
|
||||
const nowTime = Math.round((+new Date)/1000)
|
||||
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
|
||||
|
||||
//Return note data
|
||||
@@ -1122,7 +1128,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.log('Error opening note id -> ', note.id)
|
||||
console.log('Error opening note id -> '+note.id+' for userId -> '+userId)
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
|
||||
|
||||
let User = module.exports = {}
|
||||
|
||||
const version = '3.1.5'
|
||||
const version = '3.1.6'
|
||||
|
||||
//Login a user, if that user does not exist create them
|
||||
//Issues login token
|
||||
@@ -498,6 +498,7 @@ User.deleteUser = (userId, password) => {
|
||||
|
||||
let deletePromises = []
|
||||
|
||||
//Delete all notes and raw text
|
||||
let noteDelete = db.promise().query(`
|
||||
DELETE note, note_raw_text
|
||||
FROM note
|
||||
@@ -506,12 +507,14 @@ User.deleteUser = (userId, password) => {
|
||||
`,[userId])
|
||||
deletePromises.push(noteDelete)
|
||||
|
||||
//Delete user entry
|
||||
let userDelete = db.promise().query(`
|
||||
DELETE FROM user WHERE id = ?
|
||||
`,[userId])
|
||||
deletePromises.push(userDelete)
|
||||
|
||||
let tables = ['user_key', 'user_encrypted_search_index', 'attachment']
|
||||
//Delete user_key, encrypted search index
|
||||
let tables = ['user_key', 'user_encrypted_search_index']
|
||||
tables.forEach(tableName => {
|
||||
|
||||
const query = `DELETE FROM ${tableName} WHERE user_id = ?`
|
||||
@@ -519,6 +522,8 @@ User.deleteUser = (userId, password) => {
|
||||
deletePromises.push(deleteQuery)
|
||||
})
|
||||
|
||||
//Remove all note attachments and files
|
||||
|
||||
return Promise.all(deletePromises)
|
||||
}
|
||||
|
||||
|
@@ -6,16 +6,23 @@ let router = express.Router()
|
||||
|
||||
let Attachment = require('@models/Attachment')
|
||||
let Note = require('@models/Note')
|
||||
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
if(userId = req.headers.userId){
|
||||
|
||||
//Session key is required to continue
|
||||
if(!req.headers.sessionId){
|
||||
next('Unauthorized')
|
||||
}
|
||||
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
next()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
router.post('/search', function (req, res) {
|
||||
|
@@ -10,12 +10,17 @@ let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
|
||||
//Session key is required to continue
|
||||
if(!req.headers.sessionId){
|
||||
next('Unauthorized')
|
||||
}
|
||||
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
next()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
//
|
||||
|
@@ -1,17 +1,60 @@
|
||||
var express = require('express')
|
||||
var router = express.Router()
|
||||
const rateLimit = require('express-rate-limit')
|
||||
|
||||
const Note = require('@models/Note')
|
||||
const User = require('@models/User')
|
||||
|
||||
|
||||
|
||||
let Note = require('@models/Note')
|
||||
|
||||
//
|
||||
// Public Note action
|
||||
//
|
||||
router.post('/opensharednote', function (req, res) {
|
||||
const sharedNoteLimiter = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, //30 min window
|
||||
max: 50, // start blocking after 50 requests
|
||||
message:'Unable to open that many shared notes'
|
||||
})
|
||||
router.post('/opensharednote', sharedNoteLimiter, function (req, res) {
|
||||
|
||||
Note.getShared(req.body.noteId, req.body.sharedKey)
|
||||
.then(results => res.send(results))
|
||||
})
|
||||
|
||||
//
|
||||
// Login User
|
||||
//
|
||||
const loginLimiter = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, // 30 min window
|
||||
max: 25, // start blocking after 25 requests
|
||||
message:'Please try to login again later'
|
||||
})
|
||||
router.post('/login', loginLimiter, function (req, res) {
|
||||
|
||||
User.login(req.body.username, req.body.password, req.body.authToken)
|
||||
.then( returnData => {
|
||||
|
||||
res.send(returnData)
|
||||
})
|
||||
})
|
||||
|
||||
//
|
||||
// Register User
|
||||
//
|
||||
const registerLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour window
|
||||
max: 5, // start blocking after 5 requests
|
||||
message:'Please try again to create an acount in an hour'
|
||||
})
|
||||
router.post('/register', registerLimiter, function (req, res) {
|
||||
|
||||
User.register(req.body.username, req.body.password)
|
||||
.then( returnData => {
|
||||
|
||||
res.send(returnData)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
@@ -6,13 +6,19 @@ let QuickNote = require('@models/QuickNote');
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
|
||||
//Session key is required to continue
|
||||
if(!req.headers.sessionId){
|
||||
next('Unauthorized')
|
||||
}
|
||||
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
next()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
//Get quick note text
|
||||
|
@@ -1,16 +1,24 @@
|
||||
var express = require('express')
|
||||
var router = express.Router()
|
||||
|
||||
let Tags = require('@models/Tag');
|
||||
let Tags = require('@models/Tag')
|
||||
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
if(userId = req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
|
||||
//Session key is required to continue
|
||||
if(!req.headers.sessionId){
|
||||
next('Unauthorized')
|
||||
}
|
||||
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
next()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
//Get the latest notes the user has created
|
||||
|
@@ -5,20 +5,24 @@ const User = require('@models/User')
|
||||
const Auth = require('@helpers/Auth')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function timeLog (req, res, next) {
|
||||
// console.log('Time: ', Date.now())
|
||||
next()
|
||||
router.use(function setUserId (req, res, next) {
|
||||
|
||||
//Session key is required to continue
|
||||
if(!req.headers.sessionId){
|
||||
next('Unauthorized')
|
||||
}
|
||||
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// Login User
|
||||
router.post('/login', function (req, res) {
|
||||
|
||||
User.login(req.body.username, req.body.password, req.body.authToken)
|
||||
.then( returnData => {
|
||||
res.send(returnData)
|
||||
})
|
||||
})
|
||||
// Logout User
|
||||
router.post('/logout', function (req, res) {
|
||||
|
||||
@@ -28,19 +32,6 @@ router.post('/logout', function (req, res) {
|
||||
})
|
||||
})
|
||||
|
||||
// Register User
|
||||
router.post('/register', function (req, res) {
|
||||
|
||||
User.register(req.body.username, req.body.password)
|
||||
.then( returnData => {
|
||||
|
||||
res.send(returnData)
|
||||
})
|
||||
.catch(e => {
|
||||
res.send(false)
|
||||
})
|
||||
})
|
||||
|
||||
// change password
|
||||
router.post('/changepassword', function (req, res) {
|
||||
|
||||
|
Reference in New Issue
Block a user