* Added new token system to add more security to logins
* Added simple tag editing from note page
This commit is contained in:
parent
d349fb8328
commit
56d4664d0d
@ -347,9 +347,11 @@
|
|||||||
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
|
||||||
},
|
},
|
||||||
destroyLoginToken() {
|
destroyLoginToken() {
|
||||||
this.$bus.$emit('notification', 'Logged Out')
|
axios.post('/api/user/logout').then( response => {
|
||||||
this.$store.commit('destroyLoginToken')
|
this.$bus.$emit('notification', 'Logged Out')
|
||||||
this.$router.push('/')
|
this.$store.commit('destroyLoginToken')
|
||||||
|
this.$router.push('/')
|
||||||
|
})
|
||||||
},
|
},
|
||||||
toggleNightMode(){
|
toggleNightMode(){
|
||||||
this.$store.commit('toggleNightMode')
|
this.$store.commit('toggleNightMode')
|
||||||
|
@ -165,12 +165,20 @@
|
|||||||
<!-- Squire Box -->
|
<!-- Squire Box -->
|
||||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||||
|
|
||||||
<!-- <div v-if="caretShow" class="artificial-caret" :style="{ 'top':caretTop+'px', 'left':caretLeft+'px' }"></div> -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- little tags on the side -->
|
||||||
|
<div class="note-mini-tag-area" v-if="allTags.length > 0" :class="{ 'slide-out-right':(sizeDown == true) }">
|
||||||
|
<span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)">
|
||||||
|
{{ tag.text }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="subtle-tag" v-on:click="addTag(tag.text)">
|
||||||
|
{{ tag.text }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- color picker -->
|
<!-- color picker -->
|
||||||
<color-tooltip
|
<color-tooltip
|
||||||
v-if="colorpicker"
|
v-if="colorpicker"
|
||||||
@ -334,6 +342,10 @@
|
|||||||
//Used to restore caret position
|
//Used to restore caret position
|
||||||
lastRange: null,
|
lastRange: null,
|
||||||
startOffset: 0,
|
startOffset: 0,
|
||||||
|
|
||||||
|
//Tag Display
|
||||||
|
allTags: [],
|
||||||
|
noteTags: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -449,6 +461,77 @@
|
|||||||
|
|
||||||
}, totalTime + 40)
|
}, totalTime + 40)
|
||||||
},
|
},
|
||||||
|
removeTag(tagId){
|
||||||
|
|
||||||
|
this.allTags = []
|
||||||
|
let entryId = 0
|
||||||
|
|
||||||
|
//Find fucking note tag for removal
|
||||||
|
this.noteTags.forEach(noteTag => {
|
||||||
|
if(noteTag['tagId'] == tagId){
|
||||||
|
entryId = noteTag['entryId']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let postData = {
|
||||||
|
'tagId':entryId,
|
||||||
|
'noteId':this.noteid
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/api/tag/removefromnote', postData)
|
||||||
|
.then(response => {
|
||||||
|
this.fetchNoteTags()
|
||||||
|
})
|
||||||
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Remove Tag') })
|
||||||
|
},
|
||||||
|
addTag(tagText){
|
||||||
|
|
||||||
|
this.allTags = []
|
||||||
|
|
||||||
|
let postData = {
|
||||||
|
'tagText':tagText,
|
||||||
|
'noteId':this.noteid
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/api/tag/addtonote', postData)
|
||||||
|
.then(response => {
|
||||||
|
this.fetchNoteTags()
|
||||||
|
})
|
||||||
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Add Tag') })
|
||||||
|
},
|
||||||
|
fetchNoteTags(){
|
||||||
|
axios.post('/api/tag/get', {'noteId': this.noteid})
|
||||||
|
.then(({data}) => {
|
||||||
|
this.allTags = data.allTags
|
||||||
|
this.noteTags = data.noteTagIds
|
||||||
|
|
||||||
|
//Stick used tags at top.
|
||||||
|
if(this.noteTags.length > 0){
|
||||||
|
|
||||||
|
let frontTags = []
|
||||||
|
|
||||||
|
for (var i = this.allTags.length - 1; i >= 0; i--) {
|
||||||
|
this.noteTags.forEach(noteTag => {
|
||||||
|
if(this.allTags[i]['id'] == noteTag['tagId']){
|
||||||
|
frontTags.push(this.allTags[i])
|
||||||
|
this.allTags.splice(i,1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allTags.unshift(...frontTags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
isTagOnNote(id){
|
||||||
|
for (let i = 0; i < this.noteTags.length; i++) {
|
||||||
|
const current = this.noteTags[i]
|
||||||
|
if(current && current['tagId'] == id){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
initSquire(){
|
initSquire(){
|
||||||
|
|
||||||
//Set up squire and load note text
|
//Set up squire and load note text
|
||||||
@ -460,6 +543,9 @@
|
|||||||
this.editor.moveCursorToEnd()
|
this.editor.moveCursorToEnd()
|
||||||
this.caretShow = true
|
this.caretShow = true
|
||||||
this.moveArtificialCaret()
|
this.moveArtificialCaret()
|
||||||
|
|
||||||
|
|
||||||
|
this.fetchNoteTags() //Don't load tags on mobile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -474,9 +560,6 @@
|
|||||||
this.lastRange = e.range
|
this.lastRange = e.range
|
||||||
this.startOffset = parseInt(e.range.startOffset)
|
this.startOffset = parseInt(e.range.startOffset)
|
||||||
return
|
return
|
||||||
|
|
||||||
// const rect = e.range.getBoundingClientRect()
|
|
||||||
// this.moveArtificialCaret(rect)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//Change button states on editor when element is active
|
//Change button states on editor when element is active
|
||||||
@ -995,26 +1078,53 @@
|
|||||||
margin: 45px 0 45px 0;
|
margin: 45px 0 45px 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.artificial-caret {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 21px;
|
|
||||||
width: 1.5px;
|
|
||||||
margin: 0 0;
|
|
||||||
/*opacity: 0.3;*/
|
|
||||||
background-color: rgb(33, 186, 69);
|
|
||||||
animation: blinker 1.1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
/* .artificial-caret:after {
|
|
||||||
content: '';
|
|
||||||
width: 3px;
|
|
||||||
height: 3px;
|
|
||||||
margin: 0 0 0 -1px;
|
|
||||||
display: block;
|
|
||||||
background-color: rgb(33, 186, 69);
|
|
||||||
|
|
||||||
}*/
|
.note-mini-tag-area {
|
||||||
|
position: fixed;
|
||||||
|
width: 100px;
|
||||||
|
left: calc(15% - 100px);
|
||||||
|
top: 46px;
|
||||||
|
bottom: 0;
|
||||||
|
height: 500px;
|
||||||
|
z-index: 1000;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
|
scrollbar-color: transparent transparent;
|
||||||
|
}
|
||||||
|
.subtle-tag {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px 1px 2px 4px;
|
||||||
|
margin: 0 0 2px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-right: none;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: transparent;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: color ease 0.3s, background ease 0.3s;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.note-mini-tag-area:hover .subtle-tag {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.note-mini-tag-area:hover .subtle-tag:not(.active-mini-tag) {
|
||||||
|
border-right: none;
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: var(--body_bg_color);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.active-mini-tag {
|
||||||
|
opacity: 0.6;
|
||||||
|
background-color: #16ab39;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes blinker {
|
@keyframes blinker {
|
||||||
50% {
|
50% {
|
||||||
|
@ -3,19 +3,27 @@
|
|||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="sixteen wide column">
|
<div class="sixteen wide column">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
|
<div class="ui raised segment">
|
||||||
|
|
||||||
|
|
||||||
<h2 class="ui dividing header">
|
<h2 class="ui dividing header">
|
||||||
|
<i class="green question circle outline icon"></i>
|
||||||
Help
|
Help
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<p v-if="$store.getters.totals && $store.getters.totals['activeSessions'] > 0">
|
||||||
|
Logged in user has {{ $store.getters.totals['activeSessions'] }} active sessions.
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Content copied from note -->
|
<!-- Content copied from note -->
|
||||||
<!-- https://www.solidscribe.com/#/notes/open/552 -->
|
<!-- https://www.solidscribe.com/#/notes/open/552 -->
|
||||||
|
|
||||||
<p><b>Only Note Text is Encrypted</b><br></p><p>Only you can read your notes. Encryption is the transformation of data into a form unreadable by anyone without the password. Its purpose is to ensure privacy by keeping the information hidden from anyone for whom it is not intended, even those who can see the encrypted data. Even if every note in the database was leaked, nothing would be readable. If the government asked for your notes, it would all be gibberish. <br></p><p><br></p><p><b>Some Data is not encrypted</b><br></p><p>Everything isn't encrypted, to keep up ease of use. Files, Tags and Attachments are not encrypted.<br></p><p><br></p><p><b>Searching is somewhat limited</b><br></p><p>Since every note is encrypted, searching is limited. To maintain security, only single words can be searched. Your search index is private and Encrypted.<br></p><p><br></p><p><b>The Scrat</b><b></b><b>ch Pad</b><b></b><br></p><p>The Scratch Pad was designed to allow rapid input to a single note. Rather than junking up all your notes with random links, numbers or haikus, you can put them all in one place. <br></p><ul><li>All data pushed to the quick note can still be edited like a normal note.<br></li><li>Creating a new quick note will not erase your old <br></li></ul><p><br></p><p><b>Flux Theme</b><br></p><p>Flux theme limits the amount of blue emitted by your screen. Most things turn sepia and a filter is applied to images to make them more sepia. Less blue light at night is supposed to be helpful for falling asleep.<br></p><p>Here is some good research on the topic: <a href="https://justgetflux.com/research.html">https://justgetflux.com/research.html</a><br></p><p><br></p><p><b>Keyboard Shor</b><b></b><b>tcuts</b><br></p><p>Number List - CTRL + SHIFT + 9<br></p><p>Todo List - CTRL + SHIFT + 8<br></p><p>Underline CTRL + u<br></p><p>Bold - CTRL + b<br></p><p>Quote - CRTL + i<br></p><p>Indent - CTL + ]<br></p><p>Outdent - CRTL + [<br></p><p>Undo - CTRL + z<br></p><p>Redo - CTRL + y<br></p><p><br></p><p><b>Links in notes</b><br></p><p>Links put into notes are automatically scraped. This means the data from the link will be scanned to get an image and some text from the website to help make that link more accessible in the future. You can edit the text of scarped links and any time and search for it later. <br></p><p><br></p><p><b>Files in notes</b><br></p><p>Files can be uploaded to notes. If its an image, the picture will be put into the note.<br></p><p>Images added to notes will have text scanned, so it can be searched (This isn't super accurate so don't rely to heavily on it.) The text can be updated at any time.<br></p><p><br></p><p><b>Deleting notes</b><br></p><p>When<b> </b>notes are deleted, none of the files related to the note are deleted. <br></p><p><br></p><p><b>Daily Backups</b><br></p><p>All notes are backed up, every night, at midnight. If there is data loss, it can be restored from a backup. If you experience some sort of cataclysmic data loss please contact the system administrator for a copy of your data or a restoration procedure. <br></p>
|
<p><b>Only Note Text is Encrypted</b><br></p><blockquote><p>Only you can read your notes. Encryption is the transformation of data into a form unreadable by anyone without the password. Its purpose is to ensure privacy by keeping the information hidden from anyone for whom it is not intended, even those who can see the encrypted data. Even if every note in the database was leaked, nothing would be readable. If the government asked for your notes, it would all be gibberish. <br></p></blockquote><p><br></p><p><b>Some Data is not encrypted</b><br></p><blockquote><p>Everything isn't encrypted, to keep up ease of use. Files, Tags and Attachments are not encrypted.<br></p></blockquote><p><br></p><p><b>Searching is somewhat limited</b><br></p><blockquote><p>Since every note is encrypted, searching is limited. To maintain security, only single words can be searched. Your search index is private and Encrypted.<br></p></blockquote><p><br></p><p><b>The Scrat</b><b></b><b>ch Pad</b><b></b><br></p><blockquote><p>The Scratch Pad was designed to allow rapid input to a single note. Rather than junking up all your notes with random links, numbers or haikus, you can put them all in one place. <br></p><ul><li>All data pushed to the quick note can still be edited like a normal note.<br></li><li>Creating a new quick note will not erase your old <br></li></ul></blockquote><p><br></p><p><b>Flux Theme</b><br></p><blockquote><p>Flux theme limits the amount of blue emitted by your screen. Most things turn sepia and a filter is applied to images to make them more sepia. Less blue light at night is supposed to be helpful for falling asleep.<br></p><p>Here is some good research on the topic: <a href="https://justgetflux.com/research.html">https://justgetflux.com/research.html</a><br></p></blockquote><p><br></p><p><b>Text Editor Keyboard Shor</b><b>tcuts</b><br></p><blockquote><p>Number List - CTRL + SHIFT + 9<br></p><p>Todo List - CTRL + SHIFT + 8<br></p><p>Underline CTRL + u<br></p><p>Bold - CTRL + b<br></p><p>Quote - CRTL + i<br></p><p>Indent - CTL + ]<br></p><p>Outdent - CRTL + [<br></p><p>Undo - CTRL + z<br></p><p>Redo - CTRL + y<br></p></blockquote><p><br></p><p><b>Shared Notes and Security</b><br></p><blockquote><p>Shared notes still respect privacy but use a different security scheme. Instead of encrypting the note with your password, a shared password is created. This note then uses <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a> to share the note with other users, while still making it unreadable to anyone else. <br></p><ul><li>You can revoke any users access to a shared note at any time.<br></li><li>Notes you share can not be shared by other people.<br></li><li>If you turn off sharing, all users lose access and the note is reverted to the default security scheme.<br></li></ul></blockquote><p><b>Public Links for shared notes</b><br></p><blockquote><p>It is possible to generate a public URL for shared notes. This is a huge security risk and exposes the Encryption Key for the note. The encryption key to a shared note can easily be changed by turning off sharing, then turning it back on. <br></p><p><br></p></blockquote><p><b>Links in notes</b><br></p><blockquote><p>Links put into notes are automatically scraped. This means the data from the link will be scanned to get an image and some text from the website to help make that link more accessible in the future. You can edit the text of scarped links and any time and search for it later. <br></p></blockquote><p><br></p><p><b>Files in notes</b><br></p><blockquote><p>Files can be uploaded to notes. If its an image, the picture will be put into the note.<br></p><p>Images added to notes will have text scanned, so it can be searched (This isn't super accurate so don't rely to heavily on it.) The text can be updated at any time.<br></p></blockquote><p><br></p><p><b>Deleting notes</b><br></p><blockquote><p>When<b> </b>notes are deleted, none of the files related to the note are deleted. <br></p></blockquote><p><br></p><p><b>Daily Backups</b><br></p><blockquote><p>All notes are backed up, every night, at midnight. If there is data loss, it can be restored from a backup. If you experience some sort of cataclysmic data loss please contact the system administrator for a copy of your data or a restoration procedure. <br></p></blockquote><p><br></p>
|
||||||
|
|
||||||
<!-- content copied from note -->
|
<!-- content copied from note -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
-webkit-animation: fadeorama 16s ease infinite;
|
-webkit-animation: fadeorama 16s ease infinite;
|
||||||
-moz-animation: fadeorama 16s ease infinite;
|
-moz-animation: fadeorama 16s ease infinite;
|
||||||
animation: fadeorama 16s ease infinite;
|
animation: fadeorama 16s ease infinite;
|
||||||
|
height: 350px;
|
||||||
}
|
}
|
||||||
.logo-display {
|
.logo-display {
|
||||||
width: 50%;
|
width: 140px;
|
||||||
max-width: 450px;
|
height: auto;
|
||||||
}
|
}
|
||||||
.lightly-padded {
|
.lightly-padded {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -112,10 +113,10 @@
|
|||||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg" alt="">
|
<img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg" alt="">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="one wide large screen only column"></div>
|
<!-- <div class="one wide large screen only column"></div> -->
|
||||||
|
|
||||||
<!-- desktop column - large screen only -->
|
<!-- desktop column - large screen only -->
|
||||||
<div class="seven wide middle aligned left aligned column">
|
<div class="sixteen wide middle aligned center aligned column">
|
||||||
|
|
||||||
<h2 class="massive-text">
|
<h2 class="massive-text">
|
||||||
<img class="logo-display" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
|
<img class="logo-display" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
|
||||||
@ -129,9 +130,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="eight wide middle aligned left aligned column">
|
<!-- <div class="eight wide middle aligned left aligned column">
|
||||||
<img loading="lazy" width="90%" src="/api/static/assets/marketing/notebook.svg" alt="The Venus fly laptop about to capture another victim">
|
<img loading="lazy" width="90%" src="/api/static/assets/marketing/notebook.svg" alt="The Venus fly laptop about to capture another victim">
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -341,12 +342,6 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeMount(){
|
beforeMount(){
|
||||||
|
|
||||||
//Don't change hero banner on mobile
|
|
||||||
if(!this.$store.getters.getIsUserOnMobile){
|
|
||||||
let windowHeight = window.innerHeight
|
|
||||||
this.height = windowHeight - (windowHeight * 0.18)
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showRealInformation(){
|
showRealInformation(){
|
||||||
|
@ -509,7 +509,7 @@
|
|||||||
|
|
||||||
//Don't move notes that were not changed
|
//Don't move notes that were not changed
|
||||||
if(note.updated == newNote.updated){
|
if(note.updated == newNote.updated){
|
||||||
return
|
// return
|
||||||
}
|
}
|
||||||
|
|
||||||
//go through each prop and update it with new values
|
//go through each prop and update it with new values
|
||||||
|
25
package-lock.json
generated
25
package-lock.json
generated
@ -4,11 +4,6 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@log4js-node/log4js-api": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-eoFD+zPwd98+V53Kfxj+p0oC7Is="
|
|
||||||
},
|
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.6.8",
|
"version": "12.6.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
|
||||||
@ -1209,11 +1204,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
|
||||||
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
|
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
|
||||||
"version": "2.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
|
||||||
"integrity": "sha1-5jNFY4bUqlWGP2dqerDaqP3ssP0="
|
|
||||||
},
|
|
||||||
"node-tesseract-ocr": {
|
"node-tesseract-ocr": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-1.0.0.tgz",
|
||||||
@ -1592,16 +1582,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"solr-node": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/solr-node/-/solr-node-1.2.1.tgz",
|
|
||||||
"integrity": "sha1-vZkXswJRp+N+fg2+nBtZnzl4zwY=",
|
|
||||||
"requires": {
|
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
|
||||||
"node-fetch": "^2.3.0",
|
|
||||||
"underscore": "^1.8.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sqlstring": {
|
"sqlstring": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||||
@ -1714,11 +1694,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
"underscore": {
|
|
||||||
"version": "1.9.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
|
|
||||||
"integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE="
|
|
||||||
},
|
|
||||||
"unpipe": {
|
"unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
"node-tesseract-ocr": "^1.0.0",
|
"node-tesseract-ocr": "^1.0.0",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"request-promise": "^4.2.4",
|
"request-promise": "^4.2.4",
|
||||||
"socket.io": "^2.3.0",
|
"socket.io": "^2.3.0"
|
||||||
"solr-node": "^1.2.1"
|
|
||||||
},
|
},
|
||||||
"_moduleAliases": {
|
"_moduleAliases": {
|
||||||
"@root": ".",
|
"@root": ".",
|
||||||
|
@ -1,26 +1,123 @@
|
|||||||
var jwt = require('jsonwebtoken');
|
const db = require('@config/database')
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const cs = require('@helpers/CryptoString')
|
||||||
|
|
||||||
let Auth = {}
|
let Auth = {}
|
||||||
|
|
||||||
const tokenSecretKey = process.env.JSON_KEY
|
const tokenSecretKey = process.env.JSON_KEY
|
||||||
|
|
||||||
Auth.createToken = (userId, masterKey) => {
|
Auth.createToken = (userId, masterKey, request = null) => {
|
||||||
const signedData = {'id':userId, 'date':Date.now(), 'masterKey':masterKey}
|
|
||||||
const token = jwt.sign(signedData, tokenSecretKey)
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
Auth.decodeToken = (token) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jwt.verify(token, tokenSecretKey, function(err, decoded){
|
|
||||||
if(err || decoded.id == undefined){
|
const created = Math.floor(+new Date/1000)
|
||||||
reject('Bad Token')
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
return
|
|
||||||
}
|
//Encrypt Master Password and save it to the server
|
||||||
//Pass back decoded token
|
const salt = cs.createSmallSalt()
|
||||||
resolve(decoded)
|
const tempPass = cs.createSmallSalt()
|
||||||
return
|
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])
|
||||||
|
.then((r,f) => {
|
||||||
|
|
||||||
|
//Required Data for JWT payload
|
||||||
|
const tokenPayload = {userId, tempPass, salt}
|
||||||
|
|
||||||
|
//Return token
|
||||||
|
const token = jwt.sign(tokenPayload, tokenSecretKey)
|
||||||
|
return resolve(token)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Auth.decodeToken = (token, request = null) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
let decodedToken = null
|
||||||
|
|
||||||
|
//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')
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
|
||||||
|
const row = r[0][0]
|
||||||
|
if(row == undefined || row.length == 0){
|
||||||
|
return reject(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Decrypt master key from database
|
||||||
|
const masterKey = cs.decrypt(decodedToken.tempPass, decodedToken.salt, row.encrypted_master_password)
|
||||||
|
if(masterKey == null){
|
||||||
|
return reject (false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
userId: decodedToken.userId, masterKey, tokenId: row.id
|
||||||
|
}
|
||||||
|
|
||||||
|
//Async update DB counts
|
||||||
|
db.promise().query('UPDATE user_active_session SET uses = uses + 1 WHERE salt = ? LIMIT 1', [decodedToken.salt])
|
||||||
|
|
||||||
|
return resolve(userData)
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Auth.reissueToken = () => {
|
||||||
|
//If token has more than 200 uses, renew it
|
||||||
|
}
|
||||||
|
Auth.deletAllLoginKeys = (userId) => {
|
||||||
|
|
||||||
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
|
|
||||||
|
return db.promise().query('DELETE FROM user_active_session WHERE user_hash = ?', [userHash])
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth.test = () => {
|
||||||
|
|
||||||
|
// return Auth.deletAllLoginKeys(testUserId)
|
||||||
|
|
||||||
|
const testUserId = 22
|
||||||
|
const testPass = cs.createSmallSalt()
|
||||||
|
Auth.createToken(testUserId, testPass)
|
||||||
|
.then(token => {
|
||||||
|
|
||||||
|
console.log('Test: Create JWT -> Pass')
|
||||||
|
|
||||||
|
return Auth.decodeToken(token)
|
||||||
|
})
|
||||||
|
.then(userData => {
|
||||||
|
|
||||||
|
console.log('Test: Decrypted key Match -> ' + (testPass == userData.masterKey))
|
||||||
|
|
||||||
|
return Auth.deletAllLoginKeys(testUserId)
|
||||||
|
})
|
||||||
|
.then(results => {
|
||||||
|
|
||||||
|
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
|
@ -198,19 +198,21 @@ http.listen(3001, function(){
|
|||||||
//Enable json body parsing in requests. Allows me to post data in ajax calls
|
//Enable json body parsing in requests. Allows me to post data in ajax calls
|
||||||
app.use(express.json({limit: '5mb'}))
|
app.use(express.json({limit: '5mb'}))
|
||||||
|
|
||||||
|
|
||||||
//Prefix defied by route in nginx config
|
|
||||||
const prefix = '/api'
|
|
||||||
|
|
||||||
//App Auth, all requests will come in with a token, decode the token and set global var
|
//App Auth, all requests will come in with a token, decode the token and set global var
|
||||||
app.use(function(req, res, next){
|
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
|
||||||
|
|
||||||
//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 && token != null && typeof token === 'string'){
|
||||||
Auth.decodeToken(token)
|
Auth.decodeToken(token, req)
|
||||||
.then(userData => {
|
.then(userData => {
|
||||||
req.headers.userId = userData.id //Update headers for the rest of the application
|
req.headers.userId = userData.userId //Update headers for the rest of the application
|
||||||
req.headers.masterKey = userData.masterKey
|
req.headers.masterKey = userData.masterKey
|
||||||
|
req.headers.tokenId = userData.tokenId
|
||||||
next()
|
next()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
|
||||||
@ -224,9 +226,11 @@ app.use(function(req, res, next){
|
|||||||
|
|
||||||
|
|
||||||
// Test Area
|
// Test Area
|
||||||
const printResults = false
|
const printResults = true
|
||||||
let UserTest = require('@models/User')
|
let UserTest = require('@models/User')
|
||||||
let NoteTest = require('@models/Note')
|
let NoteTest = require('@models/Note')
|
||||||
|
let AuthTest = require('@helpers/Auth')
|
||||||
|
Auth.test()
|
||||||
UserTest.keyPairTest('genMan12', '1', printResults)
|
UserTest.keyPairTest('genMan12', '1', printResults)
|
||||||
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||||
.then( message => {
|
.then( message => {
|
||||||
@ -236,34 +240,34 @@ UserTest.keyPairTest('genMan12', '1', printResults)
|
|||||||
|
|
||||||
|
|
||||||
//Test
|
//Test
|
||||||
app.get(prefix, (req, res) => res.send('The api is running'))
|
app.get('/api', (req, res) => res.send('Solidscribe API is up and running'))
|
||||||
|
|
||||||
//Serve up uploaded files
|
//Serve up uploaded files
|
||||||
app.use(prefix+'/static', express.static( __dirname+'/../staticFiles' ))
|
app.use('/api/static', express.static( __dirname+'/../staticFiles' ))
|
||||||
|
|
||||||
//Public routes
|
//Public routes
|
||||||
var public = require('@routes/publicController')
|
var public = require('@routes/publicController')
|
||||||
app.use(prefix+'/public', public)
|
app.use('/api/public', public)
|
||||||
|
|
||||||
//user endpoint
|
//user endpoint
|
||||||
var user = require('@routes/userController')
|
var user = require('@routes/userController')
|
||||||
app.use(prefix+'/user', user)
|
app.use('/api/user', user)
|
||||||
|
|
||||||
//notes endpoint
|
//notes endpoint
|
||||||
var notes = require('@routes/noteController')
|
var notes = require('@routes/noteController')
|
||||||
app.use(prefix+'/note', notes)
|
app.use('/api/note', notes)
|
||||||
|
|
||||||
//tags endpoint
|
//tags endpoint
|
||||||
var tags = require('@routes/tagController')
|
var tags = require('@routes/tagController')
|
||||||
app.use(prefix+'/tag', tags)
|
app.use('/api/tag', tags)
|
||||||
|
|
||||||
//notes endpoint
|
//notes endpoint
|
||||||
var attachment = require('@routes/attachmentController')
|
var attachment = require('@routes/attachmentController')
|
||||||
app.use(prefix+'/attachment', attachment)
|
app.use('/api/attachment', attachment)
|
||||||
|
|
||||||
//quick notes endpoint
|
//quick notes endpoint
|
||||||
var quickNote = require('@routes/quicknoteController')
|
var quickNote = require('@routes/quicknoteController')
|
||||||
app.use(prefix+'/quick-note', quickNote)
|
app.use('/api/quick-note', quickNote)
|
||||||
|
|
||||||
//Output running status
|
//Output running status
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
|
@ -40,9 +40,10 @@ User.login = (username, password) => {
|
|||||||
.then(({publicKey, privateKey}) => {
|
.then(({publicKey, privateKey}) => {
|
||||||
|
|
||||||
//Passback a json web token
|
//Passback a json web token
|
||||||
const token = Auth.createToken(lookedUpUser.id, masterKey)
|
Auth.createToken(lookedUpUser.id, masterKey)
|
||||||
resolve({ token: token, userId:lookedUpUser.id })
|
.then(token => {
|
||||||
|
return resolve({ token: token, userId:lookedUpUser.id })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -119,7 +120,10 @@ User.register = (username, password) => {
|
|||||||
})
|
})
|
||||||
.then(({publicKey, privateKey}) => {
|
.then(({publicKey, privateKey}) => {
|
||||||
|
|
||||||
const token = Auth.createToken(userId, newMasterKey)
|
return Auth.createToken(userId, newMasterKey)
|
||||||
|
})
|
||||||
|
.then(token => {
|
||||||
|
|
||||||
return resolve({token, userId})
|
return resolve({token, userId})
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
@ -166,6 +170,16 @@ 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(
|
||||||
|
`SELECT count(id) as activeSessions FROM user_active_session WHERE user_hash = ?`, [userHash]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then( (rows, fields) => {
|
||||||
|
|
||||||
|
Object.assign(countTotals, rows[0][0]) //combine results
|
||||||
|
|
||||||
return db.promise().query(
|
return db.promise().query(
|
||||||
`SELECT
|
`SELECT
|
||||||
SUM(attachment_type = 1) as linkFiles,
|
SUM(attachment_type = 1) as linkFiles,
|
||||||
@ -191,6 +205,11 @@ 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.generateMasterKey = (userId, password) => {
|
User.generateMasterKey = (userId, password) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
@ -30,6 +30,18 @@ router.post('/login', function (req, res) {
|
|||||||
res.send(false)
|
res.send(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
// Logout User
|
||||||
|
router.post('/logout', function (req, res) {
|
||||||
|
|
||||||
|
User.logout(req.headers.tokenId)
|
||||||
|
.then( returnData => {
|
||||||
|
res.send(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Login User
|
// Login User
|
||||||
router.post('/register', function (req, res) {
|
router.post('/register', function (req, res) {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user