|
|
|
@@ -8,7 +8,8 @@ let ProcessText = require('@helpers/ProcessText')
|
|
|
|
|
|
|
|
|
|
const DiffMatchPatch = require('@helpers/DiffMatchPatch')
|
|
|
|
|
|
|
|
|
|
var rp = require('request-promise');
|
|
|
|
|
const cs = require('@helpers/CryptoString')
|
|
|
|
|
const rp = require('request-promise');
|
|
|
|
|
const fs = require('fs')
|
|
|
|
|
|
|
|
|
|
let Note = module.exports = {}
|
|
|
|
@@ -98,7 +99,7 @@ Note.stressTest = () => {
|
|
|
|
|
|
|
|
|
|
// --------------
|
|
|
|
|
|
|
|
|
|
Note.create = (userId, noteText, quickNote = 0) => {
|
|
|
|
|
Note.create = (userId, noteTitle, noteText, quickNote = 0, ) => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
|
|
if(userId == null || userId < 10){ reject('User Id required to create note') }
|
|
|
|
@@ -106,7 +107,7 @@ Note.create = (userId, noteText, quickNote = 0) => {
|
|
|
|
|
const created = Math.round((+new Date)/1000)
|
|
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
|
.query(`INSERT INTO note_raw_text (text, updated) VALUE (?, ?)`, [noteText, created])
|
|
|
|
|
.query(`INSERT INTO note_raw_text (text, title, updated) VALUE (?, ?, ?)`, [noteText, noteTitle, created])
|
|
|
|
|
.then( (rows, fields) => {
|
|
|
|
|
|
|
|
|
|
const rawTextId = rows[0].insertId
|
|
|
|
@@ -129,7 +130,11 @@ Note.reindex = (userId, noteId) => {
|
|
|
|
|
Note.get(userId, noteId)
|
|
|
|
|
.then(note => {
|
|
|
|
|
|
|
|
|
|
const noteText = note.text
|
|
|
|
|
let noteText = note.text
|
|
|
|
|
if(note.encrypted == 1){
|
|
|
|
|
noteText = '' //Don't put note text in encrypted notes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Update Solr index
|
|
|
|
@@ -137,7 +142,7 @@ Note.reindex = (userId, noteId) => {
|
|
|
|
|
Tags.string(userId, noteId)
|
|
|
|
|
.then(tagString => {
|
|
|
|
|
|
|
|
|
|
const fullText = ProcessText.removeHtml(noteText) +' '+ tagString
|
|
|
|
|
const fullText = note.title + ' ' + ProcessText.removeHtml(noteText) +' '+ tagString
|
|
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
|
.query(`
|
|
|
|
@@ -158,33 +163,59 @@ Note.reindex = (userId, noteId) => {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Note.update = (io, userId, noteId, noteText, color, pinned, archived) => {
|
|
|
|
|
Note.update = (io, userId, noteId, noteText, noteTitle, color, pinned, archived, password = '', passwordHint = '') => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
|
|
//Prevent note loss if it saves with empty text
|
|
|
|
|
if(ProcessText.removeHtml(noteText) == ''){
|
|
|
|
|
console.log('Not saving empty note')
|
|
|
|
|
resolve(false)
|
|
|
|
|
// console.log('Not saving empty note')
|
|
|
|
|
// resolve(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const now = Math.round((+new Date)/1000)
|
|
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
|
.query('SELECT note_raw_text_id FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
|
|
|
|
|
.query(`
|
|
|
|
|
SELECT note_raw_text_id, salt FROM note
|
|
|
|
|
JOIN note_raw_text ON note_raw_text_id = note_raw_text.id
|
|
|
|
|
WHERE note.id = ? AND user_id = ?`, [noteId, userId])
|
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
|
|
const textId = rows[0][0]['note_raw_text_id']
|
|
|
|
|
let salt = rows[0][0]['salt']
|
|
|
|
|
|
|
|
|
|
//If password is removed, remove salt. generate a new one next time its encrypted
|
|
|
|
|
if(password.length == 0){
|
|
|
|
|
salt = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If a password is set, create a salt
|
|
|
|
|
if(password.length > 3 && !salt){
|
|
|
|
|
salt = cs.createSalt()
|
|
|
|
|
|
|
|
|
|
//Save password hint on first encryption
|
|
|
|
|
if(passwordHint.length > 0){
|
|
|
|
|
db.promise().query('UPDATE note_raw_text SET password_hint = ? WHERE id = ?', [passwordHint, textId])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Encrypt note text if proper data is setup
|
|
|
|
|
if(password.length > 3 && salt.length > 1000){
|
|
|
|
|
noteText = cs.encrypt(password, salt, noteText)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Update Note text
|
|
|
|
|
return db.promise()
|
|
|
|
|
.query('UPDATE note_raw_text SET text = ?, updated = ? WHERE id = ?', [noteText, now, textId])
|
|
|
|
|
.query('UPDATE note_raw_text SET text = ?, title = ?, updated = ?, salt = ? WHERE id = ?', [noteText, noteTitle, now, salt, textId])
|
|
|
|
|
})
|
|
|
|
|
.then( (rows, fields) => {
|
|
|
|
|
|
|
|
|
|
const encrypted = password.length > 3 ? 1:0
|
|
|
|
|
|
|
|
|
|
//Update other note attributes
|
|
|
|
|
return db.promise()
|
|
|
|
|
.query('UPDATE note SET pinned = ?, archived = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
|
|
|
|
[pinned, archived, color, noteId, userId])
|
|
|
|
|
.query('UPDATE note SET pinned = ?, archived = ?, color = ?, encrypted = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
|
|
|
|
[pinned, archived, color, encrypted, noteId, userId])
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
.then((rows, fields) => {
|
|
|
|
@@ -309,6 +340,9 @@ Note.getDiffText = (userId, noteId, usersCurrentText, lastUpdated) => {
|
|
|
|
|
Note.get(userId, noteId)
|
|
|
|
|
.then(noteObject => {
|
|
|
|
|
|
|
|
|
|
if(!noteObject.text || !usersCurrentText){
|
|
|
|
|
resolve(null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let oldText = noteObject.text.replace(/(\r\n|\n|\r)/gm,"")
|
|
|
|
|
let newText = usersCurrentText.replace(/(\r\n|\n|\r)/gm,"")
|
|
|
|
@@ -354,17 +388,23 @@ Note.getDiffText = (userId, noteId, usersCurrentText, lastUpdated) => {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Note.get = (userId, noteId) => {
|
|
|
|
|
Note.get = (userId, noteId, password = '') => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
db.promise()
|
|
|
|
|
.query(`
|
|
|
|
|
SELECT
|
|
|
|
|
SELECT
|
|
|
|
|
note_raw_text.title,
|
|
|
|
|
note_raw_text.text,
|
|
|
|
|
note_raw_text.salt,
|
|
|
|
|
note_raw_text.password_hint,
|
|
|
|
|
note_raw_text.updated as updated,
|
|
|
|
|
note_raw_text.decrypt_attempts_count,
|
|
|
|
|
note_raw_text.last_decrypted_date,
|
|
|
|
|
note.created,
|
|
|
|
|
note.pinned,
|
|
|
|
|
note.archived,
|
|
|
|
|
note.color,
|
|
|
|
|
note.encrypted,
|
|
|
|
|
count(distinct attachment.id) as attachment_count,
|
|
|
|
|
note.note_raw_text_id as rawTextId,
|
|
|
|
|
shareUser.username as shareUsername
|
|
|
|
@@ -372,15 +412,74 @@ Note.get = (userId, noteId) => {
|
|
|
|
|
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
|
|
|
|
LEFT JOIN attachment ON (note.id = attachment.note_id)
|
|
|
|
|
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
|
|
|
|
|
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId])
|
|
|
|
|
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, noteId])
|
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
|
|
const created = Math.round((+new Date)/1000)
|
|
|
|
|
const nowTime = Math.round((+new Date)/1000)
|
|
|
|
|
let noteLockedOut = false
|
|
|
|
|
let noteData = rows[0][0]
|
|
|
|
|
const rawTextId = noteData['rawTextId']
|
|
|
|
|
noteData.decrypted = true
|
|
|
|
|
|
|
|
|
|
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [created, noteId])
|
|
|
|
|
|
|
|
|
|
//If this is not and encrypted note, pass decrypted true, skip encryption stuff
|
|
|
|
|
if(noteData.encrypted == 1){
|
|
|
|
|
noteData.decrypted = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
//Rate Limiting
|
|
|
|
|
//
|
|
|
|
|
//Check if note is exceeding decrypt attempt limit
|
|
|
|
|
if(noteData.encrypted == 1){
|
|
|
|
|
const timeSinceLastUnlock = nowTime - noteData.last_decrypted_date
|
|
|
|
|
|
|
|
|
|
//To many attempts in less than 5 minutes, note is locked
|
|
|
|
|
if(noteData.decrypt_attempts_count > 3 && timeSinceLastUnlock < 300){
|
|
|
|
|
console.log('Locked Out')
|
|
|
|
|
noteLockedOut = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//its been 5 minutes, reset attempt count
|
|
|
|
|
if(noteData.decrypt_attempts_count > 0 && timeSinceLastUnlock > 300){
|
|
|
|
|
noteLockedOut = false
|
|
|
|
|
noteData.decrypt_attempts_count = 0
|
|
|
|
|
console.log('Resseting Lockout')
|
|
|
|
|
db.promise().query('UPDATE note_raw_text SET last_decrypted_date = ?, decrypt_attempts_count = 0 WHERE id = ?', [nowTime, rawTextId ])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Note is encrypted, lets try and decipher it with the given password
|
|
|
|
|
if(password.length > 3 && noteData.encrypted == 1 && !noteLockedOut){
|
|
|
|
|
|
|
|
|
|
const decipheredText = cs.decrypt(password, noteData.salt, noteData.text)
|
|
|
|
|
|
|
|
|
|
//Text was decrypted, return decrypted text
|
|
|
|
|
if(decipheredText !== null){
|
|
|
|
|
noteData.decrypted = true
|
|
|
|
|
noteData.text = decipheredText
|
|
|
|
|
|
|
|
|
|
//Save last decrypted date, reset decrypt atempts
|
|
|
|
|
db.promise().query('UPDATE note_raw_text SET last_decrypted_date = ?, decrypt_attempts_count = 0 WHERE id = ?', [nowTime, rawTextId ])
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
//Text was not deciphered, delete object, never return cipher text
|
|
|
|
|
if(decipheredText === null){
|
|
|
|
|
noteData.text = '' //Never return cipher text
|
|
|
|
|
noteData.decryptFail = true
|
|
|
|
|
noteData.decrypt_attempts_count++ //Update display for user
|
|
|
|
|
|
|
|
|
|
//Update decrypt attempts
|
|
|
|
|
db.promise().query('UPDATE note_raw_text SET decrypt_attempts_count = decrypt_attempts_count +1 WHERE id = ?', [rawTextId ])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
|
|
|
|
|
|
|
|
|
|
//Return note data
|
|
|
|
|
resolve(rows[0][0])
|
|
|
|
|
delete noteData.salt //remove salt from return data
|
|
|
|
|
noteData.lockedOut = noteLockedOut
|
|
|
|
|
resolve(noteData)
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
.catch(console.log)
|
|
|
|
@@ -484,6 +583,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|
|
|
|
let noteSearchQuery = `
|
|
|
|
|
SELECT note.id,
|
|
|
|
|
SUBSTRING(note_raw_text.text, 1, 1500) as text,
|
|
|
|
|
note_raw_text.title as title,
|
|
|
|
|
note_raw_text.updated as updated,
|
|
|
|
|
opened,
|
|
|
|
|
color,
|
|
|
|
@@ -491,6 +591,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|
|
|
|
count(distinct attachment.id) as attachment_count,
|
|
|
|
|
note.pinned,
|
|
|
|
|
note.archived,
|
|
|
|
|
note.encrypted,
|
|
|
|
|
GROUP_CONCAT(DISTINCT tag.text) as tags,
|
|
|
|
|
GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
|
|
|
|
|
shareUser.username as shareUsername,
|
|
|
|
@@ -527,6 +628,10 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|
|
|
|
searchAllNotes = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(fastFilters.onlyShowEncrypted == 1){
|
|
|
|
|
noteSearchQuery += ' AND encrypted = 1'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If tags are passed, use those tags in search
|
|
|
|
|
if(searchTags.length > 0){
|
|
|
|
|
searchParams.push(searchTags)
|
|
|
|
@@ -610,14 +715,18 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|
|
|
|
noteIds.push(note.id)
|
|
|
|
|
|
|
|
|
|
if(note.text == null){ note.text = '' }
|
|
|
|
|
if(note.encrypted == 1){ note.text = '' }
|
|
|
|
|
|
|
|
|
|
//Deduce note title
|
|
|
|
|
const textData = ProcessText.deduceNoteTitle(note.text)
|
|
|
|
|
// console.log(textData)
|
|
|
|
|
|
|
|
|
|
// console.log(textData)
|
|
|
|
|
|
|
|
|
|
if(note.title == null){
|
|
|
|
|
note.title = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
note.title = textData.title
|
|
|
|
|
note.subtext = textData.sub
|
|
|
|
|
note.titleLength = textData.titleLength
|
|
|
|
|
note.subtextLength = textData.subtextLength
|
|
|
|
|