* Added theme colors to form fields

* Added some basic table styles for inserting some shitty tables
* Made popup notification styles look better and work better on mobile
* Quick note now opens a note and not some weird page
* Menu collapses when page is small, behaves like mobile menu
* Added terms and conditions to help and login forms
* Added password change functionality
* Better styles for shared page
* Added some tests for changing password
This commit is contained in:
Max G 2020-07-14 05:31:02 +00:00
parent 06b8f0ad6a
commit a8a966866c
16 changed files with 423 additions and 142 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -86,6 +86,8 @@ body {
text-align: center; text-align: center;
} }
.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]),
@ -94,6 +96,21 @@ body {
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
border-color: var(--dark_border_color); border-color: var(--dark_border_color);
} }
.ui.form input[type="password"],
.ui.form input[type="text"],
.ui.input > input {
color: var(--text_color);
background-color: var(--small_element_bg_color);
border-color: var(--dark_border_color);
}
.ui.form input[type="password"]:focus, .ui.form input[type="password"]:active,
.ui.form input[type="text"]:focus, .ui.form input[type="text"]:active,
.ui.input > input:focus, .ui.input > input:active {
color: var(--text_color);
background-color: var(--small_element_bg_color);
border-color: var(--main-accent);
border-right-color: var(--main-accent) !important;
}
.ui.basic.label, .ui.header, .ui.header div.sub.header { .ui.basic.label, .ui.header, .ui.header div.sub.header {
color: var(--text_color); color: var(--text_color);
background-color: transparent; background-color: transparent;
@ -358,10 +375,15 @@ i.green.icon.icon.icon.icon {
border-collapse: collapse; border-collapse: collapse;
} }
tr {
display: flex;
}
th, td { th, td {
border: 1px solid #ddd; border: 1px solid #ddd;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
font-weight: normal; font-weight: normal;
flex: 1;
} }
/* table:hover th, table:hover td { /* table:hover th, table:hover td {
border: 1px solid black; border: 1px solid black;
@ -371,6 +393,22 @@ i.green.icon.icon.icon.icon {
padding: 3px; padding: 3px;
text-align: left; text-align: left;
} }
.table-tic-table {
}
.table-tic-table > div {
height: 21px;
margin: 0;
padding: 0;
}
.tabletic {
display: inline-block;
border: 1px solid black;
border-radius: 2px;
width: 20px;
height: 20px;
margin: 0 1px 1px 0;
cursor: pointer;
}
.t-table { .t-table {
width: 100%; width: 100%;
@ -430,7 +468,7 @@ i.green.icon.icon.icon.icon {
font-family: 'Icons'; font-family: 'Icons';
content: "\f058"; content: "\f058";
color: #21BA45; color: var(--main-accent);
opacity: 1; opacity: 1;
} }
} }

View File

@ -2,30 +2,31 @@
.popup-body { .popup-body {
position: fixed; position: fixed;
bottom: 15px; top: 15px;
left: 15px; left: 15px;
min-height: 50px; min-height: 50px;
min-width: 200px; min-width: 200px;
max-width: calc(100% - 20px); max-width: calc(100% - 30px);
z-index: 1002; z-index: 1002;
border-top: 2px solid #21ba45;
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1); box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
border-top-right-radius: 4px; border-radius: 4px;
border-top-left-radius: 4px;
color: white;
background-color: var(--main-accent);
} }
.popup-row { .popup-row {
padding: 1em 5px; padding: 1em 5px;
cursor: pointer; cursor: pointer;
white-space: nowrap;
} }
.popup-row > span { .popup-row > span {
width: calc(100% - 50px); /*width: calc(100% - 50px);*/
display: inline-block; display: inline-block;
text-align: center; text-align: left;
box-sizing: border-box; box-sizing: border-box;
padding: 0 10px 0; padding: 0 10px 0;
font-size: 1.25em; font-size: 1.25em;
border-radius: 4px;
} }
.popup-row + .popup-row { .popup-row + .popup-row {
border-top: 1px solid #FFF; border-top: 1px solid #FFF;
@ -36,12 +37,10 @@
} }
@keyframes slide-in-bottom { @keyframes slide-in-bottom {
0% { 0% {
transform: translateY(1000px); transform: translateY(-1000px);
opacity: 0;
} }
100% { 100% {
transform: translateY(0); transform: translateY(0);
opacity: 1;
} }
} }
@ -63,14 +62,46 @@
} }
} }
.meter {
height: 2px;
display: inline-block;
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
overflow: hidden;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.meter span {
display: block;
height: 100%;
}
.progress {
background-color: white;
animation: progressBar 3s linear;
animation-fill-mode: both;
}
@keyframes progressBar {
0% { width: 0; }
100% { width: 100%; }
}
</style> </style>
<template> <template>
<div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0"> <div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0">
<div class="popup-row color-fade" v-for="item in notifications"> <div class="popup-row" v-for="item in notifications">
<span>{{ item }}</span> <div class="meter">
<span><span class="progress"></span></span>
</div>
<span><i class="small info circle icon"></i>{{ item }}</span>
</div> </div>
</div> </div>
</template> </template>
@ -94,9 +125,9 @@
}, },
mounted(){ mounted(){
// this.$bus.$emit('notification', 'Password Protection Removed') // this.$bus.$emit('notification', 'Password Protection Removed Login did not succeed')
// this.$bus.$emit('notification', 'Password Protection Removed') // this.$bus.$emit('notification', 'Password Protection Removed your life is exposed to the internet')
// this.$bus.$emit('notification', 'Password Protection Removed') // this.$bus.$emit('notification', 'Password Protection Removed everyone can see everything')
}, },
methods: { methods: {
@ -105,7 +136,7 @@
clearTimeout(this.totalTimeout) clearTimeout(this.totalTimeout)
this.totalTimeout = setTimeout(() => { this.totalTimeout = setTimeout(() => {
this.dismiss() this.dismiss()
}, 4000) }, 3000)
}, },
dismiss(){ dismiss(){
this.notifications = [] this.notifications = []

View File

@ -133,10 +133,24 @@
<div class="mobile-button"></div> <div class="mobile-button"></div>
<router-link v-if="loggedIn" to="/quick" class="mobile-button" exact-active-class="active"> <!-- open straight to note -->
<router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
exact-active-class="active"
class="mobile-button"
:to="`/notes/open/${$store.getters.totals['quickNote']}`">
<i class="green sticky note outline icon"></i> <i class="green sticky note outline icon"></i>
</router-link> </router-link>
<!-- create new and redirect to new note id -->
<a
v-if="loggedIn && $store.getters.totals && !$store.getters.totals['quickNote']"
v-on:click="newQuickNote()"
exact-active-class="active"
class="mobile-button">
<i class="green sticky note outline icon"></i>
</a>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()"> <router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()">
<logo class="logo-display" color="var(--main-accent)" /> <logo class="logo-display" color="var(--main-accent)" />
</router-link> </router-link>
@ -167,10 +181,8 @@
<div class="global-menu" v-if="!collapsed" v-on:click="menuClicked"> <div class="global-menu" v-if="!collapsed" v-on:click="menuClicked">
<div class="menu-section" v-on:click="collapseMenu"> <div class="menu-section" v-on:click="collapseMenu">
<!-- <div class="menu-item menu-button" > --> <i class="white angle left icon"></i>
<i class="white angle left icon"></i> <logo class="menu-logo-display" color="var(--main-accent)" />
<logo class="menu-logo-display" color="var(--main-accent)" />
<!-- </div> -->
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
@ -216,9 +228,26 @@
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<router-link v-if="loggedIn" exact-active-class="active" class="menu-item menu-button" to="/quick">
<!-- open straight to note -->
<router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
exact-active-class="active"
class="menu-item menu-button"
:to="`/notes/open/${$store.getters.totals['quickNote']}`">
<i class="sticky note outline icon"></i>Scratch Pad <i class="sticky note outline icon"></i>Scratch Pad
</router-link> </router-link>
<!-- create new and redirect to new note id -->
<a
v-if="loggedIn && $store.getters.totals && !$store.getters.totals['quickNote']"
v-on:click="newQuickNote()"
exact-active-class="active"
class="menu-item menu-button">
<i class="sticky note outline icon"></i>Scratch Pad
</a>
</div> </div>
<div class="menu-section" v-if="!loggedIn"> <div class="menu-section" v-if="!loggedIn">
@ -244,7 +273,7 @@
<div class="menu-section"> <div class="menu-section">
<router-link class="menu-item menu-button" exact-active-class="active" to="/help"> <router-link class="menu-item menu-button" exact-active-class="active" to="/help">
<i class="question circle outline icon"></i>Help / Terms <i class="question circle outline icon"></i>Help | Terms
</router-link> </router-link>
</div> </div>
@ -305,6 +334,8 @@
this.version = localStorage.getItem('currentVersion') this.version = localStorage.getItem('currentVersion')
} }
this.resizeEventHandler() //Trigger resize event
}, },
computed: { computed: {
loggedIn () { loggedIn () {
@ -329,16 +360,29 @@
}, },
}, },
methods: { methods: {
newQuickNote(){
axios.post('/api/quick-note/get')
.then( ({data}) => {
console.log(data)
this.$router.push({'path':'/notes/open/'+data.noteId})
})
},
resizeEventHandler(e) { resizeEventHandler(e) {
clearTimeout(this.resizeDebounce) clearTimeout(this.resizeDebounce)
this.resizeDebounce = setTimeout(() => { this.resizeDebounce = setTimeout(() => {
this.mobile = false
this.menuOpen = false this.menuOpen = false
this.collapsed = false this.collapsed = false
if(window.innerWidth < 700){ if(window.innerWidth < 700){
this.collapsed = true this.collapsed = true
this.mobile = true
} }
}, 100) }, 100)
}, },

View File

@ -69,10 +69,6 @@
<i class="grip lines icon"></i> <i class="grip lines icon"></i>
</div> </div>
<div class="edit-button" v-on:click="insertTable(4,4)" data-tooltip="Insert Table" data-position="bottom center" data-inverted>
<i class="book dead icon"></i>
</div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center" data-inverted>
<i class="remove format icon"></i> <i class="remove format icon"></i>
</div> </div>
@ -276,7 +272,14 @@
<!-- create table option --> <!-- create table option -->
<side-slide-menu v-if="table" v-on:close="table = false; fetchNoteTags()" name="table" :style-object="styleObject"> <side-slide-menu v-if="table" v-on:close="table = false; fetchNoteTags()" name="table" :style-object="styleObject">
<div class="ui basic segment"> <div class="ui basic segment">
Create a table <h2>Insert Table</h2>
<div class="table-tic-table">
<div v-for="i in 10">
<div v-for="j in 10" class="tabletic" v-on:click="insertTable(i,j)">
</div>
</div>
</div>
</div> </div>
</side-slide-menu> </side-slide-menu>

View File

@ -12,6 +12,7 @@
<ul> <ul>
<li>Shared notes can be read and edited by you and all shared users.</li> <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> <li>Shared notes can only be shared by the creator of the note.</li>
<li>If you turn off sharing, no one else can read the note.</li>
</ul> </ul>
</div> </div>

View File

@ -345,62 +345,26 @@ const SquireButtonFunctions = {
this.editor.focus() this.editor.focus()
this.editor.moveCursorToEnd() this.editor.moveCursorToEnd()
}, },
insertTable(wide, tall){ insertTable(tall, wide){
console.log('Insert a table') console.log(`Table: ${wide} x ${tall}`)
let tableSyntax = ` //Insert a table
<div> let tableSyntax = '<div>'
<table> tableSyntax += '<table>'
<tr>
<th><p><br></p></th>
<th><p><br></p></th>
</tr>
<tr>
<td><p><br></p></td>
<td><p><br></p></td>
</tr>
<tr>
<td><p><br></p></td>
<td><p><br></p></td>
</tr>
</table>
</div>
`
tableSyntax = `
<span class="t-table">
<span>
<span>
<p><br></p>
</span>
<span>
<p><br></p>
</span>
</span>
<span>
<span>
<p><br></p>
</span>
<span>
<p><br></p>
</span>
</span>
</span>
<p><br></p>
`
tableSyntax = ''
tableSyntax += '<span class="t-table">'
for (let i = 0; i < tall; i++) { for (let i = 0; i < tall; i++) {
tableSyntax += '<tr>'
for (let j = 0; j < wide; j++) { for (let j = 0; j < wide; j++) {
tableSyntax += '<td><p><br></p></td>'
} }
tableSyntax += '</tr>'
} }
tableSyntax += '</span><p><br></p>' tableSyntax += '</table></div><p><br></p>'
this.editor.insertHTML(tableSyntax) this.editor.insertHTML(tableSyntax)
this.editor.focus() this.editor.focus()
this.editor.moveCursorToEnd() this.editor.moveCursorToEnd()
this.$router.go(-1)
}, },
}, },
} }

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,23 @@
Settings Settings
</h2> </h2>
<div class="ui segment">
<h3>Change Password</h3>
<p>Create a new scratch pad. Old scratch pad will turn into a normal note.</p>
<div class="ui compact basic button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
<i class="sync alternate reload icon"></i>
New Scratch Pad
</div>
<div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="showNewNoteConfirm = false">
<i class="close icon"></i>
Cancel
</div>
<div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="newQuickNote()">
<i class="green thumbs up icon"></i>
Confirm
</div>
</div>
<!-- Accent Color --> <!-- Accent Color -->
<div class="ui segment"> <div class="ui segment">
<div class="ui grid"> <div class="ui grid">
@ -65,20 +82,20 @@
<div class="five wide column"> <div class="five wide column">
<label>Current Password</label> <label>Current Password</label>
<div class="ui fluid input"> <div class="ui fluid input">
<input type="text" placeholder="Current Password"> <input v-model="change1" type="password" placeholder="Current Password">
</div> </div>
</div> </div>
<div class="five wide column"> <div class="five wide column">
<label>New Password</label> <label>New Password</label>
<div class="ui fluid input"> <div class="ui fluid input">
<input type="text" placeholder="New Password"> <input v-model="change2" type="password" placeholder="New Password">
</div> </div>
</div> </div>
<div class="six wide column"> <div class="six wide column">
<label>Rereat New Password</label> <label>Rereat New Password</label>
<div class="ui fluid action input"> <div class="ui fluid action input">
<input type="text" placeholder="Repeat Password"> <input v-model="change3" type="password" placeholder="Repeat Password">
<div class="ui green button"> <div v-on:click="passwordChange()" class="ui button" :class="{'green':(change1.length > 0 && change2 == change3)}">
Change it! Change it!
</div> </div>
</div> </div>
@ -98,7 +115,7 @@
</div> </div>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui button"> <div class="ui button" v-on:click="revokeAllSessions()">
Log Out all other browsers Log Out all other browsers
</div> </div>
</div> </div>
@ -123,6 +140,7 @@
password: '', password: '',
qrCode: '', qrCode: '',
verificationToken: '', verificationToken: '',
showNewNoteConfirm: false,
themeColors: [ themeColors: [
'#21BA45', //Green '#21BA45', //Green
@ -137,10 +155,26 @@
'#fbbd08', //Yellow '#fbbd08', //Yellow
'#767676', //Grey '#767676', //Grey
'#303030', //Black-almost '#303030', //Black-almost
] ],
change1: '',
change2: '',
change3: '',
} }
}, },
methods: { methods: {
newQuickNote(){
this.showNewNoteConfirm = true
axios.post('/api/quick-note/new')
.then( ({data}) => {
this.showNewNoteConfirm = false
this.$store.dispatch('fetchAndUpdateUserTotals')
this.$bus.$emit('notification', 'New Scratch Pad Created')
})
},
logout() { logout() {
this.$store.commit('destroyLoginToken') this.$store.commit('destroyLoginToken')
this.$router.push('/') this.$router.push('/')
@ -176,6 +210,47 @@
//It failed //It failed
} }
}) })
},
passwordChange(){
if(this.change1 == '' || this.change2 == '' || this.change3 == ''){
this.$bus.$emit('notification', 'All Password Fields Required')
return
}
if(this.change1 == this.change2){
this.$bus.$emit('notification', 'Old password matches new password')
return
}
if(this.change2 != this.change3){
this.$bus.$emit('notification', 'New Passwords do not match')
return
}
const postData = {
'currentPass':this.change1,
'newPass':this.change3
}
axios.post('/api/user/changepassword', postData)
.then(({data}) => {
if(data){
this.$bus.$emit('notification', 'Success: Password Changed')
this.change1 = ''
this.change2 = ''
this.change3 = ''
} else {
this.$bus.$emit('notification', 'Failed to change password')
this.change1 = ''
}
})
},
revokeAllSessions(){
axios.post('/api/user/revokesessions')
.then(({data}) => {
this.$bus.$emit('notification', 'All other active sessions revoked.')
})
} }
} }
} }

View File

@ -4,38 +4,55 @@
<div class="sixteen wide column"></div> <div class="sixteen wide column"></div>
<div class="sixteen wide column" v-if="text.length > 0 || title.length > 0"> <div class="sixteen wide column" v-if="text.length > 0 || title.length > 0">
<div class="ui text container squire-box" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> <div class="ui text container">
<h1 v-if="title">{{title}}</h1> <div class="ui segment" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<div v-if="text" v-html="text"></div> <h1 v-if="title">{{title}}</h1>
<div v-if="text" v-html="text" class="squire-box"></div>
</div>
</div> </div>
</div> </div>
<div class="sixteen wide column" v-if="!$store.getters.getLoggedIn"> <div class="sixteen wide column" v-if="!$store.getters.getLoggedIn">
<div class="ui text container"> <div class="ui text container">
<h2 class="ui header">
<img class="small-logo" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo"> <div class="ui segment">
<div class="content">
Solid Scribe is an easy, free, secure Note App <div class="ui grid">
<div class="sub header"> <div class="three wide middle aligned center aligned column">
Encrypted notes, only readable by you. Unless you share them. <img class="small-logo" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
</div>
<div class="thirteen wide column">
<!-- header -->
<h2 class="ui header">
<div class="content">
Solid Scribe is an easy, free, secure Note App
<div class="sub header">
Encrypted notes, only readable by you. Unless you share them.
</div>
</div>
</h2>
<!-- buttons -->
<div class="ui grid">
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/login">
<i class="plug icon"></i>Sign Up
</router-link>
</div>
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/">
<i class="comment outline icon"></i>
Learn More
</router-link>
</div>
</div>
</div> </div>
</div> </div>
</h2>
<div class="ui grid">
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/login">
<i class="plug icon"></i>Sign Up
</router-link>
</div>
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/">
<i class="comment outline icon"></i>
Learn More
</router-link>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -99,7 +116,7 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.small-logo { .small-logo {
width: 30px; width: 100%;
height: auto; height: auto;
} }
</style> </style>

View File

@ -7,6 +7,7 @@ let Auth = {}
const tokenSecretKey = process.env.JSON_KEY const tokenSecretKey = process.env.JSON_KEY
//Creates session token
Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -42,6 +43,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
}) })
} }
//Decodes session token
Auth.decodeToken = (token, request = null) => { Auth.decodeToken = (token, request = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -263,7 +263,7 @@ let AuthTest = require('@helpers/Auth')
Auth.testTwoFactor() Auth.testTwoFactor()
Auth.test() Auth.test()
UserTest.keyPairTest('genMan16', '1', printResults) UserTest.keyPairTest('genMan23', '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)

View File

@ -10,19 +10,28 @@ QuickNote.get = (userId, masterKey) => {
db.promise() db.promise()
.query(` .query(`
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1 SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId])
`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
//Quick Note is set, return note text //Quick Note is set, return note text
if(rows[0][0] != undefined){ if(rows[0][0] != undefined){
let noteId = rows[0][0].id let noteId = rows[0][0].id
Note.get(userId, noteId, masterKey) return resolve({'noteId':noteId})
.then( noteObject => {
return resolve(noteObject)
})
} else { } else {
return resolve(null) //Or create a new note and get the id
let finalId = null
return Note.create(userId, 'Scratch Pad', '', masterKey)
.then(insertedId => {
finalId = insertedId
db.promise().query('UPDATE note SET quick_note = 1 WHERE id = ? AND user_id = ?',[insertedId, userId])
.then((rows, fields) => {
return resolve({'noteId':finalId})
})
})
} }

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.1.3' const version = '3.1.5'
//Login a user, if that user does not exist create them //Login a user, if that user does not exist create them
//Issues login token //Issues login token
@ -238,7 +238,7 @@ User.getCounts = (userId) => {
Object.assign(countTotals, rows[0][0]) //combine results Object.assign(countTotals, rows[0][0]) //combine results
return db.promise().query('SELECT two_fa_enabled FROM user WHERE id = ?', [userId]) return db.promise().query('SELECT id AS quickNote FROM note WHERE quick_note = 1 AND user_id = ?', [userId])
}).then( (rows, fields) => { }).then( (rows, fields) => {
@ -432,6 +432,66 @@ User.getByUserName = (username) => {
}) })
} }
User.changePassword = (userId, oldPass, newPass) => {
return new Promise((resolve, reject) => {
User.getMasterKey(userId, oldPass)
.then(masterKey => {
User.getPrivateKey(userId, masterKey)
.then(privateKey => {
//If success, user has correct password
// Generate new master pass, encrypt with new password
// const masterPassword = cs.createSmallSalt()
const salt = cs.createSmallSalt()
const encryptedMasterPassword = cs.encrypt(newPass, salt, masterKey)
const encryptedPrivateKey = cs.encrypt(masterKey, salt, privateKey)
db.promise()
.query(
'UPDATE user_key SET salt = ?, `key` = ?, private_key_encrypted = ? WHERE user_id = ? LIMIT 1',
[salt, encryptedMasterPassword, encryptedPrivateKey, userId]
).then((r,f) => {
//Create login using password
let shasum = crypto.createHash('sha512') //Prepare Hash
const saltString = shasum.digest('hex')
const passwordSalt = Buffer.from(saltString, 'binary') //Generate Salt hash
const iterations = 25000
crypto.pbkdf2(newPass, passwordSalt, iterations, 512, 'sha512', function(err, delivered_key) {
const deliveredPass = delivered_key.toString('hex')
db.promise().query('UPDATE user SET password = ?, salt = ? WHERE id = ? LIMIT 1', [deliveredPass, passwordSalt, userId])
.then((r,f) => {
return resolve(true)
})
})
})
})
})
.catch(error => {
resolve(false)
})
})
}
User.revokeActiveSessions = (userId, sessionId) => {
return new Promise((resolve, reject) => {
const userHash = cs.hash(String(userId)).toString('base64')
db.promise().query('DELETE FROM user_active_session WHERE user_hash = ? AND session_id != ?', [userHash, sessionId])
.then((r,f) => {
resolve(true)
})
})
}
User.deleteUser = (userId, password) => { User.deleteUser = (userId, password) => {
//Verify user is correct by decryptig master key with password //Verify user is correct by decryptig master key with password
@ -471,6 +531,7 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
const randomUsername = Math.random().toString(36).substring(2, 15); const randomUsername = Math.random().toString(36).substring(2, 15);
const randomPassword = '1' const randomPassword = '1'
const secondPassword = '2'
User.register(testUserName, password) User.register(testUserName, password)
.then( ({ token, userId }) => { .then( ({ token, userId }) => {
@ -510,6 +571,26 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
if(printResults) console.log('Test: Login New User - Pass') if(printResults) console.log('Test: Login New User - Pass')
return User.changePassword(testUserId, randomPassword, secondPassword)
})
.then(passwordChangeResults => {
if(printResults) console.log('Test: Password Change - ', passwordChangeResults?'Pass':'Fail')
return User.login(testUserName, secondPassword)
})
.then(reLogin => {
if(printResults) console.log('Test: Login With new Password - Pass')
return User.getMasterKey(testUserId, secondPassword)
})
.then(newMasterKey => {
masterKey = newMasterKey
resolve({testUserId, masterKey}) resolve({testUserId, masterKey})
}) })
}) })

View File

@ -6,9 +6,8 @@ let QuickNote = require('@models/QuickNote');
let userId = null let userId = null
let masterKey = null let masterKey = null
// middleware that is specific to this router
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
if(userId = req.headers.userId){ if(req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey masterKey = req.headers.masterKey
} }

View File

@ -41,6 +41,25 @@ router.post('/register', function (req, res) {
}) })
}) })
// change password
router.post('/changepassword', function (req, res) {
User.changePassword(req.headers.userId, req.body.currentPass, req.body.newPass)
.then( returnData => {
res.send(returnData)
})
})
//Revoke all active session keys for user
router.post('/revokesessions', function(req, res) {
User.revokeActiveSessions(req.headers.userId, req.headers.sessionId)
.then( returnData => {
res.send(returnData)
})
})
// fetch counts of users notes // fetch counts of users notes
router.post('/totals', function (req, res) { router.post('/totals', function (req, res) {
User.getCounts(req.headers.userId) User.getCounts(req.headers.userId)