Added privacy policy

Updated marketing
Added some keyboard shortcuts
Added settings page
Added accent theming
Added beta 2FA
This commit is contained in:
Max G
2020-07-07 04:04:55 +00:00
parent 2ae84ab73e
commit 06b8f0ad6a
29 changed files with 1428 additions and 362 deletions

View File

@@ -1,6 +1,7 @@
const db = require('@config/database')
const jwt = require('jsonwebtoken')
const cs = require('@helpers/CryptoString')
const speakeasy = require('speakeasy')
let Auth = {}
@@ -120,6 +121,7 @@ Auth.decodeToken = (token, request = null) => {
}
Auth.terminateSession = (sessionId) => {
return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId])
}
@@ -130,6 +132,143 @@ Auth.deletAllLoginKeys = (userId) => {
return db.promise().query('DELETE FROM user_active_session WHERE user_hash = ?', [userHash])
}
//Generate two factor secret key, if key is not verified, return a new one
//Only return QR code if user is not verified, only show unique QR code, once
Auth.generateTwoFactorSecretKey = (userId, password) => {
return new Promise((resolve, reject) => {
const QRCode = require('qrcode')
const User = require('@models/User')
User.getMasterKey(userId, password)
.then(masterKey => {
return db.promise().query('SELECT username, two_fa_enabled FROM user WHERE id = ?', [userId])
})
.then((r,f) => {
const tfaEnabled = r[0][0]['two_fa_enabled'] == 1
const username = r[0][0]['username']
if(!tfaEnabled){
var secret = speakeasy.generateSecret({length: 20, name: username+' - solidscribe.com'})
const twoFaSecretToken = secret.base32
const otpauthUrl = secret.otpauth_url
//Generate test Token
var token = speakeasy.totp({
secret: twoFaSecretToken,
encoding: 'base32'
})
db.promise().query('UPDATE user SET two_fa_secret = ? WHERE id = ? LIMIT 1', [twoFaSecretToken, userId])
.then((r,f) => {
QRCode.toDataURL(otpauthUrl, function(err, qrCode) {
//Return A QR code for the user, one time use
return resolve({qrCode, token})
})
})
} else {
return reject('Two factor already enabled for user')
}
})
.catch(error => {
console.log('Key auth error')
console.log(error)
return reject(false)
})
})
}
Auth.setTwoFactorEnabled = (userId, password, token, enable) => {
return new Promise((resolve, reject) => {
Auth.validateTwoFactorToken(userId, password, token)
.then(isValid => {
if(isValid){
db.promise().query('UPDATE user SET two_fa_enabled = ? WHERE id = ? LIMIT 1', [enable, userId])
.then((r, f) => {
return resolve(true)
})
} else {
return resolve(false)
}
})
})
}
Auth.validateTwoFactorToken = (userId, password, token) => {
return new Promise((resolve, reject) => {
const User = require('@models/User')
User.getMasterKey(userId, password)
.then(masterKey => {
return db.promise().query('SELECT two_fa_secret FROM user WHERE id = ?', [userId])
})
.then((r,f) => {
//Verify Token
const tokenValidates = speakeasy.totp.verify({
'secret': r[0][0]['two_fa_secret'],
'encoding': 'base32',
'token': token,
'window': 6
})
return resolve(tokenValidates)
})
.catch(error => {
console.log('Token Validation Error')
return resolve(false)
})
})
}
Auth.testTwoFactor = () => {
const userId = 93
const pass = '1'
let tfaToken = null
console.log('Test Two Factor')
Auth.generateTwoFactorSecretKey(userId, pass)
.then( ({qrCode, token}) => {
tfaToken = token
Auth.validateTwoFactorToken(userId, pass, tfaToken)
.then(validToken => {
console.log('Is Token Valid:', validToken)
})
return Auth.setTwoFactorEnabled(userId, pass, tfaToken, true)
})
.then(twoFactorEnbled => {
console.log('Was it enabled?', twoFactorEnbled)
return Auth.setTwoFactorEnabled(userId, pass, tfaToken, false)
})
.then(twoFactorEnbled => {
console.log('Was it disabled?', twoFactorEnbled)
})
.catch(error => {
console.log(error)
})
}
Auth.test = () => {
const testUserId = 22

View File

@@ -259,13 +259,15 @@ const printResults = true
let UserTest = require('@models/User')
let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth')
Auth.testTwoFactor()
Auth.test()
UserTest.keyPairTest('genMan15', '1', printResults)
UserTest.keyPairTest('genMan16', '1', printResults)
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
.then( message => {
if(printResults) console.log(message)
})
// Test Area
//Test

View File

@@ -182,7 +182,7 @@ Note.create = (userId, noteTitle = '', noteText = '', masterKey) => {
const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise()
.query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, created])
.query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, (+new Date)])
.then( (rows, fields) => {
const rawTextId = rows[0].insertId
@@ -515,7 +515,7 @@ Note.setPinned = (userId, noteId, pinnedBoolean) => {
return new Promise((resolve, reject) => {
const pinned = pinnedBoolean ? 1:0
const now = Math.round((+new Date)/1000)
const now = (+new Date)
//Update other note attributes
return db.promise()
@@ -532,7 +532,7 @@ Note.setArchived = (userId, noteId, archivedBoolead) => {
return new Promise((resolve, reject) => {
const archived = archivedBoolead ? 1:0
const now = Math.round((+new Date)/1000)
const now = (+new Date)
//Update other note attributes
return db.promise()
@@ -549,7 +549,7 @@ Note.setTrashed = (userId, noteId, trashedBoolean, masterKey) => {
return new Promise((resolve, reject) => {
const trashed = trashedBoolean ? 1:0
const now = Math.round((+new Date)/1000)
const now = (+new Date)
//Update other note attributes
return db.promise()
@@ -1048,7 +1048,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
// Always prioritize pinned notes in searches.
//Default Sort, order by last updated
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC, id DESC'
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC'
//Order by Last Created Date
if(fastFilters.lastCreated == 1){

View File

@@ -5,22 +5,32 @@ const Note = require('@models/Note')
const db = require('@config/database')
const Auth = require('@helpers/Auth')
const cs = require('@helpers/CryptoString')
const speakeasy = require('speakeasy')
let User = module.exports = {}
const version = '3.0.1'
const version = '3.1.3'
//Login a user, if that user does not exist create them
//Issues login token
User.login = (username, password) => {
User.login = (username, password, authToken = null) => {
return new Promise((resolve, reject) => {
const lowerName = username.toLowerCase();
const lowerName = username.toLowerCase()
let statusObject = {
success: false,
token: null,
userId: null,
verificationRequired: false,
message: 'Incorrect Username or Password'
}
db.promise()
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
.then((rows, fields) => {
//
// Login User
//
if(rows[0].length == 1){
@@ -28,34 +38,76 @@ User.login = (username, password) => {
//Pull out user data from database results
const lookedUpUser = rows[0][0]
//hash the password and check for a match
// const salt = new Buffer(lookedUpUser.salt, 'binary')
const salt = Buffer.from(lookedUpUser.salt, 'binary')
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
if(delivered_key.toString('hex') === lookedUpUser.password){
//Verify Token if set
const tokenValidates = speakeasy.totp.verify({
'secret': lookedUpUser['two_fa_secret'],
'encoding': 'base32',
'token': authToken,
'window': 2
})
User.generateMasterKey(lookedUpUser.id, password)
.then( result => User.getMasterKey(lookedUpUser.id, password))
.then(masterKey => {
if(lookedUpUser.two_fa_enabled == 1 && !authToken){
User.generateKeypair(lookedUpUser.id, masterKey)
.then(({publicKey, privateKey}) => {
statusObject['verificationRequired'] = true
statusObject['message'] = '2FA authentication required.'
//Passback a json web token
Auth.createToken(lookedUpUser.id, masterKey)
.then(token => {
return resolve({ token: token, userId:lookedUpUser.id })
return resolve(statusObject)
}
if(lookedUpUser.two_fa_enabled == 1 && !tokenValidates){
statusObject['verificationRequired'] = true
statusObject['message'] = 'Invalid Authorization Token.'
return resolve(statusObject)
}
if(lookedUpUser.two_fa_enabled == 0 || (lookedUpUser.two_fa_enabled == 1 && tokenValidates) ){
//hash the password and check for a match
const salt = Buffer.from(lookedUpUser.salt, 'binary')
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
if(delivered_key.toString('hex') === lookedUpUser.password){
User.generateMasterKey(lookedUpUser.id, password)
.then( result => User.getMasterKey(lookedUpUser.id, password))
.then(masterKey => {
User.generateKeypair(lookedUpUser.id, masterKey)
.then(({publicKey, privateKey}) => {
//Passback a json web token
Auth.createToken(lookedUpUser.id, masterKey)
.then(token => {
statusObject['token'] = token
statusObject['userId'] = lookedUpUser.id
statusObject['success'] = true
return resolve(statusObject)
})
})
})
})
} else {
} else {
return resolve(statusObject)
}
})
}
reject('Password does not match database')
}
})
} else {
return reject('Incorrect Username or Password')
//If user is not found, say two factor authentication is required
statusObject['verificationRequired'] = true
statusObject['message'] = '2FA authentication required.'
//Show fake auth token message
if(authToken){
statusObject['message'] = 'Invalid Authorization Token.'
}
return resolve(statusObject)
}
})
.catch(console.log)
@@ -186,6 +238,12 @@ User.getCounts = (userId) => {
Object.assign(countTotals, rows[0][0]) //combine results
return db.promise().query('SELECT two_fa_enabled FROM user WHERE id = ?', [userId])
}).then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
//Convert everything to an int or 0
Object.keys(countTotals).forEach( key => {
const count = parseInt(countTotals[key])
@@ -253,13 +311,12 @@ User.generateMasterKey = (userId, password) => {
}
User.getMasterKey = (userId, password) => {
if(!userId || !password){
reject('Need userId and password to fetch key')
}
return new Promise((resolve, reject) => {
if(!userId || !password){
reject('Need userId and password to fetch key')
}
db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId])
.then((rows, fields) => {

View File

@@ -1,7 +1,8 @@
var express = require('express')
var router = express.Router()
let User = require('@models/User');
const User = require('@models/User')
const Auth = require('@helpers/Auth')
const cs = require('@helpers/CryptoString')
// middleware that is specific to this router
@@ -9,26 +10,14 @@ router.use(function timeLog (req, res, next) {
// console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('User Home Page ' + User.getUsername())
})
// define the about route
router.get('/about', function (req, res) {
User.getUsername(req.headers.userId)
.then( data => res.send(data) )
})
// Login User
router.post('/login', function (req, res) {
User.login(req.body.username, req.body.password)
User.login(req.body.username, req.body.password, req.body.authToken)
.then( returnData => {
res.send(returnData)
})
.catch(e => {
res.send(false)
})
})
// Logout User
router.post('/logout', function (req, res) {
@@ -39,10 +28,7 @@ router.post('/logout', function (req, res) {
})
})
// Login User
// Register User
router.post('/register', function (req, res) {
User.register(req.body.username, req.body.password)
@@ -55,12 +41,36 @@ router.post('/register', function (req, res) {
})
})
// fetch counts of users notes
router.post('/totals', function (req, res) {
User.getCounts(req.headers.userId)
.then( countsObject => res.send( countsObject ))
})
//
// Two Factor Auth Setup
//
router.post('/twofactorsetup', function (req, res) {
//Send QR code to user for 2FA setup
Auth.generateTwoFactorSecretKey(req.headers.userId, req.body.password)
.then( ({ qrCode }) => { res.send( qrCode ) })
})
router.post('/verifytwofactorsetuptoken', function (req, res) {
//Verify Users QR code with token
Auth.setTwoFactorEnabled(req.headers.userId, req.body.password, req.body.token, true)
.then( ( results ) => { res.send( results ) })
})
router.post('/validatetwofactortoken', function (req, res) {
//Verify Users QR code with token
Auth.validateTwoFactorToken(req.headers.userId, req.body.password, req.body.token)
.then( ( results ) => { res.send( results ) })
})
module.exports = router