Added privacy policy
Updated marketing Added some keyboard shortcuts Added settings page Added accent theming Added beta 2FA
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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){
|
||||
|
@@ -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) => {
|
||||
|
||||
|
@@ -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
|
Reference in New Issue
Block a user