* Adjusted theme colors to add more contrast on white theme while making black more OLED friendly

* Links now get an underline on hover
* Cleaned up CSS variable names, added another theme color for more control
* Cleaned up unused CSS, removed scrollbars popping up, tons of other little UI tweaks
* Renamed shared notes to inbox
* Tweaked form display, seperated login and create accouts
* Put login/sign up form on home page
* Created more legitimate marketing for home page
* Tons up updates to note page and note input panel
* Better support for two users editing a note
* MUCH better diff handling, web sockets restore notes with unsaved diffs
* Moved all squire text modifier functions into a mixin class
* It now says saving when closing a note
* Lots of cleanup and better handiling of events on mount and destroy
* Scroll behavior modified to load notes when closer to bottom of page
* Pretty decent shared notes and sharable link support
* Updated help text
* Search now includes tag suggestions and attachment suggestions
* Cleaned up scratch pad a ton, allow for users to create new scratch pads
* Created a 404 Page and a Shared note page
* So many other small improvements. Oh my god, what is wrong with me, not doing commits!?
This commit is contained in:
Max G
2020-06-07 20:57:35 +00:00
parent 8e5e06be9b
commit 6bb856689d
31 changed files with 1605 additions and 1095 deletions

View File

@@ -280,9 +280,7 @@ Attachment.scanTextForWebsites = (io, userId, noteId, noteText) => {
Attachment.scrapeUrlsCreateAttachments(userId, noteId, foundUrls).then( freshlyScrapedText => {
//Once everything is done being scraped, emit new attachment events
if(io){
io.to(userId).emit('update_counts')
}
SocketIo.to(userId).emit('update_counts')
solrAttachmentText += freshlyScrapedText
resolve(solrAttachmentText)

View File

@@ -72,7 +72,7 @@ Note.test = (userId, masterKey, printResults) => {
if(printResults) console.log('Test: Normal Note Search Index - Pass')
} else { console.log('Test: Search Index - Fail') }
return ShareNote.migrateNoteToShared(userId, testNoteId, shareUserId, masterKey)
return ShareNote.addUserToSharedNote(userId, testNoteId, shareUserId, masterKey)
})
.then(({success, shareUserId, sharedUserNoteId}) => {
@@ -134,13 +134,13 @@ Note.test = (userId, masterKey, printResults) => {
if(printResults) console.log('Test: Delete Shared Note - Pass')
return ShareNote.migrateNoteToShared(userId, testNoteId2, shareUserId, masterKey)
return ShareNote.addUserToSharedNote(userId, testNoteId2, shareUserId, masterKey)
})
.then(({success, shareUserId, sharedUserNoteId}) => {
if(printResults) console.log('Test: Created Another New Shared Note - pass')
return ShareNote.removeUserFromShared(userId, testNoteId2, sharedUserNoteId, masterKey)
return ShareNote.removeUserFromSharedNote(userId, testNoteId2, sharedUserNoteId, masterKey)
})
.then(() => {
@@ -162,75 +162,6 @@ Note.test = (userId, masterKey, printResults) => {
return resolve('Test: Complete ---')
})
})
}
//User doesn't have an encrypted note set. Encrypt all notes
Note.encryptEveryNote = (userId, masterKey) => {
return new Promise((resolve, reject) => {
//Select all the user notes
db.promise().query(`
SELECT * FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE salt IS NULL AND user_id = ? AND shared = 0`, [userId])
.then((rows, fields) => {
let foundNotes = rows[0]
console.log('Encrypting user notes ',rows[0].length)
// return resolve(true)
let allTheUpdates = []
let timeoutAdder = 0
foundNotes.forEach(note => {
timeoutAdder += 100
const newUpdate = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Encrypting Note ', note.id)
const created = Math.round((+new Date)/1000)
const salt = cs.createSmallSalt()
const noteText = note.text
const noteTitle = note.title
const snippet = JSON.stringify([noteTitle, noteText.substring(0, 500)])
const noteSnippet = cs.encrypt(masterKey, salt, snippet)
const textObject = JSON.stringify([noteTitle, noteText])
const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise()
.query('UPDATE note_raw_text SET title = ?, text = ?, snippet = ?, salt = ? WHERE id = ?',
[null, encryptedText, noteSnippet, salt, note.note_raw_text_id])
.then(() => {
resolve(true)
})
}, timeoutAdder)
})
allTheUpdates.push(newUpdate)
})
Promise.all(allTheUpdates).then(done => {
console.log('Indexing first 100')
return Note.reindex(userId, masterKey)
}).then(results => {
console.log('Done')
resolve(true)
})
})
})
}
@@ -490,7 +421,8 @@ Note.reindex = (userId, masterKey, removeId = null) => {
Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, hash, masterKey) => {
return new Promise((resolve, reject) => {
const now = Math.round((+new Date)/1000)
// const now = Math.round((+new Date)/1000)
const now = +new Date
let noteSnippet = ''
@@ -691,10 +623,6 @@ Note.delete = (userId, noteId, masterKey = null) => {
// delete tags
return db.promise()
.query('DELETE FROM note_tag WHERE note_tag.note_id = ? AND note_tag.user_id = ?', [noteId,userId])
})
.then((rows, fields) => {
})
.then((rows, fields) => {
@@ -706,6 +634,8 @@ Note.delete = (userId, noteId, masterKey = null) => {
}
})
SocketIo.to(userId).emit('update_counts')
if(masterKey){
//Remove note ID from index
Note.reindex(userId, masterKey, [noteId])
@@ -803,6 +733,7 @@ Note.get = (userId, noteId, masterKey) => {
note.archived,
note.trashed,
note.color,
note.shared,
note.encrypted_share_password_key,
count(distinct attachment.id) as attachment_count,
note.note_raw_text_id as rawTextId,
@@ -845,8 +776,8 @@ Note.get = (userId, noteId, masterKey) => {
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
//Return note data
delete noteData.salt //remove salt from return data
delete noteData.encrypted_share_password_key
// delete noteData.salt //remove salt from return data
// delete noteData.encrypted_share_password_key
noteData.lockedOut = noteLockedOut
resolve(noteData)
@@ -859,17 +790,48 @@ Note.get = (userId, noteId, masterKey) => {
}
//Public note share action -> may not be used
Note.getShared = (noteId) => {
Note.getShared = (noteId, sharedKey) => {
return new Promise((resolve, reject) => {
db.promise()
.query('SELECT text, color FROM note WHERE id = ? AND shared = 1 LIMIT 1', [noteId])
.query(`
SELECT
note_raw_text.text,
note_raw_text.salt,
note_raw_text.updated as updated,
note.id,
note.user_id,
note.created,
note.pinned,
note.archived,
note.trashed,
note.color,
note.shared,
note.encrypted_share_password_key,
note.note_raw_text_id as rawTextId
FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE note.id = ? LIMIT 1`, [noteId])
.then((rows, fields) => {
//Return note data
resolve(rows[0][0])
let noteData = rows[0][0]
const decipheredText = cs.decrypt(sharedKey, noteData.salt, noteData.text)
if(decipheredText == null){
throw new Error('Unable to decropt note text')
}
const success = true
const noteObject = JSON.parse(decipheredText)
const title = noteObject[0]
const text = noteObject[1]
const styleObject = JSON.parse(noteData.color)
return resolve({success, title, text, styleObject})
})
.catch(console.log)
.catch(error => {
resolve({'success':false})
})
})
}

View File

@@ -12,19 +12,19 @@ let ShareNote = module.exports = {}
const crypto = require('crypto')
const cs = require('@helpers/CryptoString')
ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
ShareNote.addUserToSharedNote = (userId, noteId, shareUserId, masterKey) => {
return new Promise((resolve, reject) => {
const Note = require('@models/Note')
const User = require('@models/User')
//generate new random salts and password
const sharedNoteMasterKey = cs.createSmallSalt()
let sharedNoteMasterKey = null
let encryptedSharedKey = null //new key for note encrypted with shared users pubic key
//Current note object
let note = null
let noteObject = null
let publicKey = null
db.promise().query('SELECT id FROM user WHERE id = ?', [shareUserId])
@@ -34,20 +34,16 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
throw new Error('User Does Not Exist')
}
return Note.get(userId, noteId, masterKey)
return ShareNote.migrateToShared(userId, noteId, masterKey)
})
.then( noteObject => {
.then(({note, sharedNoteKey})=> {
if(!noteObject){
throw new Error('Note Not Found')
}
note = noteObject
sharedNoteMasterKey = sharedNoteKey
noteObject = note
return db.promise()
.query('SELECT id FROM note WHERE user_id = ? AND note_raw_text_id = ?', [shareUserId, note.rawTextId])
})
.then((rows, fields) => {
@@ -55,46 +51,6 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
throw new Error('User Already has this note shared with them')
}
//All check pass, proceed with sharing note
return User.getPublicKey(userId)
})
.then( userPublicKey => {
//Get users public key
publicKey = userPublicKey
//
// Modify note to have a shared password, encrypt text with this password
//
const sharedNoteSalt = cs.createSmallSalt()
//Encrypt note text with new password
const textObject = JSON.stringify([note.title, note.text])
const encryptedText = cs.encrypt(sharedNoteMasterKey, sharedNoteSalt, textObject)
//Update note raw text with new data
return db.promise()
.query("UPDATE `application`.`note_raw_text` SET `text` = ?, `salt` = ? WHERE (`id` = ?)",
[encryptedText, sharedNoteSalt, note.rawTextId])
})
.then((rows, fields) => {
//New Encrypted snippet, using new shared password
const sharedNoteSnippetSalt = cs.createSmallSalt()
const snippet = JSON.stringify([note.title, note.text.substring(0, 500)])
const encryptedSnippet = cs.encrypt(sharedNoteMasterKey, sharedNoteSnippetSalt, snippet)
//Encrypt shared password for this user
const encryptedSharedKey = crypto.publicEncrypt(publicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64')
//Update note snippet for current user with public key encoded snippet
return db.promise().query('UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 2 WHERE id = ? AND user_id = ?',
[encryptedSnippet, sharedNoteSnippetSalt, encryptedSharedKey, noteId, userId])
})
.then((rows, fields) => {
return User.getPublicKey(shareUserId)
})
@@ -102,7 +58,7 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
//New Encrypted snippet, using new shared password
const newSnippetSalt = cs.createSmallSalt()
const snippet = JSON.stringify([note.title, note.text.substring(0, 500)])
const snippet = JSON.stringify([noteObject.title, noteObject.text.substring(0, 500)])
const encryptedSnippet = cs.encrypt(sharedNoteMasterKey, newSnippetSalt, snippet)
//Encrypt shared password for this user
@@ -111,7 +67,7 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
//Insert new note for shared user
return db.promise().query(`
INSERT INTO note (user_id, note_raw_text_id, created, color, share_user_id, snippet, snippet_salt, encrypted_share_password_key) VALUES (?,?,?,?,?,?,?,?);
`, [shareUserId, note.rawTextId, note.created, note.color, userId, encryptedSnippet, newSnippetSalt, encryptedSharedKey])
`, [shareUserId, noteObject.rawTextId, noteObject.created, noteObject.color, userId, encryptedSnippet, newSnippetSalt, encryptedSharedKey])
})
.then((rows, fields) => {
@@ -119,7 +75,7 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
const sharedUserNoteId = rows[0]['insertId']
//Emit update count event to user shared with - so they see the note in real time
SocketIo.to(sharedUserNoteId).emit('update_counts')
SocketIo.to(shareUserId).emit('update_counts')
let success = true
return resolve({success, shareUserId, sharedUserNoteId})
@@ -133,7 +89,7 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
})
}
ShareNote.removeUserFromShared = (userId, noteId, shareNoteUserId, masterKey) => {
ShareNote.removeUserFromSharedNote = (userId, noteId, shareNoteUserId, masterKey) => {
return new Promise((resolve, reject) => {
let rawTextId = null
@@ -160,32 +116,9 @@ ShareNote.removeUserFromShared = (userId, noteId, shareNoteUserId, masterKey) =>
//Convert back to normal note if there is only one person with this note
if(rows[0][0]['count'] == 1){
Note.get(userId, noteId, masterKey)
.then(noteObject => {
const salt = cs.createSmallSalt()
const snippetSalt = cs.createSmallSalt()
const snippetObj = JSON.stringify([noteObject.title, noteObject.text.substring(0, 500)])
const snippet = cs.encrypt(masterKey, snippetSalt, snippetObj)
const textObject = JSON.stringify([noteObject.title, noteObject.text])
const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise()
.query(`UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 0 WHERE id = ? AND user_id = ?`,
[snippet, snippetSalt, null, noteId, userId])
.then((r,f) => {
db.promise()
.query('UPDATE note_raw_text SET text = ?, salt = ? WHERE id = ?',
[encryptedText, salt, noteObject.rawTextId])
.then(() => {
return resolve(true)
})
})
return ShareNote.migrateToNormal(userId, noteId, masterKey)
.then(results => {
resolve(true)
})
} else {
@@ -196,9 +129,163 @@ ShareNote.removeUserFromShared = (userId, noteId, shareNoteUserId, masterKey) =>
})
}
// Get users who see a shared note
ShareNote.getUsers = (userId, rawTextId) => {
//Encrypt note with private shared key
ShareNote.migrateToShared = (userId, noteId, masterKey) => {
const User = require('@models/User')
return new Promise((resolve, reject) => {
//generate new random password
const sharedNoteMasterKey = cs.createSmallSalt()
let userPublicKey = null
let userPrivateKey = null
let note = null
User.generateKeypair(userId, masterKey)
.then( ({publicKey, privateKey}) => {
//Get users public key
userPublicKey = publicKey
userPrivateKey = privateKey
return Note.get(userId, noteId, masterKey)
})
.then(noteObject => {
note = noteObject
if(note.shared == 2){
//Note is already shared, decrypt sharedKey
let sharedNoteKey = null
const encryptedShareKey = note.encrypted_share_password_key
if(encryptedShareKey != null){
sharedNoteKey = crypto.privateDecrypt(userPrivateKey,
Buffer.from(encryptedShareKey, 'base64') )
}
return resolve({note, sharedNoteKey})
} else {
//
// Update raw_text to have a shared password, encrypt text with this password
//
const sharedNoteSalt = cs.createSmallSalt()
//Encrypt note text with new password
const textObject = JSON.stringify([note.title, note.text])
const encryptedText = cs.encrypt(sharedNoteMasterKey, sharedNoteSalt, textObject)
//Update note raw text with new data
db.promise()
.query("UPDATE `application`.`note_raw_text` SET `text` = ?, `salt` = ? WHERE (`id` = ?)",
[encryptedText, sharedNoteSalt, note.rawTextId])
.then((rows, fields) => {
//
// Update snippet using new shared password
// + Save shared password (encrypted with public key)
//
const sharedNoteSnippetSalt = cs.createSmallSalt()
const snippet = JSON.stringify([note.title, note.text.substring(0, 500)])
const encryptedSnippet = cs.encrypt(sharedNoteMasterKey, sharedNoteSnippetSalt, snippet)
//Encrypt shared password for this user
const encryptedSharedKey = crypto.publicEncrypt(userPublicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64')
//Update note snippet for current user with public key encoded snippet
return db.promise().query('UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 2 WHERE id = ? AND user_id = ?',
[encryptedSnippet, sharedNoteSnippetSalt, encryptedSharedKey, noteId, userId])
})
.then((rows, fields) => {
return resolve({note, 'sharedNoteKey':sharedNoteMasterKey})
})
}
})
})
}
//Remove private shared key, encrypt note with users master password
ShareNote.migrateToNormal = (userId, noteId, masterKey) => {
return new Promise((resolve, reject) => {
Note.get(userId, noteId, masterKey)
.then(noteObject => {
const salt = cs.createSmallSalt()
const snippetSalt = cs.createSmallSalt()
const snippetObj = JSON.stringify([noteObject.title, noteObject.text.substring(0, 500)])
const snippet = cs.encrypt(masterKey, snippetSalt, snippetObj)
const textObject = JSON.stringify([noteObject.title, noteObject.text])
const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise()
.query(`UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 0 WHERE id = ? AND user_id = ?`,
[snippet, snippetSalt, null, noteId, userId])
.then((r,f) => {
db.promise()
.query('UPDATE note_raw_text SET text = ?, salt = ? WHERE id = ?',
[encryptedText, salt, noteObject.rawTextId])
.then(() => {
return resolve(true)
})
})
})
})
}
ShareNote.decryptSharedKey = (userId, noteId, masterKey) => {
return new Promise((resolve, reject) => {
let userPrivateKey = null
const User = require('@models/User')
User.generateKeypair(userId, masterKey)
.then( ({publicKey, privateKey}) => {
userPrivateKey = privateKey
return Note.get(userId, noteId, masterKey)
})
.then(noteObject => {
//Shared notes use encrypted key - decrypt key then return it.
const encryptedShareKey = noteObject.encrypted_share_password_key
if(encryptedShareKey != null && userPrivateKey != null){
const currentNoteKey = crypto.privateDecrypt(userPrivateKey,
Buffer.from(encryptedShareKey, 'base64') )
return resolve(currentNoteKey)
} else {
return resolve(null)
}
})
})
}
// Get users who see a shared note
ShareNote.getShareInfo = (userId, noteId, rawTextId) => {
return new Promise((resolve, reject) => {
let shareUsers = []
let shareStatus = 0
db.promise()
.query(`
SELECT username, note.id as noteId
@@ -211,47 +298,14 @@ ShareNote.getUsers = (userId, rawTextId) => {
.then((rows, fields) => {
//Return a list of user names
return resolve (rows[0])
shareUsers = rows[0]
return db.promise().query('SELECT shared FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
})
})
}
.then((rows, fields) => {
// Remove a user from a shared note
ShareNote.removeUser = (userId, noteId) => {
return new Promise((resolve, reject) => {
const Note = require('@models/Note')
let rawTextId = null
let removeUserId = null
//note.id = noteId, share_user_id = userId
db.promise()
.query('SELECT note_raw_text_id, user_id FROM note WHERE id = ? AND share_user_id = ?', [noteId, userId])
.then( (rows, fields) => {
rawTextId = rows[0][0]['note_raw_text_id']
removeUserId = rows[0][0]['user_id']
//Delete note entry for other user - remove users access
if(removeUserId && Number.isInteger(removeUserId)){
//Delete this users access to the note
return Note.delete(removeUserId, noteId, masterKey)
} else {
return new Promise((resolve, reject) => { resolve(true) })
}
shareStatus = rows[0][0]['shared']
return resolve({ shareStatus, shareUsers })
})
.then(stuff => {
resolve(true)
})
.catch(error => {
console.log(error)
resolve(false)
})
})
}

View File

@@ -19,15 +19,6 @@ User.login = (username, password) => {
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
.then((rows, fields) => {
// Create New Account
//
if(rows[0].length == 0){
User.create(lowerName, password)
.then( ({token, userId}) => {
return resolve({ token, userId })
})
}
// Login User
//
if(rows[0].length == 1){
@@ -60,6 +51,8 @@ User.login = (username, password) => {
reject('Password does not match database')
}
})
} else {
return reject('Incorrect Username or Password')
}
})
.catch(console.log)
@@ -69,7 +62,7 @@ User.login = (username, password) => {
//Create user account
//Issues login token
User.create = (username, password) => {
User.register = (username, password) => {
//For some reason, username won't get into the promise. But password will @TODO figure this out
const lowerName = username.toLowerCase().trim()
@@ -152,8 +145,8 @@ User.getCounts = (userId) => {
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
SUM(trashed = 1) AS trashedNotes,
SUM(share_user_id IS NULL && trashed = 0) AS totalNotes,
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes,
SUM( (share_user_id != ? && opened IS null && trashed = 0) || (share_user_id != ? && note_raw_text.updated > opened && trashed = 0) ) AS unreadNotes
SUM(share_user_id IS NOT null && opened IS null && trashed = 0) AS youGotMailCount,
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
FROM note
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE user_id = ?`, [userId, userId, userId, userId])
@@ -406,12 +399,12 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
const randomUsername = Math.random().toString(36).substring(2, 15);
const randomPassword = '1'
User.login(testUserName, password)
User.register(testUserName, password)
.then( ({ token, userId }) => {
testUserId = userId
if(printResults) console.log('Test: Create/Login User '+testUserName+' - Pass')
if(printResults) console.log('Test: Register User '+testUserName+' - Pass')
return User.getMasterKey(testUserId, password)
})
@@ -439,6 +432,12 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
//Convert it back to string
if(printResults) console.log(publicDeccryptMessage.toString('utf8'))
return User.login(testUserName, password)
})
.then( ({token, userId}) => {
if(printResults) console.log('Test: Login New User - Pass')
resolve({testUserId, masterKey})
})
})