* Added Much better session Management, key updating and deleting
* Force reload of JS if app numbers dont match * Added cool tag display on side of note * Cleaned up a bunch of code and tweaked little things to be better
This commit is contained in:
parent
d2624628d8
commit
071aaf22cd
@ -23,22 +23,76 @@ export default {
|
|||||||
data: function(){
|
data: function(){
|
||||||
return {
|
return {
|
||||||
// loggedIn:
|
// 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(){
|
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
|
//Puts token into state on page load
|
||||||
let token = localStorage.getItem('loginToken')
|
let token = localStorage.getItem('loginToken')
|
||||||
let username = localStorage.getItem('username')
|
let username = localStorage.getItem('username')
|
||||||
|
|
||||||
// const socket = io({ path:'/socket' });
|
//
|
||||||
|
if(token && token.length > 0){
|
||||||
|
|
||||||
|
//setup username display
|
||||||
|
this.$store.commit('setUsername', username)
|
||||||
|
|
||||||
|
//Set session token on every request if set
|
||||||
|
axios.defaults.headers.common['authorizationtoken'] = token
|
||||||
|
|
||||||
|
//Setup websockets into vue instance
|
||||||
const socket = this.$io
|
const socket = this.$io
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
|
||||||
this.$store.commit('setSocketIoSocket', socket.id)
|
//Put user into personal event room for live note updates, etc
|
||||||
|
|
||||||
this.$io.emit('user_connect', token)
|
this.$io.emit('user_connect', token)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Detect if user is on a mobile browser and set a flag in store
|
//Detect if user is on a mobile browser and set a flag in store
|
||||||
this.$store.commit('detectIsUserOnMobile')
|
this.$store.commit('detectIsUserOnMobile')
|
||||||
@ -49,11 +103,6 @@ export default {
|
|||||||
this.$store.commit('toggleNightMode', themeNumber)
|
this.$store.commit('toggleNightMode', themeNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Put user data into global store on load
|
|
||||||
if(token){
|
|
||||||
this.$store.commit('setLoginToken', {token, username})
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted: function(){
|
mounted: function(){
|
||||||
|
|
||||||
@ -63,6 +112,17 @@ export default {
|
|||||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
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: {
|
computed: {
|
||||||
loggedIn () {
|
loggedIn () {
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
||||||
|
--main-accent: #16ab39;
|
||||||
|
|
||||||
|
/*theme colors */
|
||||||
--body_bg_color: #f5f6f7;
|
--body_bg_color: #f5f6f7;
|
||||||
--small_element_bg_color: #fff;
|
--small_element_bg_color: #fff;
|
||||||
--text_color: #3d3d3d;
|
--text_color: #3d3d3d;
|
||||||
|
@ -248,7 +248,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div v-on:click="reloadPage" class="version-display">
|
<div v-on:click="reloadPage" class="version-display" v-if="version != 0" >
|
||||||
<i :class="`${getVersionIcon()} icon`"></i> {{ version }}
|
<i :class="`${getVersionIcon()} icon`"></i> {{ version }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -267,7 +267,7 @@
|
|||||||
},
|
},
|
||||||
data: function(){
|
data: function(){
|
||||||
return {
|
return {
|
||||||
version: '2.3.4',
|
version: '0',
|
||||||
username: '',
|
username: '',
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
mobile: false,
|
mobile: false,
|
||||||
@ -277,6 +277,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeCreate: function(){
|
beforeCreate: function(){
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted: function(){
|
mounted: function(){
|
||||||
this.mobile = this.$store.getters.getIsUserOnMobile
|
this.mobile = this.$store.getters.getIsUserOnMobile
|
||||||
@ -288,6 +289,7 @@
|
|||||||
|
|
||||||
if(this.loggedIn){
|
if(this.loggedIn){
|
||||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||||
|
this.version = localStorage.getItem('currentVersion')
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -347,11 +349,12 @@
|
|||||||
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
|
||||||
},
|
},
|
||||||
destroyLoginToken() {
|
destroyLoginToken() {
|
||||||
axios.post('/api/user/logout').then( response => {
|
axios.post('/api/user/logout')
|
||||||
|
setTimeout(() => {
|
||||||
this.$bus.$emit('notification', 'Logged Out')
|
this.$bus.$emit('notification', 'Logged Out')
|
||||||
this.$store.commit('destroyLoginToken')
|
this.$store.commit('destroyLoginToken')
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
})
|
}, 200)
|
||||||
},
|
},
|
||||||
toggleNightMode(){
|
toggleNightMode(){
|
||||||
this.$store.commit('toggleNightMode')
|
this.$store.commit('toggleNightMode')
|
||||||
|
@ -98,13 +98,15 @@
|
|||||||
//Login user if we have a valid token
|
//Login user if we have a valid token
|
||||||
if(data && data.token && data.token.length > 0){
|
if(data && data.token && data.token.length > 0){
|
||||||
|
|
||||||
const token = data.token
|
//Set username to local session
|
||||||
const username = this.username
|
this.$store.commit('setUsername', this.username)
|
||||||
|
|
||||||
this.$store.commit('setLoginToken', {token, username})
|
const token = data.token
|
||||||
|
|
||||||
//Setup socket io after user logs in
|
//Setup socket io after user logs in
|
||||||
|
axios.defaults.headers.common['authorizationtoken'] = token
|
||||||
this.$io.emit('user_connect', token)
|
this.$io.emit('user_connect', token)
|
||||||
|
localStorage.setItem('loginToken', token)
|
||||||
|
|
||||||
//Redirect user to notes section after login
|
//Redirect user to notes section after login
|
||||||
this.$router.push('/notes')
|
this.$router.push('/notes')
|
||||||
@ -113,7 +115,7 @@
|
|||||||
register(){
|
register(){
|
||||||
|
|
||||||
if( this.username.length == 0 || this.password.length == 0 ){
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,19 +123,19 @@
|
|||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
|
|
||||||
if(data == false){
|
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)
|
this.finalizeLogin(data)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.$bus.$emit('notification', 'Username already in use')
|
this.$bus.$emit('notification', 'Unable to Sign Up - Username already in use')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
login(){
|
login(){
|
||||||
|
|
||||||
if( this.username.length == 0 || this.password.length == 0 ){
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,13 +143,13 @@
|
|||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
|
|
||||||
if(data == false){
|
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)
|
this.finalizeLogin(data)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.$bus.$emit('notification', 'Incorrect Username or Password')
|
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,13 +170,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- little tags on the side -->
|
<!-- little tags on the side -->
|
||||||
<div class="note-mini-tag-area" v-if="allTags.length > 0" :class="{ 'slide-out-right':(sizeDown == true) }">
|
<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }">
|
||||||
<span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)">
|
<span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)">
|
||||||
|
<i class="tag icon"></i>
|
||||||
{{ tag.text }}
|
{{ tag.text }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="subtle-tag" v-on:click="addTag(tag.text)">
|
<span v-else class="subtle-tag" v-on:click="addTag(tag.text)">
|
||||||
|
<i class="plus icon"></i>
|
||||||
{{ tag.text }}
|
{{ tag.text }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="subtle-tag" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)">
|
||||||
|
<i class="plus icon"></i><i class="green tags icon"></i>Add Tag
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- color picker -->
|
<!-- color picker -->
|
||||||
@ -195,7 +200,7 @@
|
|||||||
/>
|
/>
|
||||||
</side-slide-menu>
|
</side-slide-menu>
|
||||||
|
|
||||||
<side-slide-menu v-if="tags" v-on:close="tags = false" name="tags" :style-object="styleObject">
|
<side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags" :style-object="styleObject">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
|
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
|
||||||
</div>
|
</div>
|
||||||
@ -254,10 +259,10 @@
|
|||||||
|
|
||||||
<!-- Show side shades if user is on desktop only -->
|
<!-- Show side shades if user is on desktop only -->
|
||||||
<div class="full-focus-shade shade1"
|
<div class="full-focus-shade shade1"
|
||||||
:class="{ 'slide-out-left':(sizeDown == true) }"
|
:class="{ 'slide-out-left':sizeDown }"
|
||||||
v-on:click="close()"></div>
|
v-on:click="close()"></div>
|
||||||
<div class="full-focus-shade shade2"
|
<div class="full-focus-shade shade2"
|
||||||
:class="{ 'slide-out-right':(sizeDown == true) }"
|
:class="{ 'slide-out-right':sizeDown }"
|
||||||
v-on:click="close()"></div>
|
v-on:click="close()"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -335,10 +340,6 @@
|
|||||||
diffTextTimeout: null,
|
diffTextTimeout: null,
|
||||||
diffsApplied: null,
|
diffsApplied: null,
|
||||||
|
|
||||||
//Fake Caret position and visibility
|
|
||||||
caretShow: false,
|
|
||||||
caretLeft: null,
|
|
||||||
caretTop: null,
|
|
||||||
//Used to restore caret position
|
//Used to restore caret position
|
||||||
lastRange: null,
|
lastRange: null,
|
||||||
startOffset: 0,
|
startOffset: 0,
|
||||||
@ -541,9 +542,6 @@
|
|||||||
if(!this.$store.getters.getIsUserOnMobile){
|
if(!this.$store.getters.getIsUserOnMobile){
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
this.editor.moveCursorToEnd()
|
this.editor.moveCursorToEnd()
|
||||||
this.caretShow = true
|
|
||||||
this.moveArtificialCaret()
|
|
||||||
|
|
||||||
|
|
||||||
this.fetchNoteTags() //Don't load tags on mobile
|
this.fetchNoteTags() //Don't load tags on mobile
|
||||||
}
|
}
|
||||||
@ -615,14 +613,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.editor.addEventListener('keydown', event => {
|
this.editor.addEventListener('keydown', event => {
|
||||||
setTimeout(() => {
|
|
||||||
if(event.keyCode == 32){
|
|
||||||
this.caretLeft += 3
|
|
||||||
}
|
|
||||||
if(event.keyCode == 8){
|
|
||||||
// this.caretLeft -= 3
|
|
||||||
}
|
|
||||||
}, 10)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//Bind event handlers
|
//Bind event handlers
|
||||||
@ -633,29 +624,10 @@
|
|||||||
|
|
||||||
//Show and hide additional toolbars
|
//Show and hide additional toolbars
|
||||||
this.editor.addEventListener('focus', e => {
|
this.editor.addEventListener('focus', e => {
|
||||||
// this.caretShow = true
|
|
||||||
})
|
})
|
||||||
this.editor.addEventListener('blur', e => {
|
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(){
|
openEditAttachment(){
|
||||||
|
|
||||||
this.$router.push('/attachments/note/'+this.currentNoteId)
|
this.$router.push('/attachments/note/'+this.currentNoteId)
|
||||||
@ -845,7 +817,7 @@
|
|||||||
// clearTimeout(this.editDebounce)
|
// clearTimeout(this.editDebounce)
|
||||||
|
|
||||||
if(this.statusText == 'saving'){
|
if(this.statusText == 'saving'){
|
||||||
return reject(false)
|
return resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Don't save note if its hash doesn't change
|
//Don't save note if its hash doesn't change
|
||||||
@ -1081,38 +1053,45 @@
|
|||||||
|
|
||||||
.note-mini-tag-area {
|
.note-mini-tag-area {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100px;
|
width: 120px;
|
||||||
left: calc(15% - 100px);
|
left: calc(15% - 125px);
|
||||||
top: 46px;
|
top: 46px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 500px;
|
height: calc(100vh - 55px);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
scrollbar-color: transparent transparent;
|
scrollbar-color: transparent transparent;
|
||||||
}
|
}
|
||||||
|
.note-mini-tag-area {
|
||||||
|
scrollbar-width: auto;
|
||||||
|
scrollbar-color: inherit inherit;
|
||||||
|
}
|
||||||
.subtle-tag {
|
.subtle-tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 2px 1px 2px 4px;
|
padding: 1px 1px 1px 5px;
|
||||||
margin: 0 0 2px;
|
margin: 0 0 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top-left-radius: 4px;
|
border-radius: 3px;
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
color: var(--text_color);
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
transition: color ease 0.3s, background ease 0.3s;
|
transition: color ease 0.3s, background ease 0.3s;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
text-transform:capitalize;
|
||||||
}
|
}
|
||||||
.note-mini-tag-area:hover .subtle-tag {
|
.note-mini-tag-area:hover .subtle-tag {
|
||||||
opacity: 1;
|
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) {
|
.note-mini-tag-area:hover .subtle-tag:not(.active-mini-tag) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
color: var(--text_color);
|
color: var(--text_color);
|
||||||
@ -1120,9 +1099,9 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.active-mini-tag {
|
.active-mini-tag {
|
||||||
opacity: 0.6;
|
opacity: 0.7;
|
||||||
background-color: #16ab39;
|
background-color: var(--small_element_bg_color);
|
||||||
color: white;
|
color: var(--text_color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
class="big-text"><p>{{ note.title }}</p></span>
|
class="big-text"><p>{{ note.title }}</p></span>
|
||||||
|
|
||||||
<!-- Sub text display -->
|
<!-- Sub text display -->
|
||||||
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
|
<span v-if="note.subtext.length > 0"
|
||||||
class="small-text"
|
class="small-text"
|
||||||
v-html="note.subtext"></span>
|
v-html="note.subtext"></span>
|
||||||
|
|
||||||
@ -49,15 +49,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- Display highlights from solr results -->
|
|
||||||
<span v-if="note.note_highlights.length > 0" class="term-usage">
|
|
||||||
<span
|
|
||||||
class="usage-row"
|
|
||||||
v-for="highlight in note.note_highlights"
|
|
||||||
:class="{ 'big-text':(highlight <= 100), 'small-text-title':(highlight >= 100) }"
|
|
||||||
v-html="cleanHighlight(highlight)"></span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="titleView" class="single-line-text" @click="cardClicked">
|
<div v-if="titleView" class="single-line-text" @click="cardClicked">
|
||||||
@ -179,12 +170,6 @@
|
|||||||
|
|
||||||
return updated
|
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){
|
splitTags(text){
|
||||||
return text.split(',')
|
return text.split(',')
|
||||||
},
|
},
|
||||||
|
@ -8,8 +8,12 @@
|
|||||||
<div class="ui grid" v-if="shareUsername == null">
|
<div class="ui grid" v-if="shareUsername == null">
|
||||||
|
|
||||||
<div v-if="!isNoteShared" class="sixteen wide column">
|
<div v-if="!isNoteShared" class="sixteen wide column">
|
||||||
<div class="ui button" v-on:click="makeShared()">Enable Shared</div>
|
<div class="ui button" v-on:click="makeShared()">Enable Sharing</div>
|
||||||
<p>Shared notes are different and junk.</p>
|
<ul>
|
||||||
|
<li>Shared notes can be read and edited by you and all shared users.</li>
|
||||||
|
<li>Shared notes can only be shared by the creator of the note.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isNoteShared" class="sixteen wide column">
|
<div v-if="isNoteShared" class="sixteen wide column">
|
||||||
@ -17,14 +21,10 @@
|
|||||||
<div class="ui button" v-on:click="removeShared()">Remove Shared</div>
|
<div class="ui button" v-on:click="removeShared()">Remove Shared</div>
|
||||||
|
|
||||||
<div class="ui button" v-on:click="getSharedUrl()">Get Shareable URL</div>
|
<div class="ui button" v-on:click="getSharedUrl()">Get Shareable URL</div>
|
||||||
|
</div>
|
||||||
<div v-if="sharedUrl.length > 0">
|
<div class="sixteen wide column" v-if="isNoteShared && sharedUrl.length > 0">
|
||||||
|
<p>Public Link - this link can be disabled by turning off sharing</p>
|
||||||
<a target="_blank" :href="sharedUrl">{{ sharedUrl }}</a>
|
<a target="_blank" :href="sharedUrl">{{ sharedUrl }}</a>
|
||||||
<div class="ui input">
|
|
||||||
<input type="text" v-model="sharedUrl">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@
|
|||||||
:data="note"
|
:data="note"
|
||||||
:title-view="titleView"
|
:title-view="titleView"
|
||||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
: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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,30 +6,16 @@ Vue.use(Vuex);
|
|||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
token: null,
|
|
||||||
username: null,
|
username: null,
|
||||||
nightMode: false,
|
nightMode: false,
|
||||||
isUserOnMobile: false,
|
isUserOnMobile: false,
|
||||||
isNoteSettingsOpen: false, //Little note settings pane
|
|
||||||
socket: null,
|
|
||||||
userTotals: null,
|
userTotals: null,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setLoginToken(state, userData){
|
setUsername(state, username){
|
||||||
|
|
||||||
const username = userData.username
|
|
||||||
const token = userData.token
|
|
||||||
|
|
||||||
localStorage.removeItem('loginToken') //We only want one login token per computer
|
|
||||||
localStorage.setItem('loginToken', token)
|
|
||||||
|
|
||||||
localStorage.removeItem('username') //We only want one login token per computer
|
localStorage.removeItem('username') //We only want one login token per computer
|
||||||
localStorage.setItem('username', username)
|
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
|
state.username = username
|
||||||
},
|
},
|
||||||
destroyLoginToken(state){
|
destroyLoginToken(state){
|
||||||
@ -37,8 +23,8 @@ export default new Vuex.Store({
|
|||||||
//Remove login token from local storage and from headers
|
//Remove login token from local storage and from headers
|
||||||
localStorage.removeItem('loginToken')
|
localStorage.removeItem('loginToken')
|
||||||
localStorage.removeItem('username')
|
localStorage.removeItem('username')
|
||||||
|
localStorage.removeItem('currentVersion')
|
||||||
delete axios.defaults.headers.common['authorizationtoken']
|
delete axios.defaults.headers.common['authorizationtoken']
|
||||||
state.token = null
|
|
||||||
state.username = null
|
state.username = null
|
||||||
},
|
},
|
||||||
toggleNightMode(state, pastTheme){
|
toggleNightMode(state, pastTheme){
|
||||||
@ -125,6 +111,20 @@ export default new Vuex.Store({
|
|||||||
//Save all the totals for the user
|
//Save all the totals for the user
|
||||||
state.userTotals = totalsObject
|
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('-------------')
|
// console.log('-------------')
|
||||||
// Object.keys(totalsObject).forEach( key => {
|
// Object.keys(totalsObject).forEach( key => {
|
||||||
// console.log(key + ' -- ' + totalsObject[key])
|
// console.log(key + ' -- ' + totalsObject[key])
|
||||||
@ -135,11 +135,8 @@ export default new Vuex.Store({
|
|||||||
getUsername: state => {
|
getUsername: state => {
|
||||||
return state.username
|
return state.username
|
||||||
},
|
},
|
||||||
getLoginToken: state => {
|
|
||||||
return state.token
|
|
||||||
},
|
|
||||||
getLoggedIn: state => {
|
getLoggedIn: state => {
|
||||||
let weIn = (state.token !== null && state.token != undefined && state.token.length > 0)
|
let weIn = (state.username && state.username.length > 0)
|
||||||
return weIn
|
return weIn
|
||||||
},
|
},
|
||||||
getIsNightMode: state => {
|
getIsNightMode: state => {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,26 +6,33 @@ let Auth = {}
|
|||||||
|
|
||||||
const tokenSecretKey = process.env.JSON_KEY
|
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) => {
|
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')
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
|
|
||||||
//Encrypt Master Password and save it to the server
|
//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 salt = cs.createSmallSalt()
|
||||||
const tempPass = cs.createSmallSalt()
|
const tempPass = cs.createSmallSalt()
|
||||||
const encryptedMasterPass = cs.encrypt(tempPass, salt, masterKey)
|
const encryptedMasterPass = cs.encrypt(tempPass, salt, masterKey)
|
||||||
|
|
||||||
|
//Deactivate all other session keys, they delete after 30 seconds
|
||||||
db.promise().query(
|
db.promise().query('UPDATE user_active_session SET active = 0 WHERE session_id = ?', [sessionId])
|
||||||
|
|
||||||
'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash) VALUES (?,?,?,?,?)',
|
|
||||||
[salt, encryptedMasterPass, created, 1, userHash])
|
|
||||||
.then((r,f) => {
|
.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
|
//Required Data for JWT payload
|
||||||
const tokenPayload = {userId, tempPass, salt}
|
const tokenPayload = {userId, tempPass, sessionNum}
|
||||||
|
|
||||||
//Return token
|
//Return token
|
||||||
const token = jwt.sign(tokenPayload, tokenSecretKey)
|
const token = jwt.sign(tokenPayload, tokenSecretKey)
|
||||||
@ -33,50 +40,85 @@ Auth.createToken = (userId, masterKey, request = null) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Auth.decodeToken = (token, request = null) => {
|
Auth.decodeToken = (token, request = null) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let decodedToken = null
|
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
|
//Decode Json web token
|
||||||
jwt.verify(token, tokenSecretKey, function(err, decoded){
|
jwt.verify(token, tokenSecretKey, function(err, decoded){
|
||||||
if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5 || decoded.salt == undefined || decoded.salt.length < 5){
|
if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5){
|
||||||
return reject('Bad Token')
|
throw new Error('Bad Token')
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedToken = decoded
|
decodedToken = decoded
|
||||||
|
|
||||||
//Lookup session data in database
|
db.promise().query('DELETE from user_active_session WHERE (created < ?) OR (active = false AND last_used < ?)', [twentyDays, thirtySeconds])
|
||||||
return db.promise().query('SELECT * FROM user_active_session WHERE salt = ? LIMIT 1', [decodedToken.salt])
|
|
||||||
})
|
|
||||||
.then((r,f) => {
|
.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) => {
|
||||||
|
|
||||||
|
if(r == undefined || r[0].length == 0){
|
||||||
|
throw new Error('Active Session not found for token')
|
||||||
|
}
|
||||||
|
|
||||||
const row = r[0][0]
|
const row = r[0][0]
|
||||||
if(row == undefined || row.length == 0){
|
|
||||||
return reject(false)
|
// console.log(decodedToken.sessionNum + ' uses -> ' + row.uses)
|
||||||
|
|
||||||
|
if(row.uses <= 0){
|
||||||
|
throw new Error('Token is used up')
|
||||||
}
|
}
|
||||||
|
|
||||||
//Decrypt master key from database
|
//Decrypt master key from lookup
|
||||||
const masterKey = cs.decrypt(decodedToken.tempPass, decodedToken.salt, row.encrypted_master_password)
|
const masterKey = cs.decrypt(decodedToken.tempPass, row.salt, row.encrypted_master_password)
|
||||||
if(masterKey == null){
|
if(masterKey == null){
|
||||||
return reject (false)
|
// console.log('Deleting invalid session')
|
||||||
|
Auth.terminateSession(row.session_id)
|
||||||
|
throw new Error ('Unable to decrypt password for session')
|
||||||
}
|
}
|
||||||
|
|
||||||
const userData = {
|
//Async update DB counts and disable session if needed
|
||||||
userId: decodedToken.userId, masterKey, tokenId: row.id
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
//Async update DB counts
|
//Return token Data
|
||||||
db.promise().query('UPDATE user_active_session SET uses = uses + 1 WHERE salt = ? LIMIT 1', [decodedToken.salt])
|
|
||||||
|
|
||||||
return resolve(userData)
|
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) => {
|
Auth.deletAllLoginKeys = (userId) => {
|
||||||
|
|
||||||
const userHash = cs.hash(String(userId)).toString('base64')
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
@ -86,8 +128,6 @@ Auth.deletAllLoginKeys = (userId) => {
|
|||||||
|
|
||||||
Auth.test = () => {
|
Auth.test = () => {
|
||||||
|
|
||||||
// return Auth.deletAllLoginKeys(testUserId)
|
|
||||||
|
|
||||||
const testUserId = 22
|
const testUserId = 22
|
||||||
const testPass = cs.createSmallSalt()
|
const testPass = cs.createSmallSalt()
|
||||||
Auth.createToken(testUserId, testPass)
|
Auth.createToken(testUserId, testPass)
|
||||||
@ -100,7 +140,6 @@ Auth.test = () => {
|
|||||||
.then(userData => {
|
.then(userData => {
|
||||||
|
|
||||||
console.log('Test: Decrypted key Match -> ' + (testPass == userData.masterKey))
|
console.log('Test: Decrypted key Match -> ' + (testPass == userData.masterKey))
|
||||||
|
|
||||||
return Auth.deletAllLoginKeys(testUserId)
|
return Auth.deletAllLoginKeys(testUserId)
|
||||||
})
|
})
|
||||||
.then(results => {
|
.then(results => {
|
||||||
@ -108,16 +147,6 @@ Auth.test = () => {
|
|||||||
console.log('Test: Remove user Json Web Tokens - Pass')
|
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
|
module.exports = Auth
|
@ -31,6 +31,9 @@ CryptoString.encrypt = (password, salt64, rawText) => {
|
|||||||
//Decrypt base64 string cipher text,
|
//Decrypt base64 string cipher text,
|
||||||
CryptoString.decrypt = (password, salt64, cipherTextString) => {
|
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')
|
let cipherText = Buffer.from(cipherTextString, 'base64')
|
||||||
const salt = Buffer.from(salt64, 'base64')
|
const salt = Buffer.from(salt64, 'base64')
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ ProcessText.deduceNoteTitle = (inTitle, inString) => {
|
|||||||
//Remove inline styles that may be added by editor
|
//Remove inline styles that may be added by editor
|
||||||
// inString = inString.replace(/style=".*?"/g,'')
|
// inString = inString.replace(/style=".*?"/g,'')
|
||||||
|
|
||||||
const tagFreeLength = ProcessText.removeHtml(inString).length
|
// const tagFreeLength = ProcessText.removeHtml(inString).length
|
||||||
|
|
||||||
//
|
//
|
||||||
// Simplified attempt!
|
// Simplified attempt!
|
||||||
@ -80,7 +80,7 @@ ProcessText.deduceNoteTitle = (inTitle, inString) => {
|
|||||||
// if(tagFreeLength > 200){
|
// if(tagFreeLength > 200){
|
||||||
// sub += '... <i class="green caret down icon"></i>'
|
// sub += '... <i class="green caret down icon"></i>'
|
||||||
// }
|
// }
|
||||||
inString += '</end>'
|
// inString += '</end>'
|
||||||
|
|
||||||
return {title, sub}
|
return {title, sub}
|
||||||
|
|
||||||
|
@ -50,13 +50,37 @@ io.on('connection', function(socket){
|
|||||||
socket.on('user_connect', token => {
|
socket.on('user_connect', token => {
|
||||||
Auth.decodeToken(token)
|
Auth.decodeToken(token)
|
||||||
.then(userData => {
|
.then(userData => {
|
||||||
socket.join(userData.id)
|
socket.join(userData.userId)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
//Don't add user to room if they are not logged in
|
//Don't add user to room if they are not logged in
|
||||||
// console.log(error)
|
// 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 => {
|
socket.on('join_room', rawTextId => {
|
||||||
// Join user to rawtextid room when they enter
|
// Join user to rawtextid room when they enter
|
||||||
socket.join(rawTextId)
|
socket.join(rawTextId)
|
||||||
@ -78,11 +102,7 @@ io.on('connection', function(socket){
|
|||||||
//Update users in room count
|
//Update users in room count
|
||||||
io.to(rawTextId).emit('update_user_count', usersInRoom.length)
|
io.to(rawTextId).emit('update_user_count', usersInRoom.length)
|
||||||
|
|
||||||
//Debugging text
|
//Debugging text - prints out notes in limbo
|
||||||
console.log('Note diff object')
|
|
||||||
console.log(noteDiffs)
|
|
||||||
|
|
||||||
|
|
||||||
let noteDiffKeys = Object.keys(noteDiffs)
|
let noteDiffKeys = Object.keys(noteDiffs)
|
||||||
let totalDiffs = 0
|
let totalDiffs = 0
|
||||||
noteDiffKeys.forEach(diffSetKey => {
|
noteDiffKeys.forEach(diffSetKey => {
|
||||||
@ -90,9 +110,11 @@ io.on('connection', function(socket){
|
|||||||
totalDiffs += noteDiffs[diffSetKey].length
|
totalDiffs += noteDiffs[diffSetKey].length
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
//Debugging Text
|
||||||
|
if(noteDiffKeys.length > 0){
|
||||||
console.log('Total notes in limbo -> ', noteDiffKeys.length)
|
console.log('Total notes in limbo -> ', noteDiffKeys.length)
|
||||||
console.log('Total Diffs for all notes -> ', totalDiffs)
|
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
|
//Always null out master key, never allow it set from outside
|
||||||
req.headers.masterKey = null
|
req.headers.masterKey = null
|
||||||
req.headers.tokenId = null
|
req.headers.sessionId = null
|
||||||
|
|
||||||
//auth token set by axios in headers
|
//auth token set by axios in headers
|
||||||
let token = req.headers.authorizationtoken
|
let token = req.headers.authorizationtoken
|
||||||
if(token && token != null && typeof token === 'string'){
|
if(token !== undefined && token.length > 0){
|
||||||
Auth.decodeToken(token, req)
|
Auth.decodeToken(token, req)
|
||||||
.then(userData => {
|
.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.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()
|
next()
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch(error => {
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
|
||||||
res.statusMessage = error //Throw 400 error if token is bad
|
res.statusMessage = error //Throw 400 error if token is bad
|
||||||
res.status(400).end()
|
res.status(400).end()
|
||||||
@ -231,7 +262,7 @@ let UserTest = require('@models/User')
|
|||||||
let NoteTest = require('@models/Note')
|
let NoteTest = require('@models/Note')
|
||||||
let AuthTest = require('@helpers/Auth')
|
let AuthTest = require('@helpers/Auth')
|
||||||
Auth.test()
|
Auth.test()
|
||||||
UserTest.keyPairTest('genMan12', '1', printResults)
|
UserTest.keyPairTest('genMan15', '1', printResults)
|
||||||
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||||
.then( message => {
|
.then( message => {
|
||||||
if(printResults) console.log(message)
|
if(printResults) console.log(message)
|
||||||
|
@ -454,9 +454,12 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encryptedNoteText = ''
|
let encryptedNoteText = ''
|
||||||
//Create encrypted snippet
|
//Create encrypted snippet if its a long note
|
||||||
const snippet = JSON.stringify([noteTitle, noteText.substring(0, 500)])
|
let snippet = ''
|
||||||
|
if(noteText.length > 500){
|
||||||
|
snippet = JSON.stringify([noteTitle, noteText.substring(0, 500)])
|
||||||
noteSnippet = cs.encrypt(masterKey, snippetSalt, snippet)
|
noteSnippet = cs.encrypt(masterKey, snippetSalt, snippet)
|
||||||
|
}
|
||||||
|
|
||||||
//Encrypt note text
|
//Encrypt note text
|
||||||
const textObject = JSON.stringify([noteTitle, noteText])
|
const textObject = JSON.stringify([noteTitle, noteText])
|
||||||
@ -946,8 +949,10 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
|
|||||||
let searchParams = [userId]
|
let searchParams = [userId]
|
||||||
let noteSearchQuery = `
|
let noteSearchQuery = `
|
||||||
SELECT note.id,
|
SELECT note.id,
|
||||||
note.snippet as snippet,
|
note.snippet as snippetText,
|
||||||
note.snippet_salt as salt,
|
note.snippet_salt as snippetSalt,
|
||||||
|
note_raw_text.text as noteText,
|
||||||
|
note_raw_text.salt as noteSalt,
|
||||||
note_raw_text.updated as updated,
|
note_raw_text.updated as updated,
|
||||||
opened,
|
opened,
|
||||||
color,
|
color,
|
||||||
@ -1092,26 +1097,39 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Decrypt note text
|
//Only long notes have snippets, decipher it if present
|
||||||
if(note.snippet && note.salt){
|
let displayTitle = ''
|
||||||
const decipheredText = cs.decrypt(currentNoteKey, note.salt, note.snippet)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decipheredText = cs.decrypt(currentNoteKey, relatedSalt, encryptedText)
|
||||||
const textObject = JSON.parse(decipheredText)
|
const textObject = JSON.parse(decipheredText)
|
||||||
if(textObject != null && textObject.length == 2){
|
if(textObject != null && textObject.length == 2){
|
||||||
note.title = textObject[0]
|
if(textObject[0] && textObject[0] != null && textObject[0].length > 0){
|
||||||
note.text = textObject[1]
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
//Deduce note title
|
|
||||||
const textData = ProcessText.deduceNoteTitle(note.title, note.text)
|
|
||||||
|
|
||||||
note.title = textData.title
|
|
||||||
note.subtext = textData.sub
|
|
||||||
|
|
||||||
//Remove these variables
|
note.title = displayTitle
|
||||||
note.note_highlights = []
|
note.subtext = ProcessText.stripDoubleBlankLines(displayText)
|
||||||
note.attachment_highlights = []
|
|
||||||
note.tag_highlights = []
|
|
||||||
|
|
||||||
//Limit number of attachment thumbs to 4
|
//Limit number of attachment thumbs to 4
|
||||||
if(note.thumbs){
|
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
|
//Clear out note.text before sending it to front end, its being used in title and subtext
|
||||||
delete note.snippet
|
delete note.snippetText
|
||||||
delete note.salt
|
delete note.snippetSalt
|
||||||
|
delete note.noteText
|
||||||
|
delete note.noteSalt
|
||||||
delete note.encrypted_share_password_key
|
delete note.encrypted_share_password_key
|
||||||
|
delete note.text //Passed back as title and subtext
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ User.getCounts = (userId) => {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let countTotals = {}
|
let countTotals = {}
|
||||||
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
|
|
||||||
db.promise().query(
|
db.promise().query(
|
||||||
`SELECT
|
`SELECT
|
||||||
@ -170,8 +171,6 @@ User.getCounts = (userId) => {
|
|||||||
|
|
||||||
Object.assign(countTotals, rows[0][0]) //combine results
|
Object.assign(countTotals, rows[0][0]) //combine results
|
||||||
|
|
||||||
const userHash = cs.hash(String(userId)).toString('base64')
|
|
||||||
|
|
||||||
return db.promise().query(
|
return db.promise().query(
|
||||||
`SELECT count(id) as activeSessions FROM user_active_session WHERE user_hash = ?`, [userHash]
|
`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[key] = count ? count : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
countTotals['currentVersion'] = '3.0.0'
|
||||||
|
|
||||||
resolve(countTotals)
|
resolve(countTotals)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -206,8 +207,9 @@ User.getCounts = (userId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Log out user by deleting login token for that active session
|
//Log out user by deleting login token for that active session
|
||||||
User.logout = (tokenId) => {
|
User.logout = (sessionId) => {
|
||||||
return db.promise().query('DELETE FROM user_active_session WHERE (id = ?)', [tokenId])
|
console.log('Terminate Session -> ', sessionId)
|
||||||
|
return db.promise().query('DELETE FROM user_active_session WHERE (session_id = ?)', [sessionId])
|
||||||
}
|
}
|
||||||
|
|
||||||
User.generateMasterKey = (userId, password) => {
|
User.generateMasterKey = (userId, password) => {
|
||||||
|
@ -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
|
module.exports = router
|
@ -33,7 +33,7 @@ router.post('/login', function (req, res) {
|
|||||||
// Logout User
|
// Logout User
|
||||||
router.post('/logout', function (req, res) {
|
router.post('/logout', function (req, res) {
|
||||||
|
|
||||||
User.logout(req.headers.tokenId)
|
User.logout(req.headers.sessionId)
|
||||||
.then( returnData => {
|
.then( returnData => {
|
||||||
res.send(true)
|
res.send(true)
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user