diff --git a/client/src/App.vue b/client/src/App.vue index 06caf51..7624c89 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -23,22 +23,76 @@ export default { data: function(){ return { // loggedIn: + fetchingInProgress: false, //Prevent start getting token while fetch is in progress + blockUntilNextRequest: false //If token was just renewed, don't fetch more until next request } }, + + //Axios response interceptor + // - Gets new session tokens from server and uses them in app beforeCreate: function(){ + //Before all requests going out + axios.interceptors.request.use( + (config) => { + + //Enable token fetching after another request is made + if(this.blockUntilNextRequest){ + this.fetchingInProgress = false + this.blockUntilNextRequest = false + } + + return config + }, + (error) => { + return Promise.reject(error) + } + ) + + // Add a response interceptor, token can be renewed on every response + axios.interceptors.response.use( + (response) => { + + if(typeof response.headers.remaininguses !== 'undefined'){ + + // console.log(response.headers.remaininguses) + //Look at remaining uses of token, if its less than five, request a new one + if(response.headers.remaininguses < 10 && !this.fetchingInProgress && !this.blockUntilNextRequest){ + this.fetchingInProgress = true + const currentToken = localStorage.getItem('loginToken') + this.$io.emit('renew_session_token', currentToken) + } + } + + return response + }, + (error) => { + return Promise.reject(error) + } + ) + //Puts token into state on page load let token = localStorage.getItem('loginToken') let username = localStorage.getItem('username') - // const socket = io({ path:'/socket' }); - const socket = this.$io - socket.on('connect', () => { + // + if(token && token.length > 0){ - this.$store.commit('setSocketIoSocket', socket.id) + //setup username display + this.$store.commit('setUsername', username) - this.$io.emit('user_connect', token) - }) + //Set session token on every request if set + axios.defaults.headers.common['authorizationtoken'] = token + + //Setup websockets into vue instance + const socket = this.$io + socket.on('connect', () => { + + //Put user into personal event room for live note updates, etc + this.$io.emit('user_connect', token) + }) + } + //Detect if user is on a mobile browser and set a flag in store this.$store.commit('detectIsUserOnMobile') @@ -49,11 +103,6 @@ export default { this.$store.commit('toggleNightMode', themeNumber) } - //Put user data into global store on load - if(token){ - this.$store.commit('setLoginToken', {token, username}) - } - }, mounted: function(){ @@ -63,6 +112,17 @@ export default { this.$store.dispatch('fetchAndUpdateUserTotals') }) + this.$io.on('recievend_new_token', newToken => { + + // console.log('Got a new token') + + axios.defaults.headers.common['authorizationtoken'] = newToken + localStorage.setItem('loginToken', newToken) + + //Disable getting new tokens until next request + this.blockUntilNextRequest = true + }) + }, computed: { loggedIn () { diff --git a/client/src/assets/semantic-helper.css b/client/src/assets/semantic-helper.css index bf28844..a538bf6 100644 --- a/client/src/assets/semantic-helper.css +++ b/client/src/assets/semantic-helper.css @@ -17,6 +17,10 @@ :root { + + --main-accent: #16ab39; + + /*theme colors */ --body_bg_color: #f5f6f7; --small_element_bg_color: #fff; --text_color: #3d3d3d; diff --git a/client/src/components/GlobalSiteMenu.vue b/client/src/components/GlobalSiteMenu.vue index 6a9d3e9..d699082 100644 --- a/client/src/components/GlobalSiteMenu.vue +++ b/client/src/components/GlobalSiteMenu.vue @@ -248,7 +248,7 @@ -
+
{{ version }}
@@ -267,7 +267,7 @@ }, data: function(){ return { - version: '2.3.4', + version: '0', username: '', collapsed: false, mobile: false, @@ -277,6 +277,7 @@ } }, beforeCreate: function(){ + }, mounted: function(){ this.mobile = this.$store.getters.getIsUserOnMobile @@ -288,6 +289,7 @@ if(this.loggedIn){ this.$store.dispatch('fetchAndUpdateUserTotals') + this.version = localStorage.getItem('currentVersion') } }, @@ -347,11 +349,12 @@ .catch(error => { this.$bus.$emit('notification', 'Failed to create note') }) }, destroyLoginToken() { - axios.post('/api/user/logout').then( response => { + axios.post('/api/user/logout') + setTimeout(() => { this.$bus.$emit('notification', 'Logged Out') this.$store.commit('destroyLoginToken') this.$router.push('/') - }) + }, 200) }, toggleNightMode(){ this.$store.commit('toggleNightMode') diff --git a/client/src/components/LoginFormComponent.vue b/client/src/components/LoginFormComponent.vue index 9932e32..86117a2 100644 --- a/client/src/components/LoginFormComponent.vue +++ b/client/src/components/LoginFormComponent.vue @@ -98,13 +98,15 @@ //Login user if we have a valid token if(data && data.token && data.token.length > 0){ - const token = data.token - const username = this.username + //Set username to local session + this.$store.commit('setUsername', this.username) - this.$store.commit('setLoginToken', {token, username}) + const token = data.token //Setup socket io after user logs in + axios.defaults.headers.common['authorizationtoken'] = token this.$io.emit('user_connect', token) + localStorage.setItem('loginToken', token) //Redirect user to notes section after login this.$router.push('/notes') @@ -113,7 +115,7 @@ register(){ if( this.username.length == 0 || this.password.length == 0 ){ - this.$bus.$emit('notification', 'Username and Password Required') + this.$bus.$emit('notification', 'Unable to Sign Up - Username and Password Required') return } @@ -121,19 +123,19 @@ .then(({data}) => { if(data == false){ - this.$bus.$emit('notification', 'Username already in use') + this.$bus.$emit('notification', 'Unable to Sign Up - Username already in use') } this.finalizeLogin(data) }) .catch(error => { - this.$bus.$emit('notification', 'Username already in use') + this.$bus.$emit('notification', 'Unable to Sign Up - Username already in use') }) }, login(){ if( this.username.length == 0 || this.password.length == 0 ){ - this.$bus.$emit('notification', 'Username and Password Required') + this.$bus.$emit('notification', 'Unable to Login - Username and Password Required') return } @@ -141,13 +143,13 @@ .then(({data}) => { if(data == false){ - this.$bus.$emit('notification', 'Incorrect Username or Password') + this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password') } this.finalizeLogin(data) }) .catch(error => { - this.$bus.$emit('notification', 'Incorrect Username or Password') + this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password') }) } } diff --git a/client/src/components/NoteInputPanel.vue b/client/src/components/NoteInputPanel.vue index 204016a..4b54b09 100644 --- a/client/src/components/NoteInputPanel.vue +++ b/client/src/components/NoteInputPanel.vue @@ -170,13 +170,18 @@
-
+
+ {{ tag.text }} + {{ tag.text }} + + Add Tag +
@@ -195,7 +200,7 @@ /> - +
@@ -254,10 +259,10 @@
@@ -335,10 +340,6 @@ diffTextTimeout: null, diffsApplied: null, - //Fake Caret position and visibility - caretShow: false, - caretLeft: null, - caretTop: null, //Used to restore caret position lastRange: null, startOffset: 0, @@ -541,9 +542,6 @@ if(!this.$store.getters.getIsUserOnMobile){ this.editor.focus() this.editor.moveCursorToEnd() - this.caretShow = true - this.moveArtificialCaret() - this.fetchNoteTags() //Don't load tags on mobile } @@ -615,14 +613,7 @@ }) this.editor.addEventListener('keydown', event => { - setTimeout(() => { - if(event.keyCode == 32){ - this.caretLeft += 3 - } - if(event.keyCode == 8){ - // this.caretLeft -= 3 - } - }, 10) + }) //Bind event handlers @@ -633,29 +624,10 @@ //Show and hide additional toolbars this.editor.addEventListener('focus', e => { - // this.caretShow = true }) this.editor.addEventListener('blur', e => { - // this.caretShow = false }) }, - moveArtificialCaret(rect = null){ - - //Lets not use the artificial caret for now - return - - //If rect isn't present, grab by selection - if(!rect || rect.left == 0){ //Left should always be greater than 0, because of a margin - rect = this.editor.getCursorPosition() - //Another way to get range - // window.getSelection().getRangeAt(0) - } - - const textArea = document.getElementById('text-box-container').getBoundingClientRect() - - this.caretLeft = (rect.left - textArea.left - 1) - this.caretTop = (rect.top - textArea.top - 1 ) - }, openEditAttachment(){ this.$router.push('/attachments/note/'+this.currentNoteId) @@ -845,7 +817,7 @@ // clearTimeout(this.editDebounce) if(this.statusText == 'saving'){ - return reject(false) + return resolve(true) } //Don't save note if its hash doesn't change @@ -1081,38 +1053,45 @@ .note-mini-tag-area { position: fixed; - width: 100px; - left: calc(15% - 100px); + width: 120px; + left: calc(15% - 125px); top: 46px; bottom: 0; - height: 500px; + height: calc(100vh - 55px); z-index: 1000; overflow-y: scroll; scrollbar-width: none; scrollbar-color: transparent transparent; } + .note-mini-tag-area { + scrollbar-width: auto; + scrollbar-color: inherit inherit; + } .subtle-tag { display: inline-block; width: 100%; - padding: 2px 1px 2px 4px; - margin: 0 0 2px; + padding: 1px 1px 1px 5px; + margin: 0 0 0; border: 1px solid transparent; border-right: none; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - color: var(--text_color); + border-radius: 3px; background-color: transparent; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: color ease 0.3s, background ease 0.3s; - font-size: 12px; + font-size: 11px; cursor: pointer; opacity: 0; + text-transform:capitalize; } .note-mini-tag-area:hover .subtle-tag { opacity: 1; - } + } + .note-mini-tag-area:hover .active-mini-tag { + background-color: var(--main-accent); + color: white; + } .note-mini-tag-area:hover .subtle-tag:not(.active-mini-tag) { border-right: none; color: var(--text_color); @@ -1120,9 +1099,9 @@ opacity: 1; } .active-mini-tag { - opacity: 0.6; - background-color: #16ab39; - color: white; + opacity: 0.7; + background-color: var(--small_element_bg_color); + color: var(--text_color) } diff --git a/client/src/components/NoteTitleDisplayCard.vue b/client/src/components/NoteTitleDisplayCard.vue index 7afe537..a011c0d 100644 --- a/client/src/components/NoteTitleDisplayCard.vue +++ b/client/src/components/NoteTitleDisplayCard.vue @@ -32,7 +32,7 @@ class="big-text">

{{ note.title }}

- @@ -49,15 +49,6 @@ - - - - -
@@ -179,12 +170,6 @@ return updated }, - isShowingSearchResults(){ - if(this.note.note_highlights.length > 0 || this.note.attachment_highlights.length > 0 || this.note.tag_highlights.length > 0){ - return true - } - return false - }, splitTags(text){ return text.split(',') }, diff --git a/client/src/components/ShareNoteComponent.vue b/client/src/components/ShareNoteComponent.vue index 6fb61c1..65a1eea 100644 --- a/client/src/components/ShareNoteComponent.vue +++ b/client/src/components/ShareNoteComponent.vue @@ -8,8 +8,12 @@
-
Enable Shared
-

Shared notes are different and junk.

+
Enable Sharing
+
    +
  • Shared notes can be read and edited by you and all shared users.
  • +
  • Shared notes can only be shared by the creator of the note.
  • +
+
@@ -17,14 +21,10 @@
Remove Shared
Get Shareable URL
- -
- {{ sharedUrl }} -
- -
-
- +
+
+

Public Link - this link can be disabled by turning off sharing

+ {{ sharedUrl }}
diff --git a/client/src/pages/NotesPage.vue b/client/src/pages/NotesPage.vue index 5595bb3..08855ef 100644 --- a/client/src/pages/NotesPage.vue +++ b/client/src/pages/NotesPage.vue @@ -117,7 +117,7 @@ :data="note" :title-view="titleView" :currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)" - :key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated" + :key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated" />
diff --git a/client/src/stores/mainStore.js b/client/src/stores/mainStore.js index 0a697df..4ea2105 100644 --- a/client/src/stores/mainStore.js +++ b/client/src/stores/mainStore.js @@ -6,30 +6,16 @@ Vue.use(Vuex); export default new Vuex.Store({ state: { - token: null, username: null, nightMode: false, isUserOnMobile: false, - isNoteSettingsOpen: false, //Little note settings pane - socket: null, userTotals: null, }, mutations: { - setLoginToken(state, userData){ - - const username = userData.username - const token = userData.token - - localStorage.removeItem('loginToken') //We only want one login token per computer - localStorage.setItem('loginToken', token) + setUsername(state, username){ localStorage.removeItem('username') //We only want one login token per computer localStorage.setItem('username', username) - - //Set default token to axios, every request will have header - axios.defaults.headers.common['authorizationtoken'] = token - - state.token = token state.username = username }, destroyLoginToken(state){ @@ -37,8 +23,8 @@ export default new Vuex.Store({ //Remove login token from local storage and from headers localStorage.removeItem('loginToken') localStorage.removeItem('username') + localStorage.removeItem('currentVersion') delete axios.defaults.headers.common['authorizationtoken'] - state.token = null state.username = null }, toggleNightMode(state, pastTheme){ @@ -125,6 +111,20 @@ export default new Vuex.Store({ //Save all the totals for the user state.userTotals = totalsObject + //Set computer version from server + const currentVersion = localStorage.getItem('currentVersion') + if(currentVersion == null){ + localStorage.setItem('currentVersion', totalsObject.currentVersion) + return + } + + //If version is already set and it doesn't match the server, reload app + if(currentVersion != totalsObject.currentVersion){ + localStorage.setItem('currentVersion', totalsObject.currentVersion) + location.reload(true) + } + + // console.log('-------------') // Object.keys(totalsObject).forEach( key => { // console.log(key + ' -- ' + totalsObject[key]) @@ -135,11 +135,8 @@ export default new Vuex.Store({ getUsername: state => { return state.username }, - getLoginToken: state => { - return state.token - }, getLoggedIn: state => { - let weIn = (state.token !== null && state.token != undefined && state.token.length > 0) + let weIn = (state.username && state.username.length > 0) return weIn }, getIsNightMode: state => { diff --git a/configs/nginx/default b/configs/nginx/default deleted file mode 100644 index 336a3bd..0000000 --- a/configs/nginx/default +++ /dev/null @@ -1,38 +0,0 @@ -## -# -# This is just a mock config file, describing what is needed to run the app -# The app currently only needs two paths / and /api -# -## - -# -# This is needed to define any ports the app may use from node -# -upstream expressapp { - server 127.0.0.1:3000; - keepalive 8; -} - -server { - - # - # Needed to server up static, compiled JS files and index.html - # - location / { - autoindex on; - } - - # - # define the api route to connect to the backend and serve up static files - # - location /api { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - - proxy_pass http://expressapp; - proxy_redirect off; - } - -} \ No newline at end of file diff --git a/server/helpers/Auth.js b/server/helpers/Auth.js index d9cfc09..0038bac 100644 --- a/server/helpers/Auth.js +++ b/server/helpers/Auth.js @@ -6,26 +6,33 @@ let Auth = {} const tokenSecretKey = process.env.JSON_KEY -Auth.createToken = (userId, masterKey, request = null) => { +Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { return new Promise((resolve, reject) => { - const created = Math.floor(+new Date/1000) + const created = pastCreatedDate ? pastCreatedDate : Math.floor(+new Date/1000) const userHash = cs.hash(String(userId)).toString('base64') //Encrypt Master Password and save it to the server + const sessionId = pastId ? pastId : cs.createSmallSalt().slice(0,9) //Use existing session id const salt = cs.createSmallSalt() const tempPass = cs.createSmallSalt() const encryptedMasterPass = cs.encrypt(tempPass, salt, masterKey) - - db.promise().query( - - 'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash) VALUES (?,?,?,?,?)', - [salt, encryptedMasterPass, created, 1, userHash]) + //Deactivate all other session keys, they delete after 30 seconds + db.promise().query('UPDATE user_active_session SET active = 0 WHERE session_id = ?', [sessionId]) .then((r,f) => { + return db.promise().query( + 'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', + [salt, encryptedMasterPass, created, 40, userHash, sessionId]) + + }) + .then((r,f) => { + + const sessionNum = r[0].insertId + //Required Data for JWT payload - const tokenPayload = {userId, tempPass, salt} + const tokenPayload = {userId, tempPass, sessionNum} //Return token const token = jwt.sign(tokenPayload, tokenSecretKey) @@ -33,50 +40,85 @@ Auth.createToken = (userId, masterKey, request = null) => { }) }) } + Auth.decodeToken = (token, request = null) => { return new Promise((resolve, reject) => { let decodedToken = null + //Delete all tokens older than 20 days before continuing or inacive and older than 1 minute + const now = (Math.floor((+new Date)/1000)) + const twentyDays = (Math.floor((+new Date)/1000)) - (86400 * 20) + const thirtySeconds = (Math.floor((+new Date)/1000)) - (30) + //Decode Json web token - jwt.verify(token, tokenSecretKey, function(err, decoded){ - if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5 || decoded.salt == undefined || decoded.salt.length < 5){ - return reject('Bad Token') - } + jwt.verify(token, tokenSecretKey, function(err, decoded){ + if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5){ + throw new Error('Bad Token') + } - decodedToken = decoded + decodedToken = decoded - //Lookup session data in database - return db.promise().query('SELECT * FROM user_active_session WHERE salt = ? LIMIT 1', [decodedToken.salt]) - }) - .then((r,f) => { + db.promise().query('DELETE from user_active_session WHERE (created < ?) OR (active = false AND last_used < ?)', [twentyDays, thirtySeconds]) + .then((r,f) => { + + //Lookup session data in database + db.promise().query('SELECT * FROM user_active_session WHERE id = ? LIMIT 1', [decodedToken.sessionNum]) + .then((r,f) => { - const row = r[0][0] - if(row == undefined || row.length == 0){ - return reject(false) - } + if(r == undefined || r[0].length == 0){ + throw new Error('Active Session not found for token') + } - //Decrypt master key from database - const masterKey = cs.decrypt(decodedToken.tempPass, decodedToken.salt, row.encrypted_master_password) - if(masterKey == null){ - return reject (false) - } + const row = r[0][0] - const userData = { - userId: decodedToken.userId, masterKey, tokenId: row.id - } + // console.log(decodedToken.sessionNum + ' uses -> ' + row.uses) - //Async update DB counts - db.promise().query('UPDATE user_active_session SET uses = uses + 1 WHERE salt = ? LIMIT 1', [decodedToken.salt]) + if(row.uses <= 0){ + throw new Error('Token is used up') + } - return resolve(userData) + //Decrypt master key from lookup + const masterKey = cs.decrypt(decodedToken.tempPass, row.salt, row.encrypted_master_password) + if(masterKey == null){ + // console.log('Deleting invalid session') + Auth.terminateSession(row.session_id) + throw new Error ('Unable to decrypt password for session') + } + //Async update DB counts and disable session if needed + db.promise().query('UPDATE user_active_session SET uses = uses -1, last_used = ? WHERE id = ? LIMIT 1', [now, decodedToken.sessionNum]) + .then((r,f) => { + + let userData = { + 'userId': decodedToken.userId, + 'masterKey': masterKey, + 'sessionId': row.session_id, + 'created': row.created, + 'remainingUses':(row.uses--), + 'active': row.active + } + + //Return token Data + return resolve(userData) + + }) + }) + .catch(error => { + //Token errors result in having sessions deleted + // console.log('-- Auth Token Error --') + // console.log(error) + reject(error) + }) + }) }) }) } -Auth.reissueToken = () => { - //If token has more than 200 uses, renew it + +Auth.terminateSession = (sessionId) => { + return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId]) } + Auth.deletAllLoginKeys = (userId) => { const userHash = cs.hash(String(userId)).toString('base64') @@ -86,8 +128,6 @@ Auth.deletAllLoginKeys = (userId) => { Auth.test = () => { - // return Auth.deletAllLoginKeys(testUserId) - const testUserId = 22 const testPass = cs.createSmallSalt() Auth.createToken(testUserId, testPass) @@ -100,7 +140,6 @@ Auth.test = () => { .then(userData => { console.log('Test: Decrypted key Match -> ' + (testPass == userData.masterKey)) - return Auth.deletAllLoginKeys(testUserId) }) .then(results => { @@ -108,16 +147,6 @@ Auth.test = () => { console.log('Test: Remove user Json Web Tokens - Pass') }) - - //create token with userId and master key - // Auth.createToken() - - //Thirt days ago - // const thirtyDays = (Math.floor((+new Date)/1000)) - (86400 * 30) - // const created = Math.floor(decoded.date/1000) - // if(created < thirtyDays){ - // return reject('Token Expired') - // } } module.exports = Auth \ No newline at end of file diff --git a/server/helpers/CryptoString.js b/server/helpers/CryptoString.js index ea50503..e80635b 100644 --- a/server/helpers/CryptoString.js +++ b/server/helpers/CryptoString.js @@ -31,6 +31,9 @@ CryptoString.encrypt = (password, salt64, rawText) => { //Decrypt base64 string cipher text, CryptoString.decrypt = (password, salt64, cipherTextString) => { + if(!password || !salt64 || !cipherTextString){ return '' } + if(password.length == 0 || salt64.length == 0 || cipherTextString == 0){ return '' } + let cipherText = Buffer.from(cipherTextString, 'base64') const salt = Buffer.from(salt64, 'base64') diff --git a/server/helpers/ProcessText.js b/server/helpers/ProcessText.js index 9f63b80..f071080 100644 --- a/server/helpers/ProcessText.js +++ b/server/helpers/ProcessText.js @@ -69,7 +69,7 @@ ProcessText.deduceNoteTitle = (inTitle, inString) => { //Remove inline styles that may be added by editor // inString = inString.replace(/style=".*?"/g,'') - const tagFreeLength = ProcessText.removeHtml(inString).length + // const tagFreeLength = ProcessText.removeHtml(inString).length // // Simplified attempt! @@ -80,7 +80,7 @@ ProcessText.deduceNoteTitle = (inTitle, inString) => { // if(tagFreeLength > 200){ // sub += '... ' // } - inString += '' + // inString += '' return {title, sub} diff --git a/server/index.js b/server/index.js index 118dbd5..52ee28d 100644 --- a/server/index.js +++ b/server/index.js @@ -50,13 +50,37 @@ io.on('connection', function(socket){ socket.on('user_connect', token => { Auth.decodeToken(token) .then(userData => { - socket.join(userData.id) + socket.join(userData.userId) }).catch(error => { //Don't add user to room if they are not logged in // console.log(error) }) }) + //Renew Session tokens when users request a new one + socket.on('renew_session_token', token => { + + //Decode the token they currently have + Auth.decodeToken(token) + .then(userData => { + + console.log('Is active -> ', userData.active) + + if(userData.active == 1){ + //Create a new one using credentials and session keys from current + Auth.createToken(userData.userId, userData.masterKey, userData.sessionId, userData.created) + .then(newToken => { + + //Emit new token only to user on socket + socket.emit('recievend_new_token', newToken) + }) + } else { + //Attempting to reactivate disabled session, kills it all + Auth.terminateSession(userData.sessionId) + } + }) + }) + socket.on('join_room', rawTextId => { // Join user to rawtextid room when they enter socket.join(rawTextId) @@ -78,11 +102,7 @@ io.on('connection', function(socket){ //Update users in room count io.to(rawTextId).emit('update_user_count', usersInRoom.length) - //Debugging text - console.log('Note diff object') - console.log(noteDiffs) - - + //Debugging text - prints out notes in limbo let noteDiffKeys = Object.keys(noteDiffs) let totalDiffs = 0 noteDiffKeys.forEach(diffSetKey => { @@ -90,9 +110,11 @@ io.on('connection', function(socket){ totalDiffs += noteDiffs[diffSetKey].length } }) - - console.log('Total notes in limbo -> ', noteDiffKeys.length) - console.log('Total Diffs for all notes -> ', totalDiffs) + //Debugging Text + if(noteDiffKeys.length > 0){ + console.log('Total notes in limbo -> ', noteDiffKeys.length) + console.log('Total Diffs for all notes -> ', totalDiffs) + } } }) @@ -203,18 +225,27 @@ app.use(function(req, res, next){ //Always null out master key, never allow it set from outside req.headers.masterKey = null - req.headers.tokenId = null + req.headers.sessionId = null //auth token set by axios in headers let token = req.headers.authorizationtoken - if(token && token != null && typeof token === 'string'){ + if(token !== undefined && token.length > 0){ Auth.decodeToken(token, req) .then(userData => { - req.headers.userId = userData.userId //Update headers for the rest of the application + + //Update headers for the rest of the application + req.headers.userId = userData.userId req.headers.masterKey = userData.masterKey - req.headers.tokenId = userData.tokenId + req.headers.sessionId = userData.sessionId + + //Tell front end remaining uses on current token + res.set('remainingUses', userData.remainingUses) + next() - }).catch(error => { + }) + .catch(error => { + + console.log(error) res.statusMessage = error //Throw 400 error if token is bad res.status(400).end() @@ -231,7 +262,7 @@ let UserTest = require('@models/User') let NoteTest = require('@models/Note') let AuthTest = require('@helpers/Auth') Auth.test() -UserTest.keyPairTest('genMan12', '1', printResults) +UserTest.keyPairTest('genMan15', '1', printResults) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults)) .then( message => { if(printResults) console.log(message) diff --git a/server/models/Note.js b/server/models/Note.js index 6e15a9d..f23f5e3 100644 --- a/server/models/Note.js +++ b/server/models/Note.js @@ -454,9 +454,12 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has } let encryptedNoteText = '' - //Create encrypted snippet - const snippet = JSON.stringify([noteTitle, noteText.substring(0, 500)]) - noteSnippet = cs.encrypt(masterKey, snippetSalt, snippet) + //Create encrypted snippet if its a long note + let snippet = '' + if(noteText.length > 500){ + snippet = JSON.stringify([noteTitle, noteText.substring(0, 500)]) + noteSnippet = cs.encrypt(masterKey, snippetSalt, snippet) + } //Encrypt note text const textObject = JSON.stringify([noteTitle, noteText]) @@ -946,9 +949,11 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => { let searchParams = [userId] let noteSearchQuery = ` SELECT note.id, - note.snippet as snippet, - note.snippet_salt as salt, - note_raw_text.updated as updated, + note.snippet as snippetText, + note.snippet_salt as snippetSalt, + note_raw_text.text as noteText, + note_raw_text.salt as noteSalt, + note_raw_text.updated as updated, opened, color, count(distinct note_tag.id) as tag_count, @@ -1092,26 +1097,39 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => { } - //Decrypt note text - if(note.snippet && note.salt){ - const decipheredText = cs.decrypt(currentNoteKey, note.salt, note.snippet) - const textObject = JSON.parse(decipheredText) - if(textObject != null && textObject.length == 2){ - note.title = textObject[0] - note.text = textObject[1] - } + //Only long notes have snippets, decipher it if present + let displayTitle = '' + let displayText = '' + + let encryptedText = note.noteText + let relatedSalt = note.noteSalt + + //Default to note text, use snippet if set + if(note.snippetSalt && note.snippetText && note.snippetSalt.length > 0 && note.snippetText.length > 0){ + encryptedText = note.snippetText + relatedSalt = note.snippetSalt } - //Deduce note title - const textData = ProcessText.deduceNoteTitle(note.title, note.text) - - note.title = textData.title - note.subtext = textData.sub + try { + const decipheredText = cs.decrypt(currentNoteKey, relatedSalt, encryptedText) + const textObject = JSON.parse(decipheredText) + if(textObject != null && textObject.length == 2){ + if(textObject[0] && textObject[0] != null && textObject[0].length > 0){ + displayTitle = textObject[0] + } + if(textObject[1] && textObject[1] != null && textObject[1].length > 0){ + displayText = textObject[1] + } + } + } catch(err) { + console.log('Error opening note id -> ', note.id) + console.log(err) + } - //Remove these variables - note.note_highlights = [] - note.attachment_highlights = [] - note.tag_highlights = [] + + + note.title = displayTitle + note.subtext = ProcessText.stripDoubleBlankLines(displayText) //Limit number of attachment thumbs to 4 if(note.thumbs){ @@ -1123,9 +1141,12 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => { } //Clear out note.text before sending it to front end, its being used in title and subtext - delete note.snippet - delete note.salt + delete note.snippetText + delete note.snippetSalt + delete note.noteText + delete note.noteSalt delete note.encrypted_share_password_key + delete note.text //Passed back as title and subtext }) diff --git a/server/models/User.js b/server/models/User.js index 44f1574..c2da73d 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -143,6 +143,7 @@ User.getCounts = (userId) => { return new Promise((resolve, reject) => { let countTotals = {} + const userHash = cs.hash(String(userId)).toString('base64') db.promise().query( `SELECT @@ -169,8 +170,6 @@ User.getCounts = (userId) => { .then( (rows, fields) => { Object.assign(countTotals, rows[0][0]) //combine results - - const userHash = cs.hash(String(userId)).toString('base64') return db.promise().query( `SELECT count(id) as activeSessions FROM user_active_session WHERE user_hash = ?`, [userHash] @@ -199,6 +198,8 @@ User.getCounts = (userId) => { countTotals[key] = count ? count : 0 }) + countTotals['currentVersion'] = '3.0.0' + resolve(countTotals) }) @@ -206,8 +207,9 @@ User.getCounts = (userId) => { } //Log out user by deleting login token for that active session -User.logout = (tokenId) => { - return db.promise().query('DELETE FROM user_active_session WHERE (id = ?)', [tokenId]) +User.logout = (sessionId) => { + console.log('Terminate Session -> ', sessionId) + return db.promise().query('DELETE FROM user_active_session WHERE (session_id = ?)', [sessionId]) } User.generateMasterKey = (userId, password) => { diff --git a/server/routes/noteController.js b/server/routes/noteController.js index b6093de..b458e92 100644 --- a/server/routes/noteController.js +++ b/server/routes/noteController.js @@ -136,19 +136,4 @@ router.post('/disableshare', function (req, res) { }) - - -// -// Testing Action -// -//Reindex all Note. Not a very good function, not public -router.get('/reindex5yu43prchuj903mrc', function (req, res) { - - Note.migrateNoteTextToNewTable().then(status => { - return res.send(status) - }) - -}) - - module.exports = router \ No newline at end of file diff --git a/server/routes/userController.js b/server/routes/userController.js index 6102cac..b68b05a 100644 --- a/server/routes/userController.js +++ b/server/routes/userController.js @@ -33,7 +33,7 @@ router.post('/login', function (req, res) { // Logout User router.post('/logout', function (req, res) { - User.logout(req.headers.tokenId) + User.logout(req.headers.sessionId) .then( returnData => { res.send(true) })