parent
3ed26bcc03
commit
2a379f8a4e
@ -42,6 +42,12 @@ body {
|
|||||||
font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.segment {
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: var(--background_color);
|
||||||
|
border-color: var(--border_color);
|
||||||
|
}
|
||||||
|
|
||||||
.ui.form input:not([type]),
|
.ui.form input:not([type]),
|
||||||
.ui.form input:not([type]):focus,
|
.ui.form input:not([type]):focus,
|
||||||
.ui.form textarea:not([type]),
|
.ui.form textarea:not([type]),
|
||||||
@ -216,10 +222,6 @@ a:hover {
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
/*Makes the first line real big */
|
/*Makes the first line real big */
|
||||||
.squire-box > p:first-child {
|
|
||||||
font-size: 1.4em;
|
|
||||||
line-height: 1.7em;
|
|
||||||
}
|
|
||||||
.squire-box:focus {
|
.squire-box:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
|
|
||||||
color: var(--text_color);
|
color: white;
|
||||||
background-color: var(--background_color);
|
background-color: #21ba45;
|
||||||
}
|
}
|
||||||
.popup-row {
|
.popup-row {
|
||||||
padding: 1em 5px;
|
padding: 1em 5px;
|
||||||
@ -30,7 +30,7 @@
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
.popup-row + .popup-row {
|
.popup-row + .popup-row {
|
||||||
border-top: 1px solid #000;
|
border-top: 1px solid #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -64,6 +64,9 @@
|
|||||||
},
|
},
|
||||||
mounted(){
|
mounted(){
|
||||||
|
|
||||||
|
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||||
|
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||||
|
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -41,12 +41,69 @@
|
|||||||
<!-- Squire box grows -->
|
<!-- Squire box grows -->
|
||||||
<div class="note-wrapper">
|
<div class="note-wrapper">
|
||||||
|
|
||||||
<div id="squire-id" class="squire-box" ref="squirebox"></div>
|
<textarea
|
||||||
|
ref="titleTextarea"
|
||||||
|
v-on:keyup="titleResize"
|
||||||
|
v-on:keydown="titleResize"
|
||||||
|
@keydown.enter.exact.prevent="editor.focus()"
|
||||||
|
rows="1"
|
||||||
|
:style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"
|
||||||
|
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input">
|
||||||
|
</textarea>
|
||||||
|
|
||||||
|
<div v-show="isDecrypted" id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||||
|
|
||||||
|
<!-- Decrypt note prompt -->
|
||||||
|
<div v-if="isEncrypted && !isDecrypted" class="ui basic padded segment">
|
||||||
|
<div class="ui raised segment">
|
||||||
|
<h3 class="ui center aligned icon header">
|
||||||
|
<i class="green lock alternate icon"></i>
|
||||||
|
|
||||||
|
<span v-if="!lockedOut" :data-tooltip="`Unlock Attempts: ${decryptAttempts}`">
|
||||||
|
This note is encrypted and requires a password to be opened.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- note is locked for 5 minutes -->
|
||||||
|
<span v-if="lockedOut">
|
||||||
|
To many unlock attempts. Note is locked for 5 minutes.
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<!-- Decrypt note -->
|
||||||
|
<div class="ui form" v-if="!lockedOut">
|
||||||
|
<h5 class="ui horizontal divider header" v-if="passwordHint && passwordHint.length > 0">
|
||||||
|
Hint: {{ passwordHint }}
|
||||||
|
</h5>
|
||||||
|
<div class="field">
|
||||||
|
<input :name="`randomThing-${noteid}`" :id="`yupper-${noteid}`"type="password" v-model="password" placeholder="Note Password" v-on:keyup.enter="decryptNote" autofocus ref="decryptNotePrompt">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div v-on:click="decryptNote" class="ui green fluid button" v-if="password.length >= 3">
|
||||||
|
Unlock Note
|
||||||
|
</div>
|
||||||
|
<div class="ui disabled fluid button" v-if="password.length < 3">
|
||||||
|
Unlock Note
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- bottom stats -->
|
<!-- bottom stats -->
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="sixteen wide column">
|
<div class="sixteen wide column">
|
||||||
|
|
||||||
|
<div class="ui basic button"v-if="!isEncrypted" v-on:click="passwordEnterVisible = true">
|
||||||
|
<i class="shield alternate icon"></i>
|
||||||
|
Password Protect
|
||||||
|
</div>
|
||||||
|
<div class="ui icon basic button" v-if="isEncrypted && isDecrypted" v-on:click="disableEncryption">
|
||||||
|
<i class="unlock icon"></i>
|
||||||
|
Remove Password
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ui basic compact button">
|
<div class="ui basic compact button">
|
||||||
Status: {{ statusText }}
|
Status: {{ statusText }}
|
||||||
</div>
|
</div>
|
||||||
@ -56,6 +113,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- && this.$store.getters.getIsUserOnMobile -->
|
<!-- && this.$store.getters.getIsUserOnMobile -->
|
||||||
@ -202,7 +261,56 @@
|
|||||||
</div>
|
</div>
|
||||||
</side-slide-menu>
|
</side-slide-menu>
|
||||||
|
|
||||||
<div class="full-focus-shade"></div>
|
<side-slide-menu v-if="passwordEnterVisible" v-on:close="passwordEnterVisible = false" :fullShadow="true" name="encrypt note">
|
||||||
|
<div class="ui basic segment" v-if="isDecrypted && isEncrypted">
|
||||||
|
<p>Note Decrypted</p>
|
||||||
|
<div class="ui green button" v-on:click="lockNote">Lock Note</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!isEncrypted" class="ui basic segment">
|
||||||
|
|
||||||
|
<div class="ui top attached segment">
|
||||||
|
|
||||||
|
<h2><i class="green lock alternate icon"></i>Password protect this Note</h2>
|
||||||
|
<p>Password protection will prevent anyone from reading the text of this note, unless they enter the correct password.</p>
|
||||||
|
<p><b>Only the note text is protected. Title, tags, and files are not encrypted and remain visible without a password.</b></p>
|
||||||
|
<p>The password you select will only be used for this note. You can use the same password on multiple notes. The note will be encrypted using the password entered. A longer password is will be more secure.</p>
|
||||||
|
<h4><i class="red icon exclamation triangle"></i> Warning. There is no way to recover a lost password.</h4>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui bottom attached segment">
|
||||||
|
<div class="ui form">
|
||||||
|
<div class="field">
|
||||||
|
<label>New Password to lock this note</label>
|
||||||
|
<input :name="`randomThing-${noteid}`" :id="`yupper-${noteid}`"type="password" v-model="password" placeholder="Note Password">
|
||||||
|
</div>
|
||||||
|
<div class="field" v-if="password.length > 3">
|
||||||
|
<label>Confirm Password</label>
|
||||||
|
<input :name="`randomStuff-${noteid}`" :id="`heckye-${noteid}`"type="password" v-model="passwordConfirm" placeholder="Confirm Password">
|
||||||
|
</div>
|
||||||
|
<div class="field" v-if="password.length > 3">
|
||||||
|
<label>Password Hint - visible when unlocking note</label>
|
||||||
|
<input :name="`randomStuzz-${noteid}`" :id="`heckyo-${noteid}`"type="text" v-model="passwordHint" placeholder="Optional Password Hint" v-on:keyup.enter="enableEncryption">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field" v-if="passwordConfirm.length > 3 && password != passwordConfirm">
|
||||||
|
<div v-on:click="enableEncryption" class="ui disabled green button">
|
||||||
|
Passwords do not match
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field" v-if="passwordConfirm.length > 3 && password == passwordConfirm">
|
||||||
|
<div v-on:click="enableEncryption" class="ui green button">
|
||||||
|
Protect!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</side-slide-menu>
|
||||||
|
|
||||||
|
<!-- <div class="full-focus-shade"></div> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -210,6 +318,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
const crypto = require('crypto')
|
||||||
const DiffMatchPatch = require('../../../server/helpers/DiffMatchPatch')
|
const DiffMatchPatch = require('../../../server/helpers/DiffMatchPatch')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -233,6 +342,7 @@
|
|||||||
currentNoteId: 0,
|
currentNoteId: 0,
|
||||||
modified: false,
|
modified: false,
|
||||||
noteText: '',
|
noteText: '',
|
||||||
|
noteTitle: '',
|
||||||
rawTextId: 0,
|
rawTextId: 0,
|
||||||
created: '',
|
created: '',
|
||||||
updated: '',
|
updated: '',
|
||||||
@ -270,6 +380,18 @@
|
|||||||
colorPickerVisible: false,
|
colorPickerVisible: false,
|
||||||
showFilesSideMenu: false,
|
showFilesSideMenu: false,
|
||||||
showNoteOptions: false,
|
showNoteOptions: false,
|
||||||
|
|
||||||
|
//Encryption options
|
||||||
|
passwordHint: '',
|
||||||
|
password: '', //Field Variables, only for form
|
||||||
|
passwordConfirm: '', //Only a form variable
|
||||||
|
hashedPass: '', //sha-256 password hash, sends to server for decryption
|
||||||
|
isEncrypted: false,
|
||||||
|
isDecrypted: false,
|
||||||
|
passwordEnterVisible: false,
|
||||||
|
decryptAttempts: 0,
|
||||||
|
lockedOut: false,
|
||||||
|
autoLockTimeout: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -299,6 +421,11 @@
|
|||||||
},
|
},
|
||||||
beforeDestroy(){
|
beforeDestroy(){
|
||||||
|
|
||||||
|
this.password = ''
|
||||||
|
this.passwordConfirm = ''
|
||||||
|
this.hashedPass = ''
|
||||||
|
clearTimeout(this.autoLockTimeout)
|
||||||
|
|
||||||
this.$io.emit('leave_room', this.rawTextId)
|
this.$io.emit('leave_room', this.rawTextId)
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||||
@ -326,7 +453,14 @@
|
|||||||
|
|
||||||
//focus on open, not on mobile, thats annoying
|
//focus on open, not on mobile, thats annoying
|
||||||
if(!this.$store.getters.getIsUserOnMobile){
|
if(!this.$store.getters.getIsUserOnMobile){
|
||||||
this.editor.focus()
|
// this.editor.focus()
|
||||||
|
|
||||||
|
if(this.noteTitle && this.noteTitle.length == 0){
|
||||||
|
this.$refs.titleTextarea.focus()
|
||||||
|
} else {
|
||||||
|
this.editor.moveCursorToEnd()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Click Event - Open links when clicked in editor or toggle checks
|
//Click Event - Open links when clicked in editor or toggle checks
|
||||||
@ -705,48 +839,68 @@
|
|||||||
},
|
},
|
||||||
loadNote(noteId){
|
loadNote(noteId){
|
||||||
|
|
||||||
let vm = this
|
//Generate a random loading message
|
||||||
|
|
||||||
let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating','Lumping']
|
let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating','Lumping']
|
||||||
let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos']
|
let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos']
|
||||||
let p1 = doing[Math.floor(Math.random() * doing.length)]
|
let p1 = doing[Math.floor(Math.random() * doing.length)]
|
||||||
let p2 = thing[Math.floor(Math.random() * thing.length)]
|
let p2 = thing[Math.floor(Math.random() * thing.length)]
|
||||||
vm.loadingMessage = p1 + ' ' + p2
|
this.loadingMessage = p1 + ' ' + p2
|
||||||
|
|
||||||
//Component is activated with NoteId in place, lookup text with associated ID
|
//Component is activated with NoteId in place, lookup text with associated ID
|
||||||
if(this.$store.getters.getLoggedIn){
|
if(this.$store.getters.getLoggedIn){
|
||||||
axios.post('/api/note/get', {'noteId': noteId})
|
axios.post('/api/note/get', { 'noteId': this.noteid, 'password':this.hashedPass })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
||||||
//Set up local data
|
//Set up local data
|
||||||
vm.currentNoteId = noteId
|
this.currentNoteId = this.noteid
|
||||||
this.rawTextId = response.data.rawTextId
|
this.rawTextId = response.data.rawTextId
|
||||||
this.shareUsername = response.data.shareUsername
|
this.shareUsername = response.data.shareUsername
|
||||||
|
this.passwordHint = response.data.password_hint
|
||||||
|
|
||||||
this.created = response.data.created
|
this.created = response.data.created
|
||||||
this.updated = response.data.updated
|
this.updated = response.data.updated
|
||||||
|
this.noteTitle = response.data.title
|
||||||
|
|
||||||
vm.noteText = response.data.text
|
this.noteText = response.data.text
|
||||||
vm.diffNoteText = response.data.text
|
this.diffNoteText = response.data.text
|
||||||
|
|
||||||
vm.lastNoteHash = vm.hashString(response.data.text)
|
this.lastNoteHash = this.hashString(response.data.text)
|
||||||
//Set up note colors
|
//Set up note colors
|
||||||
if(response.data.color){
|
if(response.data.color){
|
||||||
vm.styleObject = JSON.parse(response.data.color)
|
this.styleObject = JSON.parse(response.data.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(response.data.pinned != null){
|
if(response.data.pinned != null){
|
||||||
vm.pinned = response.data.pinned
|
this.pinned = response.data.pinned
|
||||||
}
|
}
|
||||||
vm.archived = response.data.archived
|
this.archived = response.data.archived
|
||||||
vm.attachmentCount = response.data.attachment_count
|
this.attachmentCount = response.data.attachment_count
|
||||||
|
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
||||||
vm.$nextTick(() => {
|
this.isDecrypted = response.data.decrypted
|
||||||
|
this.isEncrypted = response.data.encrypted == 1
|
||||||
|
this.decryptAttempts = response.data.decrypt_attempts_count
|
||||||
|
this.lockedOut = response.data.lockedOut
|
||||||
|
|
||||||
|
|
||||||
|
//If password is required, display a prompt and focus on it
|
||||||
|
if(this.password.length == 0 && this.isEncrypted && !this.isDecrypted){
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if(this.$refs.decryptNotePrompt){
|
||||||
|
// this.editor.moveCursorToEnd()
|
||||||
|
this.$refs.decryptNotePrompt.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
//Adjust note title size after load
|
||||||
|
this.titleResize()
|
||||||
|
|
||||||
this.setupWebSockets()
|
this.setupWebSockets()
|
||||||
this.initSquire()
|
this.initSquire()
|
||||||
|
this.startAutolockTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -917,7 +1071,7 @@
|
|||||||
.createExpression(xpath)
|
.createExpression(xpath)
|
||||||
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue
|
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue
|
||||||
},
|
},
|
||||||
onKeyup(event){
|
onKeyup(){
|
||||||
|
|
||||||
this.statusText = 'Save'
|
this.statusText = 'Save'
|
||||||
|
|
||||||
@ -943,6 +1097,12 @@
|
|||||||
|
|
||||||
// return resolve(true)
|
// return resolve(true)
|
||||||
|
|
||||||
|
|
||||||
|
//Encrypted notes that are not decrypted should not be saved
|
||||||
|
if(this.isEncrypted && !this.isDecrypted){
|
||||||
|
return resolve(true)
|
||||||
|
}
|
||||||
|
|
||||||
//Don't save note if its hash doesn't change
|
//Don't save note if its hash doesn't change
|
||||||
const currentNoteText = this.getText()
|
const currentNoteText = this.getText()
|
||||||
if( this.lastNoteHash == this.hashString( currentNoteText )){
|
if( this.lastNoteHash == this.hashString( currentNoteText )){
|
||||||
@ -958,11 +1118,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
'noteId':this.currentNoteId,
|
'noteId': this.currentNoteId,
|
||||||
|
'title': this.noteTitle,
|
||||||
'text': currentNoteText,
|
'text': currentNoteText,
|
||||||
'color': JSON.stringify(this.styleObject), //Save little json color object
|
'color': JSON.stringify(this.styleObject), //Save little json color object
|
||||||
'pinned': this.pinned,
|
'pinned': this.pinned,
|
||||||
'archived':this.archived,
|
'archived': this.archived,
|
||||||
|
'password': this.hashedPass,
|
||||||
|
'hint': this.passwordHint,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.statusText = 'Saving'
|
this.statusText = 'Saving'
|
||||||
@ -971,8 +1134,11 @@
|
|||||||
this.updated = Math.round((+new Date)/1000)
|
this.updated = Math.round((+new Date)/1000)
|
||||||
this.modified = true
|
this.modified = true
|
||||||
|
|
||||||
|
console.log('Saved')
|
||||||
|
|
||||||
//Update last saved note hash
|
//Update last saved note hash
|
||||||
this.lastNoteHash = this.hashString( currentNoteText )
|
this.lastNoteHash = this.hashString( currentNoteText )
|
||||||
|
this.startAutolockTimer()
|
||||||
return resolve(true)
|
return resolve(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1010,6 +1176,8 @@
|
|||||||
},
|
},
|
||||||
hashString(text){
|
hashString(text){
|
||||||
|
|
||||||
|
text = this.noteTitle + text
|
||||||
|
|
||||||
var hash = 0;
|
var hash = 0;
|
||||||
if (text == null || text.length == 0) {
|
if (text == null || text.length == 0) {
|
||||||
return hash;
|
return hash;
|
||||||
@ -1055,7 +1223,80 @@
|
|||||||
this.$io.on('incoming_diff', incomingDiffData => {
|
this.$io.on('incoming_diff', incomingDiffData => {
|
||||||
this.patchText(incomingDiffData)
|
this.patchText(incomingDiffData)
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
decryptNote(){
|
||||||
|
|
||||||
|
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||||
|
//Remove plaintext password
|
||||||
|
this.hashedPass = hashed
|
||||||
|
this.password = ''
|
||||||
|
this.passwordConfirm = ''
|
||||||
|
|
||||||
|
this.loadNote()
|
||||||
|
},
|
||||||
|
lockNote(){
|
||||||
|
this.save().then(results => {
|
||||||
|
this.isDecrypted = false
|
||||||
|
this.password = ''
|
||||||
|
this.hashedPass = ''
|
||||||
|
this.passwordEnterVisible = false
|
||||||
|
this.setText('')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enableEncryption(){
|
||||||
|
|
||||||
|
if(this.noteText == ''){
|
||||||
|
this.noteText = 'Text Typed here is encrypted.'
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||||
|
//Remove plaintext password
|
||||||
|
this.hashedPass = hashed
|
||||||
|
|
||||||
|
this.lastNoteHash = 0
|
||||||
|
this.password = ''
|
||||||
|
this.passwordConfirm = ''
|
||||||
|
this.passwordEnterVisible = false
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
.then(results => {
|
||||||
|
this.$bus.$emit('notification', 'Password Protection Enabled')
|
||||||
|
this.loadNote()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
disableEncryption(){
|
||||||
|
|
||||||
|
this.lastNoteHash = 0
|
||||||
|
this.isEncrypted = false
|
||||||
|
this.password = ''
|
||||||
|
this.passwordConfirm = ''
|
||||||
|
this.hashedPass = ''
|
||||||
|
this.passwordEnterVisible = false
|
||||||
|
|
||||||
|
//Reload Note
|
||||||
|
this.save()
|
||||||
|
.then(results => {
|
||||||
|
this.loadNote()
|
||||||
|
this.$bus.$emit('notification', 'Password Protection Removed')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
titleResize(){
|
||||||
|
//Resize the title field
|
||||||
|
let element = this.$refs.titleTextarea
|
||||||
|
let padding = 0
|
||||||
|
|
||||||
|
element.style.height = 'auto';
|
||||||
|
element.style.height = (element.scrollHeight + padding) +'px';
|
||||||
|
},
|
||||||
|
startAutolockTimer(){
|
||||||
|
//Start autolock timer on encrypted notes that are encrypted and in a decrypted state
|
||||||
|
if(this.isEncrypted && this.isDecrypted){
|
||||||
|
clearTimeout(this.autoLockTimeout)
|
||||||
|
this.autoLockTimeout = setTimeout(() => {
|
||||||
|
this.lockNote()
|
||||||
|
}, (60 * 1000 * 20) ) //Autolock after 20 min
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -1090,7 +1331,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.stealth-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 15px 5px;
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
border: none;
|
||||||
|
font-size: 1.7em;
|
||||||
|
/*line-height: 1.7em;*/
|
||||||
|
color: var(--text_color);
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/*Settings manager styles */
|
/*Settings manager styles */
|
||||||
.all-settings {
|
.all-settings {
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="note.title == '' && note.subtext == ''">
|
<span v-if="note.title == '' && note.subtext == '' && note.encrypted == 0">
|
||||||
Empty Note
|
Empty Note
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -37,8 +37,7 @@
|
|||||||
<!-- Title display -->
|
<!-- Title display -->
|
||||||
<span v-if="note.title.length > 0"
|
<span v-if="note.title.length > 0"
|
||||||
data-test-id="title"
|
data-test-id="title"
|
||||||
class="big-text"
|
class="big-text"><p>{{ note.title }}</p></span>
|
||||||
v-html="note.title"></span>
|
|
||||||
|
|
||||||
<!-- Sub text display -->
|
<!-- Sub text display -->
|
||||||
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
|
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
|
||||||
@ -46,6 +45,12 @@
|
|||||||
class="small-text"
|
class="small-text"
|
||||||
v-html="note.subtext"></span>
|
v-html="note.subtext"></span>
|
||||||
|
|
||||||
|
|
||||||
|
<p v-if="note.encrypted == 1">
|
||||||
|
<i class="green lock icon"></i>
|
||||||
|
Locked
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Display highlights from solr results -->
|
<!-- Display highlights from solr results -->
|
||||||
<span v-if="note.note_highlights.length > 0" class="term-usage">
|
<span v-if="note.note_highlights.length > 0" class="term-usage">
|
||||||
<span
|
<span
|
||||||
|
@ -28,6 +28,11 @@
|
|||||||
<i class="green archive icon"></i>Archived
|
<i class="green archive icon"></i>Archived
|
||||||
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
|
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ui basic button" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['encryptedNotes'] > 0">
|
||||||
|
<i class="green lock alternate icon"></i>Locked
|
||||||
|
<!-- <span>{{ $store.getters.totals['encryptedNotes'] }}</span> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -679,6 +684,7 @@
|
|||||||
'withTags', // 'Only Show Notes with Tags'
|
'withTags', // 'Only Show Notes with Tags'
|
||||||
'onlyArchived', //'Only Show Archived Notes'
|
'onlyArchived', //'Only Show Archived Notes'
|
||||||
'onlyShowSharedNotes', //Only show shared notes
|
'onlyShowSharedNotes', //Only show shared notes
|
||||||
|
'onlyShowEncrypted',
|
||||||
]
|
]
|
||||||
|
|
||||||
let filter = {}
|
let filter = {}
|
||||||
|
@ -94,9 +94,9 @@ 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
|
||||||
|
|
||||||
// Object.keys(totalsObject).forEach( key => {
|
Object.keys(totalsObject).forEach( key => {
|
||||||
// console.log(key + ' -- ' + totalsObject[key])
|
console.log(key + ' -- ' + totalsObject[key])
|
||||||
// })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
109
server/helpers/CryptoString.js
Normal file
109
server/helpers/CryptoString.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
Crypto String
|
||||||
|
Securely Encrypts and decrypts a string using a password
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const IV_BYTE_SIZE = 100 // Size of initialization vector
|
||||||
|
const SALT_BYTE_SIZE = 900 // Size of salt
|
||||||
|
const KEY_BYTE_SIZE = 32 // size of cipher key
|
||||||
|
const AUTH_TAG_SIZE = 16 // Size of authentication tag
|
||||||
|
|
||||||
|
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
let CryptoString = module.exports = {}
|
||||||
|
|
||||||
|
CryptoString.encrypt = (password, salt64, rawText) => {
|
||||||
|
|
||||||
|
const initializationVector = crypto.randomBytes(IV_BYTE_SIZE)
|
||||||
|
const salt = Buffer.from(salt64, 'base64')
|
||||||
|
const key = CryptoString.seasonedPassword(password, salt)
|
||||||
|
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-gcm', key, initializationVector, { 'authTagLength': AUTH_TAG_SIZE })
|
||||||
|
let encryptedMessage = cipher.update(rawText)
|
||||||
|
|
||||||
|
encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()])
|
||||||
|
return Buffer.concat([initializationVector, encryptedMessage, cipher.getAuthTag()]).toString('base64')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Decrypt base64 string cipher text,
|
||||||
|
CryptoString.decrypt = (password, salt64, cipherTextString) => {
|
||||||
|
|
||||||
|
let cipherText = Buffer.from(cipherTextString, 'base64')
|
||||||
|
const salt = Buffer.from(salt64, 'base64')
|
||||||
|
|
||||||
|
const authTag = cipherText.slice(AUTH_TAG_SIZE*-1)
|
||||||
|
const initializationVector = cipherText.slice(0, IV_BYTE_SIZE)
|
||||||
|
const encryptedMessage = cipherText.slice(IV_BYTE_SIZE, AUTH_TAG_SIZE*-1)
|
||||||
|
|
||||||
|
const key = CryptoString.seasonedPassword(password, salt)
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-gcm', key, initializationVector, { 'authTagLength': AUTH_TAG_SIZE })
|
||||||
|
|
||||||
|
try {
|
||||||
|
decipher.setAuthTag(authTag)
|
||||||
|
|
||||||
|
let messagetext = decipher.update(encryptedMessage)
|
||||||
|
messagetext = Buffer.concat([messagetext, decipher.final()]).toString('utf8')
|
||||||
|
|
||||||
|
return messagetext
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
|
||||||
|
// console.log(err)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Salt the password - return {buffer}
|
||||||
|
CryptoString.seasonedPassword = (password, salt) => {
|
||||||
|
|
||||||
|
return crypto.scryptSync(password, salt, KEY_BYTE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create random salt - return {string}
|
||||||
|
CryptoString.createSalt = () => {
|
||||||
|
|
||||||
|
return crypto.randomBytes(SALT_BYTE_SIZE).toString('base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoString.hash = (hashString) => {
|
||||||
|
|
||||||
|
return crypto.createHash('sha256').update(hashString).digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoString.test = () => {
|
||||||
|
|
||||||
|
const pp = (title, output) => {
|
||||||
|
console.log('----------------'+title+'----------------')
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = 'ItsMePasswordio123'
|
||||||
|
const text = '<p>The genesis of  a new note. Very magical.<br></p><p>Quite a weonderful thing<br></p><p><br></p><p>Weonderful for sheore <br></p>'
|
||||||
|
|
||||||
|
const hashTest = CryptoString.createSalt('password')
|
||||||
|
|
||||||
|
const salt = CryptoString.createSalt()
|
||||||
|
pp('salt',salt.length)
|
||||||
|
|
||||||
|
const seasonPass = CryptoString.seasonedPassword(password, salt)
|
||||||
|
|
||||||
|
const cipherText = CryptoString.encrypt(password, salt, text)
|
||||||
|
|
||||||
|
const decipheredText = CryptoString.decrypt(password, salt, cipherText)
|
||||||
|
pp('Success Decrypt', decipheredText == text ? 'Pass 😁':'Fail' )
|
||||||
|
|
||||||
|
const wrongPass = CryptoString.decrypt('Wrong Password', salt, cipherText)
|
||||||
|
pp('Wrong Password', wrongPass === null ? 'Pass':'Fail')
|
||||||
|
|
||||||
|
const wrongSalt = CryptoString.decrypt(password, 'Wrong Salt', cipherText)
|
||||||
|
pp('Wrong Salt', wrongSalt === null ? 'Pass':'Fail')
|
||||||
|
|
||||||
|
const wrongCipher = CryptoString.decrypt(password, salt, Buffer.from('Hello there'))
|
||||||
|
pp('Wrong Cipher Text', wrongCipher === null ? 'Pass':'Fail')
|
||||||
|
|
||||||
|
}
|
@ -56,7 +56,7 @@ ProcessText.deduceNoteTitle = (inString) => {
|
|||||||
const tagFreeLength = ProcessText.removeHtml(inString).length
|
const tagFreeLength = ProcessText.removeHtml(inString).length
|
||||||
|
|
||||||
if(tagFreeLength < 100){
|
if(tagFreeLength < 100){
|
||||||
title = ProcessText.stripBlankHtmlLines(inString)
|
sub = ProcessText.stripBlankHtmlLines(inString)
|
||||||
return {title, sub}
|
return {title, sub}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ ProcessText.deduceNoteTitle = (inString) => {
|
|||||||
|
|
||||||
//Pull out title if its not an empty string
|
//Pull out title if its not an empty string
|
||||||
if(ProcessText.removeHtml(finalLines[0]).trim().replace(' ','').length > 0 && !noTitleJustList){
|
if(ProcessText.removeHtml(finalLines[0]).trim().replace(' ','').length > 0 && !noTitleJustList){
|
||||||
title = finalLines.shift()
|
// title = finalLines.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
sub = finalLines.join('')
|
sub = finalLines.join('')
|
||||||
|
@ -8,7 +8,8 @@ let ProcessText = require('@helpers/ProcessText')
|
|||||||
|
|
||||||
const DiffMatchPatch = require('@helpers/DiffMatchPatch')
|
const DiffMatchPatch = require('@helpers/DiffMatchPatch')
|
||||||
|
|
||||||
var rp = require('request-promise');
|
const cs = require('@helpers/CryptoString')
|
||||||
|
const rp = require('request-promise');
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
let Note = module.exports = {}
|
let Note = module.exports = {}
|
||||||
@ -98,7 +99,7 @@ Note.stressTest = () => {
|
|||||||
|
|
||||||
// --------------
|
// --------------
|
||||||
|
|
||||||
Note.create = (userId, noteText, quickNote = 0) => {
|
Note.create = (userId, noteTitle, noteText, quickNote = 0, ) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
if(userId == null || userId < 10){ reject('User Id required to create note') }
|
if(userId == null || userId < 10){ reject('User Id required to create note') }
|
||||||
@ -106,7 +107,7 @@ Note.create = (userId, noteText, quickNote = 0) => {
|
|||||||
const created = Math.round((+new Date)/1000)
|
const created = Math.round((+new Date)/1000)
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(`INSERT INTO note_raw_text (text, updated) VALUE (?, ?)`, [noteText, created])
|
.query(`INSERT INTO note_raw_text (text, title, updated) VALUE (?, ?, ?)`, [noteText, noteTitle, created])
|
||||||
.then( (rows, fields) => {
|
.then( (rows, fields) => {
|
||||||
|
|
||||||
const rawTextId = rows[0].insertId
|
const rawTextId = rows[0].insertId
|
||||||
@ -129,7 +130,11 @@ Note.reindex = (userId, noteId) => {
|
|||||||
Note.get(userId, noteId)
|
Note.get(userId, noteId)
|
||||||
.then(note => {
|
.then(note => {
|
||||||
|
|
||||||
const noteText = note.text
|
let noteText = note.text
|
||||||
|
if(note.encrypted == 1){
|
||||||
|
noteText = '' //Don't put note text in encrypted notes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Update Solr index
|
// Update Solr index
|
||||||
@ -137,7 +142,7 @@ Note.reindex = (userId, noteId) => {
|
|||||||
Tags.string(userId, noteId)
|
Tags.string(userId, noteId)
|
||||||
.then(tagString => {
|
.then(tagString => {
|
||||||
|
|
||||||
const fullText = ProcessText.removeHtml(noteText) +' '+ tagString
|
const fullText = note.title + ' ' + ProcessText.removeHtml(noteText) +' '+ tagString
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(`
|
.query(`
|
||||||
@ -158,33 +163,59 @@ Note.reindex = (userId, noteId) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Note.update = (io, userId, noteId, noteText, color, pinned, archived) => {
|
Note.update = (io, userId, noteId, noteText, noteTitle, color, pinned, archived, password = '', passwordHint = '') => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
//Prevent note loss if it saves with empty text
|
//Prevent note loss if it saves with empty text
|
||||||
if(ProcessText.removeHtml(noteText) == ''){
|
if(ProcessText.removeHtml(noteText) == ''){
|
||||||
console.log('Not saving empty note')
|
// console.log('Not saving empty note')
|
||||||
resolve(false)
|
// resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Math.round((+new Date)/1000)
|
const now = Math.round((+new Date)/1000)
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('SELECT note_raw_text_id FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
|
.query(`
|
||||||
|
SELECT note_raw_text_id, salt FROM note
|
||||||
|
JOIN note_raw_text ON note_raw_text_id = note_raw_text.id
|
||||||
|
WHERE note.id = ? AND user_id = ?`, [noteId, userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
const textId = rows[0][0]['note_raw_text_id']
|
const textId = rows[0][0]['note_raw_text_id']
|
||||||
|
let salt = rows[0][0]['salt']
|
||||||
|
|
||||||
|
//If password is removed, remove salt. generate a new one next time its encrypted
|
||||||
|
if(password.length == 0){
|
||||||
|
salt = null
|
||||||
|
}
|
||||||
|
|
||||||
|
//If a password is set, create a salt
|
||||||
|
if(password.length > 3 && !salt){
|
||||||
|
salt = cs.createSalt()
|
||||||
|
|
||||||
|
//Save password hint on first encryption
|
||||||
|
if(passwordHint.length > 0){
|
||||||
|
db.promise().query('UPDATE note_raw_text SET password_hint = ? WHERE id = ?', [passwordHint, textId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Encrypt note text if proper data is setup
|
||||||
|
if(password.length > 3 && salt.length > 1000){
|
||||||
|
noteText = cs.encrypt(password, salt, noteText)
|
||||||
|
}
|
||||||
|
|
||||||
//Update Note text
|
//Update Note text
|
||||||
return db.promise()
|
return db.promise()
|
||||||
.query('UPDATE note_raw_text SET text = ?, updated = ? WHERE id = ?', [noteText, now, textId])
|
.query('UPDATE note_raw_text SET text = ?, title = ?, updated = ?, salt = ? WHERE id = ?', [noteText, noteTitle, now, salt, textId])
|
||||||
})
|
})
|
||||||
.then( (rows, fields) => {
|
.then( (rows, fields) => {
|
||||||
|
|
||||||
|
const encrypted = password.length > 3 ? 1:0
|
||||||
|
|
||||||
//Update other note attributes
|
//Update other note attributes
|
||||||
return db.promise()
|
return db.promise()
|
||||||
.query('UPDATE note SET pinned = ?, archived = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
.query('UPDATE note SET pinned = ?, archived = ?, color = ?, encrypted = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
||||||
[pinned, archived, color, noteId, userId])
|
[pinned, archived, color, encrypted, noteId, userId])
|
||||||
|
|
||||||
})
|
})
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
@ -309,6 +340,9 @@ Note.getDiffText = (userId, noteId, usersCurrentText, lastUpdated) => {
|
|||||||
Note.get(userId, noteId)
|
Note.get(userId, noteId)
|
||||||
.then(noteObject => {
|
.then(noteObject => {
|
||||||
|
|
||||||
|
if(!noteObject.text || !usersCurrentText){
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
|
||||||
let oldText = noteObject.text.replace(/(\r\n|\n|\r)/gm,"")
|
let oldText = noteObject.text.replace(/(\r\n|\n|\r)/gm,"")
|
||||||
let newText = usersCurrentText.replace(/(\r\n|\n|\r)/gm,"")
|
let newText = usersCurrentText.replace(/(\r\n|\n|\r)/gm,"")
|
||||||
@ -354,17 +388,23 @@ Note.getDiffText = (userId, noteId, usersCurrentText, lastUpdated) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Note.get = (userId, noteId) => {
|
Note.get = (userId, noteId, password = '') => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(`
|
.query(`
|
||||||
SELECT
|
SELECT
|
||||||
|
note_raw_text.title,
|
||||||
note_raw_text.text,
|
note_raw_text.text,
|
||||||
|
note_raw_text.salt,
|
||||||
|
note_raw_text.password_hint,
|
||||||
note_raw_text.updated as updated,
|
note_raw_text.updated as updated,
|
||||||
|
note_raw_text.decrypt_attempts_count,
|
||||||
|
note_raw_text.last_decrypted_date,
|
||||||
note.created,
|
note.created,
|
||||||
note.pinned,
|
note.pinned,
|
||||||
note.archived,
|
note.archived,
|
||||||
note.color,
|
note.color,
|
||||||
|
note.encrypted,
|
||||||
count(distinct attachment.id) as attachment_count,
|
count(distinct attachment.id) as attachment_count,
|
||||||
note.note_raw_text_id as rawTextId,
|
note.note_raw_text_id as rawTextId,
|
||||||
shareUser.username as shareUsername
|
shareUser.username as shareUsername
|
||||||
@ -372,15 +412,74 @@ Note.get = (userId, noteId) => {
|
|||||||
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||||
LEFT JOIN attachment ON (note.id = attachment.note_id)
|
LEFT JOIN attachment ON (note.id = attachment.note_id)
|
||||||
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
|
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
|
||||||
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId])
|
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, noteId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
const created = Math.round((+new Date)/1000)
|
const nowTime = Math.round((+new Date)/1000)
|
||||||
|
let noteLockedOut = false
|
||||||
|
let noteData = rows[0][0]
|
||||||
|
const rawTextId = noteData['rawTextId']
|
||||||
|
noteData.decrypted = true
|
||||||
|
|
||||||
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [created, noteId])
|
|
||||||
|
//If this is not and encrypted note, pass decrypted true, skip encryption stuff
|
||||||
|
if(noteData.encrypted == 1){
|
||||||
|
noteData.decrypted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//Rate Limiting
|
||||||
|
//
|
||||||
|
//Check if note is exceeding decrypt attempt limit
|
||||||
|
if(noteData.encrypted == 1){
|
||||||
|
const timeSinceLastUnlock = nowTime - noteData.last_decrypted_date
|
||||||
|
|
||||||
|
//To many attempts in less than 5 minutes, note is locked
|
||||||
|
if(noteData.decrypt_attempts_count > 3 && timeSinceLastUnlock < 300){
|
||||||
|
console.log('Locked Out')
|
||||||
|
noteLockedOut = true
|
||||||
|
}
|
||||||
|
|
||||||
|
//its been 5 minutes, reset attempt count
|
||||||
|
if(noteData.decrypt_attempts_count > 0 && timeSinceLastUnlock > 300){
|
||||||
|
noteLockedOut = false
|
||||||
|
noteData.decrypt_attempts_count = 0
|
||||||
|
console.log('Resseting Lockout')
|
||||||
|
db.promise().query('UPDATE note_raw_text SET last_decrypted_date = ?, decrypt_attempts_count = 0 WHERE id = ?', [nowTime, rawTextId ])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Note is encrypted, lets try and decipher it with the given password
|
||||||
|
if(password.length > 3 && noteData.encrypted == 1 && !noteLockedOut){
|
||||||
|
|
||||||
|
const decipheredText = cs.decrypt(password, noteData.salt, noteData.text)
|
||||||
|
|
||||||
|
//Text was decrypted, return decrypted text
|
||||||
|
if(decipheredText !== null){
|
||||||
|
noteData.decrypted = true
|
||||||
|
noteData.text = decipheredText
|
||||||
|
|
||||||
|
//Save last decrypted date, reset decrypt atempts
|
||||||
|
db.promise().query('UPDATE note_raw_text SET last_decrypted_date = ?, decrypt_attempts_count = 0 WHERE id = ?', [nowTime, rawTextId ])
|
||||||
|
|
||||||
|
}
|
||||||
|
//Text was not deciphered, delete object, never return cipher text
|
||||||
|
if(decipheredText === null){
|
||||||
|
noteData.text = '' //Never return cipher text
|
||||||
|
noteData.decryptFail = true
|
||||||
|
noteData.decrypt_attempts_count++ //Update display for user
|
||||||
|
|
||||||
|
//Update decrypt attempts
|
||||||
|
db.promise().query('UPDATE note_raw_text SET decrypt_attempts_count = decrypt_attempts_count +1 WHERE id = ?', [rawTextId ])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
|
||||||
|
|
||||||
//Return note data
|
//Return note data
|
||||||
resolve(rows[0][0])
|
delete noteData.salt //remove salt from return data
|
||||||
|
noteData.lockedOut = noteLockedOut
|
||||||
|
resolve(noteData)
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
@ -484,6 +583,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|||||||
let noteSearchQuery = `
|
let noteSearchQuery = `
|
||||||
SELECT note.id,
|
SELECT note.id,
|
||||||
SUBSTRING(note_raw_text.text, 1, 1500) as text,
|
SUBSTRING(note_raw_text.text, 1, 1500) as text,
|
||||||
|
note_raw_text.title as title,
|
||||||
note_raw_text.updated as updated,
|
note_raw_text.updated as updated,
|
||||||
opened,
|
opened,
|
||||||
color,
|
color,
|
||||||
@ -491,6 +591,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|||||||
count(distinct attachment.id) as attachment_count,
|
count(distinct attachment.id) as attachment_count,
|
||||||
note.pinned,
|
note.pinned,
|
||||||
note.archived,
|
note.archived,
|
||||||
|
note.encrypted,
|
||||||
GROUP_CONCAT(DISTINCT tag.text) as tags,
|
GROUP_CONCAT(DISTINCT tag.text) as tags,
|
||||||
GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
|
GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
|
||||||
shareUser.username as shareUsername,
|
shareUser.username as shareUsername,
|
||||||
@ -527,6 +628,10 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|||||||
searchAllNotes = true
|
searchAllNotes = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(fastFilters.onlyShowEncrypted == 1){
|
||||||
|
noteSearchQuery += ' AND encrypted = 1'
|
||||||
|
}
|
||||||
|
|
||||||
//If tags are passed, use those tags in search
|
//If tags are passed, use those tags in search
|
||||||
if(searchTags.length > 0){
|
if(searchTags.length > 0){
|
||||||
searchParams.push(searchTags)
|
searchParams.push(searchTags)
|
||||||
@ -610,14 +715,18 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
|||||||
noteIds.push(note.id)
|
noteIds.push(note.id)
|
||||||
|
|
||||||
if(note.text == null){ note.text = '' }
|
if(note.text == null){ note.text = '' }
|
||||||
|
if(note.encrypted == 1){ note.text = '' }
|
||||||
|
|
||||||
//Deduce note title
|
//Deduce note title
|
||||||
const textData = ProcessText.deduceNoteTitle(note.text)
|
const textData = ProcessText.deduceNoteTitle(note.text)
|
||||||
// console.log(textData)
|
// console.log(textData)
|
||||||
|
|
||||||
// console.log(textData)
|
// console.log(textData)
|
||||||
|
|
||||||
|
if(note.title == null){
|
||||||
|
note.title = ''
|
||||||
|
}
|
||||||
|
|
||||||
note.title = textData.title
|
|
||||||
note.subtext = textData.sub
|
note.subtext = textData.sub
|
||||||
note.titleLength = textData.titleLength
|
note.titleLength = textData.titleLength
|
||||||
note.subtextLength = textData.subtextLength
|
note.subtextLength = textData.subtextLength
|
||||||
|
@ -71,7 +71,7 @@ QuickNote.update = (userId, pushText) => {
|
|||||||
let newText = broken +''+ d.text
|
let newText = broken +''+ d.text
|
||||||
|
|
||||||
//Save that, then return the new text
|
//Save that, then return the new text
|
||||||
Note.update(null, userId, d.id, newText, d.color, d.pinned, d.archived)
|
Note.update(null, userId, d.id, newText, '', d.color, d.pinned, d.archived)
|
||||||
.then( saveResults => {
|
.then( saveResults => {
|
||||||
resolve({
|
resolve({
|
||||||
id:d.id,
|
id:d.id,
|
||||||
|
@ -124,11 +124,12 @@ User.getCounts = (userId) => {
|
|||||||
`SELECT
|
`SELECT
|
||||||
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes,
|
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes,
|
||||||
SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes,
|
SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes,
|
||||||
|
SUM(encrypted = 1) AS encryptedNotes,
|
||||||
SUM(share_user_id IS NULL) AS totalNotes,
|
SUM(share_user_id IS NULL) AS totalNotes,
|
||||||
SUM(share_user_id != ?) AS sharedToNotes,
|
SUM(share_user_id != ?) AS sharedToNotes,
|
||||||
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes
|
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes
|
||||||
FROM note
|
FROM note
|
||||||
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||||
WHERE user_id = ?`, [userId, userId, userId, userId])
|
WHERE user_id = ?`, [userId, userId, userId, userId])
|
||||||
.then( (rows, fields) => {
|
.then( (rows, fields) => {
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ router.use(function setUserId (req, res, next) {
|
|||||||
//
|
//
|
||||||
router.post('/get', function (req, res) {
|
router.post('/get', function (req, res) {
|
||||||
// req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
|
// req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
|
||||||
Notes.get(userId, req.body.noteId)
|
Notes.get(userId, req.body.noteId, req.body.password)
|
||||||
.then( data => {
|
.then( data => {
|
||||||
//Join room when user opens note
|
//Join room when user opens note
|
||||||
// req.io.join('note_room')
|
// req.io.join('note_room')
|
||||||
@ -38,12 +38,12 @@ router.post('/delete', function (req, res) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.post('/create', function (req, res) {
|
router.post('/create', function (req, res) {
|
||||||
Notes.create(userId, req.body.title)
|
Notes.create(userId, req.body.title, req.body.text)
|
||||||
.then( id => res.send({id}) )
|
.then( id => res.send({id}) )
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/update', function (req, res) {
|
router.post('/update', function (req, res) {
|
||||||
Notes.update(req.io, userId, req.body.noteId, req.body.text, req.body.color, req.body.pinned, req.body.archived)
|
Notes.update(req.io, userId, req.body.noteId, req.body.text, req.body.title, req.body.color, req.body.pinned, req.body.archived, req.body.password, req.body.hint)
|
||||||
.then( id => res.send({id}) )
|
.then( id => res.send({id}) )
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -12,4 +12,4 @@
|
|||||||
# z - Compress for speed
|
# z - Compress for speed
|
||||||
# h - Human Readable file sizes
|
# h - Human Readable file sizes
|
||||||
|
|
||||||
rsync -e 'ssh' --exclude-from=dontSync.txt -havzC --update mab@marvin.local:/home/mab/pi/ /Users/maxgialanella/Code/privateInternet
|
rsync -e 'ssh' --exclude-from=dontSync.txt -havzC --update mab@marvin.local:/home/mab/pi/ .
|
||||||
|
Loading…
Reference in New Issue
Block a user