* Added new token system to add more security to logins

* Added simple tag editing from note page
This commit is contained in:
Max G
2020-06-10 04:41:52 +00:00
parent 6bb856689d
commit d2624628d8
11 changed files with 324 additions and 103 deletions

View File

@@ -1,26 +1,123 @@
var jwt = require('jsonwebtoken');
const db = require('@config/database')
const jwt = require('jsonwebtoken')
const cs = require('@helpers/CryptoString')
let Auth = {}
const tokenSecretKey = process.env.JSON_KEY
Auth.createToken = (userId, masterKey) => {
const signedData = {'id':userId, 'date':Date.now(), 'masterKey':masterKey}
const token = jwt.sign(signedData, tokenSecretKey)
return token
}
Auth.decodeToken = (token) => {
Auth.createToken = (userId, masterKey, request = null) => {
return new Promise((resolve, reject) => {
jwt.verify(token, tokenSecretKey, function(err, decoded){
if(err || decoded.id == undefined){
reject('Bad Token')
return
}
//Pass back decoded token
resolve(decoded)
return
});
const created = Math.floor(+new Date/1000)
const userHash = cs.hash(String(userId)).toString('base64')
//Encrypt Master Password and save it to the server
const salt = cs.createSmallSalt()
const tempPass = cs.createSmallSalt()
const encryptedMasterPass = cs.encrypt(tempPass, salt, masterKey)
db.promise().query(
'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash) VALUES (?,?,?,?,?)',
[salt, encryptedMasterPass, created, 1, userHash])
.then((r,f) => {
//Required Data for JWT payload
const tokenPayload = {userId, tempPass, salt}
//Return token
const token = jwt.sign(tokenPayload, tokenSecretKey)
return resolve(token)
})
})
}
Auth.decodeToken = (token, request = null) => {
return new Promise((resolve, reject) => {
let decodedToken = null
//Decode Json web token
jwt.verify(token, tokenSecretKey, function(err, decoded){
if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5 || decoded.salt == undefined || decoded.salt.length < 5){
return reject('Bad Token')
}
decodedToken = decoded
//Lookup session data in database
return db.promise().query('SELECT * FROM user_active_session WHERE salt = ? LIMIT 1', [decodedToken.salt])
})
.then((r,f) => {
const row = r[0][0]
if(row == undefined || row.length == 0){
return reject(false)
}
//Decrypt master key from database
const masterKey = cs.decrypt(decodedToken.tempPass, decodedToken.salt, row.encrypted_master_password)
if(masterKey == null){
return reject (false)
}
const userData = {
userId: decodedToken.userId, masterKey, tokenId: row.id
}
//Async update DB counts
db.promise().query('UPDATE user_active_session SET uses = uses + 1 WHERE salt = ? LIMIT 1', [decodedToken.salt])
return resolve(userData)
})
})
}
Auth.reissueToken = () => {
//If token has more than 200 uses, renew it
}
Auth.deletAllLoginKeys = (userId) => {
const userHash = cs.hash(String(userId)).toString('base64')
return db.promise().query('DELETE FROM user_active_session WHERE user_hash = ?', [userHash])
}
Auth.test = () => {
// return Auth.deletAllLoginKeys(testUserId)
const testUserId = 22
const testPass = cs.createSmallSalt()
Auth.createToken(testUserId, testPass)
.then(token => {
console.log('Test: Create JWT -> Pass')
return Auth.decodeToken(token)
})
.then(userData => {
console.log('Test: Decrypted key Match -> ' + (testPass == userData.masterKey))
return Auth.deletAllLoginKeys(testUserId)
})
.then(results => {
console.log('Test: Remove user Json Web Tokens - Pass')
})
//create token with userId and master key
// Auth.createToken()
//Thirt days ago
// const thirtyDays = (Math.floor((+new Date)/1000)) - (86400 * 30)
// const created = Math.floor(decoded.date/1000)
// if(created < thirtyDays){
// return reject('Token Expired')
// }
}
module.exports = Auth

View File

@@ -198,19 +198,21 @@ http.listen(3001, function(){
//Enable json body parsing in requests. Allows me to post data in ajax calls
app.use(express.json({limit: '5mb'}))
//Prefix defied by route in nginx config
const prefix = '/api'
//App Auth, all requests will come in with a token, decode the token and set global var
app.use(function(req, res, next){
//Always null out master key, never allow it set from outside
req.headers.masterKey = null
req.headers.tokenId = null
//auth token set by axios in headers
let token = req.headers.authorizationtoken
if(token && token != null && typeof token === 'string'){
Auth.decodeToken(token)
Auth.decodeToken(token, req)
.then(userData => {
req.headers.userId = userData.id //Update headers for the rest of the application
req.headers.userId = userData.userId //Update headers for the rest of the application
req.headers.masterKey = userData.masterKey
req.headers.tokenId = userData.tokenId
next()
}).catch(error => {
@@ -224,9 +226,11 @@ app.use(function(req, res, next){
// Test Area
const printResults = false
const printResults = true
let UserTest = require('@models/User')
let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth')
Auth.test()
UserTest.keyPairTest('genMan12', '1', printResults)
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
.then( message => {
@@ -236,34 +240,34 @@ UserTest.keyPairTest('genMan12', '1', printResults)
//Test
app.get(prefix, (req, res) => res.send('The api is running'))
app.get('/api', (req, res) => res.send('Solidscribe API is up and running'))
//Serve up uploaded files
app.use(prefix+'/static', express.static( __dirname+'/../staticFiles' ))
app.use('/api/static', express.static( __dirname+'/../staticFiles' ))
//Public routes
var public = require('@routes/publicController')
app.use(prefix+'/public', public)
app.use('/api/public', public)
//user endpoint
var user = require('@routes/userController')
app.use(prefix+'/user', user)
app.use('/api/user', user)
//notes endpoint
var notes = require('@routes/noteController')
app.use(prefix+'/note', notes)
app.use('/api/note', notes)
//tags endpoint
var tags = require('@routes/tagController')
app.use(prefix+'/tag', tags)
app.use('/api/tag', tags)
//notes endpoint
var attachment = require('@routes/attachmentController')
app.use(prefix+'/attachment', attachment)
app.use('/api/attachment', attachment)
//quick notes endpoint
var quickNote = require('@routes/quicknoteController')
app.use(prefix+'/quick-note', quickNote)
app.use('/api/quick-note', quickNote)
//Output running status
app.listen(port, () => {

View File

@@ -40,9 +40,10 @@ User.login = (username, password) => {
.then(({publicKey, privateKey}) => {
//Passback a json web token
const token = Auth.createToken(lookedUpUser.id, masterKey)
resolve({ token: token, userId:lookedUpUser.id })
Auth.createToken(lookedUpUser.id, masterKey)
.then(token => {
return resolve({ token: token, userId:lookedUpUser.id })
})
})
})
@@ -119,7 +120,10 @@ User.register = (username, password) => {
})
.then(({publicKey, privateKey}) => {
const token = Auth.createToken(userId, newMasterKey)
return Auth.createToken(userId, newMasterKey)
})
.then(token => {
return resolve({token, userId})
})
.catch(console.log)
@@ -162,6 +166,16 @@ User.getCounts = (userId) => {
FROM note WHERE shared = 2 AND user_id = ? AND trashed = 0`, [userId]
)
})
.then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
const userHash = cs.hash(String(userId)).toString('base64')
return db.promise().query(
`SELECT count(id) as activeSessions FROM user_active_session WHERE user_hash = ?`, [userHash]
)
})
.then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
@@ -191,6 +205,11 @@ User.getCounts = (userId) => {
})
}
//Log out user by deleting login token for that active session
User.logout = (tokenId) => {
return db.promise().query('DELETE FROM user_active_session WHERE (id = ?)', [tokenId])
}
User.generateMasterKey = (userId, password) => {
return new Promise((resolve, reject) => {

View File

@@ -30,6 +30,18 @@ router.post('/login', function (req, res) {
res.send(false)
})
})
// Logout User
router.post('/logout', function (req, res) {
User.logout(req.headers.tokenId)
.then( returnData => {
res.send(true)
})
})
// Login User
router.post('/register', function (req, res) {