2020-05-10 14:15:59 -07:00
|
|
|
const crypto = require('crypto')
|
2019-07-19 13:51:57 -07:00
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
const Note = require('@models/Note')
|
|
|
|
|
|
|
|
const db = require('@config/database')
|
|
|
|
const Auth = require('@helpers/Auth')
|
|
|
|
const cs = require('@helpers/CryptoString')
|
2019-07-19 13:51:57 -07:00
|
|
|
|
|
|
|
let User = module.exports = {}
|
|
|
|
|
|
|
|
//Login a user, if that user does not exist create them
|
|
|
|
//Issues login token
|
|
|
|
User.login = (username, password) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
const lowerName = username.toLowerCase();
|
|
|
|
|
|
|
|
db.promise()
|
2019-07-30 13:27:26 -07:00
|
|
|
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
2019-07-19 13:51:57 -07:00
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
//Pull out user data from database results
|
|
|
|
const lookedUpUser = rows[0][0];
|
|
|
|
|
|
|
|
//User not found, create a new account with set data
|
|
|
|
if(rows[0].length == 0){
|
|
|
|
User.create(lowerName, password)
|
2020-05-06 00:10:27 -07:00
|
|
|
.then( ({token, userId}) => {
|
|
|
|
return resolve({ token, userId })
|
2019-07-19 13:51:57 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-10 14:15:59 -07:00
|
|
|
if(rows[0].length == 1){
|
2020-05-06 00:10:27 -07:00
|
|
|
//hash the password and check for a match
|
2020-05-10 14:15:59 -07:00
|
|
|
// const salt = new Buffer(lookedUpUser.salt, 'binary')
|
|
|
|
const salt = Buffer.from(lookedUpUser.salt, 'binary')
|
2020-05-06 00:10:27 -07:00
|
|
|
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
|
|
|
|
if(delivered_key.toString('hex') === lookedUpUser.password){
|
2019-07-19 13:51:57 -07:00
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
User.generateMasterKey(lookedUpUser.id, password)
|
|
|
|
.then( result => User.getMasterKey(lookedUpUser.id, password))
|
|
|
|
.then(masterKey => {
|
2019-07-19 13:51:57 -07:00
|
|
|
|
2020-05-10 14:15:59 -07:00
|
|
|
User.generateKeypair(lookedUpUser.id, masterKey)
|
|
|
|
.then(({publicKey, privateKey}) => {
|
|
|
|
|
|
|
|
//Passback a json web token
|
|
|
|
const token = Auth.createToken(lookedUpUser.id, masterKey)
|
|
|
|
resolve({ token: token, userId:lookedUpUser.id })
|
|
|
|
|
|
|
|
})
|
2020-05-06 00:10:27 -07:00
|
|
|
})
|
2019-07-19 13:51:57 -07:00
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
} else {
|
|
|
|
|
|
|
|
reject('Password does not match database')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-07-19 13:51:57 -07:00
|
|
|
})
|
|
|
|
.catch(console.log)
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
//Create user account
|
|
|
|
//Issues login token
|
|
|
|
User.create = (username, password) => {
|
|
|
|
|
|
|
|
//For some reason, username won't get into the promise. But password will @TODO figure this out
|
|
|
|
const lowerName = username.toLowerCase().trim()
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
db.promise()
|
2019-07-30 13:27:26 -07:00
|
|
|
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
2019-07-19 13:51:57 -07:00
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
if(rows[0].length === 0){ //No users returned, create new one. Start with hashing password
|
|
|
|
|
|
|
|
//Params for hash function
|
|
|
|
let shasum = crypto.createHash('sha512') //Prepare Hash
|
|
|
|
const ran = parseInt(Date.now()) //Get current time in miliseconds
|
|
|
|
const semiRandomInt = Math.floor(Math.random()*11) //Grab a random number
|
|
|
|
const otherRandomInt = (ran*semiRandomInt+ran)*semiRandomInt-ran //Mix things up a bit
|
|
|
|
shasum.update(''+otherRandomInt) //Update Hasd
|
|
|
|
|
|
|
|
const saltString = shasum.digest('hex')
|
2020-05-10 14:15:59 -07:00
|
|
|
const salt = Buffer.from(saltString, 'binary') //Generate Salt hash
|
2019-07-19 13:51:57 -07:00
|
|
|
const iterations = 25000
|
|
|
|
|
|
|
|
crypto.pbkdf2(password, salt, iterations, 512, 'sha512', function(err, delivered_key) {
|
|
|
|
|
|
|
|
//Create new user object with freshly salted password
|
|
|
|
var currentDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
|
|
var new_user = {
|
|
|
|
username: lowerName,
|
|
|
|
password: delivered_key.toString('hex'),
|
|
|
|
salt: salt,
|
|
|
|
iterations: iterations,
|
|
|
|
last_login: currentDate,
|
|
|
|
created: currentDate
|
|
|
|
};
|
|
|
|
|
|
|
|
db.promise()
|
2019-07-30 13:27:26 -07:00
|
|
|
.query('INSERT INTO user SET ?', new_user)
|
2019-07-19 13:51:57 -07:00
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
if(rows[0].affectedRows == 1){
|
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
const userId = rows[0].insertId
|
|
|
|
|
|
|
|
User.generateMasterKey(userId, password)
|
|
|
|
.then( result => User.getMasterKey(userId, password))
|
|
|
|
.then(masterKey => {
|
|
|
|
|
2020-05-10 14:15:59 -07:00
|
|
|
User.generateKeypair(userId, masterKey)
|
|
|
|
.then(({publicKey, privateKey}) => {
|
|
|
|
|
|
|
|
const token = Auth.createToken(userId, masterKey)
|
|
|
|
return resolve({token, userId})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
})
|
2019-07-19 13:51:57 -07:00
|
|
|
|
|
|
|
} else {
|
|
|
|
//Emit Error to user
|
|
|
|
reject('New user could not be created')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(console.log)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
reject('Username already in use.')
|
|
|
|
}//END user create
|
|
|
|
})
|
|
|
|
.catch(console.log)
|
|
|
|
|
|
|
|
|
2020-02-11 13:11:14 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
|
|
|
|
User.getCounts = (userId) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
let countTotals = {}
|
|
|
|
|
|
|
|
db.promise().query(
|
|
|
|
`SELECT
|
|
|
|
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes,
|
2020-02-13 17:08:46 -08:00
|
|
|
SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes,
|
2020-03-13 16:34:32 -07:00
|
|
|
SUM(encrypted = 1) AS encryptedNotes,
|
2020-02-11 13:11:14 -08:00
|
|
|
SUM(share_user_id IS NULL) AS totalNotes,
|
|
|
|
SUM(share_user_id != ?) AS sharedToNotes,
|
2020-02-23 21:59:13 -08:00
|
|
|
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes
|
2020-02-11 13:11:14 -08:00
|
|
|
FROM note
|
2020-03-13 16:34:32 -07:00
|
|
|
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
2020-02-11 13:11:14 -08:00
|
|
|
WHERE user_id = ?`, [userId, userId, userId, userId])
|
|
|
|
.then( (rows, fields) => {
|
|
|
|
|
|
|
|
Object.assign(countTotals, rows[0][0]) //combine results
|
|
|
|
|
|
|
|
return db.promise().query(
|
|
|
|
`SELECT count(id) AS sharedFromNotes
|
|
|
|
FROM note WHERE share_user_id = ?`, [userId]
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.then( (rows, fields) => {
|
|
|
|
|
|
|
|
Object.assign(countTotals, rows[0][0]) //combine results
|
|
|
|
|
|
|
|
return db.promise().query(
|
|
|
|
`SELECT
|
|
|
|
SUM(attachment_type = 1) as linkFiles,
|
|
|
|
SUM(attachment_type != 1) as otherFiles,
|
|
|
|
COUNT(id) as totalFiles
|
|
|
|
FROM attachment WHERE visible = 1
|
|
|
|
AND user_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])
|
|
|
|
countTotals[key] = count ? count : 0
|
|
|
|
})
|
|
|
|
|
|
|
|
resolve(countTotals)
|
|
|
|
})
|
|
|
|
|
2020-05-06 00:10:27 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.generateMasterKey = (userId, password) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
if(!userId || !password){
|
|
|
|
reject('Need userId and password to generate key')
|
|
|
|
}
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
.query('SELECT count(id) as total FROM user_key WHERE user_id = ?', [userId])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
//Entry already exists, you good.
|
|
|
|
if(rows[0][0]['total'] > 0){
|
|
|
|
return resolve(true)
|
|
|
|
// throw new Error('User Encryption key already exists')
|
|
|
|
} else {
|
|
|
|
// Generate user key, its big and random
|
|
|
|
const masterPassword = cs.createSmallSalt()
|
|
|
|
|
|
|
|
//Generate a salt because it wants it
|
|
|
|
const salt = cs.createSmallSalt()
|
|
|
|
|
|
|
|
// Encrypt master password
|
|
|
|
const encryptedMasterPassword = cs.encrypt(password, salt, masterPassword)
|
|
|
|
|
|
|
|
const created = Math.round((+new Date)/1000)
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
.query(
|
|
|
|
'INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);',
|
|
|
|
[userId, salt, encryptedMasterPassword, created]
|
|
|
|
)
|
|
|
|
.then((rows, fields)=>{
|
|
|
|
return Note.encryptEveryNote(userId, masterPassword)
|
|
|
|
})
|
|
|
|
.then(results => {
|
|
|
|
return new Promise((resolve, reject) => { resolve(true) })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
.then((rows, fields) => {
|
|
|
|
return resolve(true)
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.log('Create Master Password Error')
|
|
|
|
console.log(error)
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.getMasterKey = (userId, password) => {
|
|
|
|
|
|
|
|
if(!userId || !password){
|
|
|
|
reject('Need userId and password to fetch key')
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
const row = rows[0][0]
|
|
|
|
|
|
|
|
const masterKey = cs.decrypt(password, row['salt'], row['key'])
|
|
|
|
|
|
|
|
if(masterKey == null){
|
|
|
|
return reject('Unable to decrypt key')
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(masterKey)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
2019-07-19 13:51:57 -07:00
|
|
|
})
|
2020-05-10 14:15:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
User.generateKeypair = (userId, masterKey) => {
|
|
|
|
|
|
|
|
let publicKey = null
|
|
|
|
let privateKey = null
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db.promise().query('SELECT * FROM user_key WHERE user_id = ?', [userId])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
const row = rows[0][0]
|
|
|
|
|
|
|
|
const salt = row['salt']
|
|
|
|
publicKey = row['public_key']
|
|
|
|
privateKey = row['private_key_encrypted']
|
|
|
|
|
|
|
|
if(row['public_key'] == null){
|
|
|
|
const keyPair = crypto.generateKeyPairSync('rsa', {
|
|
|
|
modulusLength: 1024,
|
|
|
|
publicKeyEncoding: {
|
|
|
|
type: 'spki',
|
|
|
|
format: 'pem'
|
|
|
|
},
|
|
|
|
privateKeyEncoding: {
|
|
|
|
type: 'pkcs8',
|
|
|
|
format: 'pem'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
publicKey = keyPair.publicKey
|
|
|
|
privateKey = keyPair.privateKey
|
|
|
|
const privateKeyEncrypted = cs.encrypt(masterKey, salt, privateKey)
|
|
|
|
|
|
|
|
db.promise()
|
|
|
|
.query(
|
|
|
|
'UPDATE user_key SET `public_key` = ?, `private_key_encrypted` = ? WHERE user_id = ?;',
|
|
|
|
[publicKey, privateKeyEncrypted, userId]
|
|
|
|
)
|
|
|
|
.then((rows, fields)=>{
|
|
|
|
|
|
|
|
return resolve({publicKey, privateKey})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//Decrypt private key
|
|
|
|
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
|
|
|
|
|
|
|
return resolve({publicKey, privateKey})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.getPublicKey = (userId) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db.promise().query('SELECT public_key FROM user_key WHERE user_id = ?', [userId])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
const row = rows[0][0]
|
|
|
|
return resolve(row['public_key'])
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.getPrivateKey = (userId, masterKey) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db.promise().query('SELECT salt, private_key_encrypted FROM user_key WHERE user_id = ?', [userId])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
|
|
|
|
const row = rows[0][0]
|
|
|
|
|
|
|
|
const salt = row['salt']
|
|
|
|
privateKey = row['private_key_encrypted']
|
|
|
|
|
|
|
|
//Decrypt private key
|
|
|
|
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
|
|
|
|
|
|
|
return resolve(privateKey)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.getByUserName = (username) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db.promise().query('SELECT * FROM user WHERE username = ? LIMIT 1', [username.toLowerCase()])
|
|
|
|
.then((rows, fields) => {
|
|
|
|
resolve(rows[0][0])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
User.deleteUser = (userId, password) => {
|
|
|
|
|
|
|
|
//Verify user is correct by decryptig master key with password
|
|
|
|
|
|
|
|
//Delete user, all notes, all keys
|
|
|
|
}
|
|
|
|
|
|
|
|
User.keyPairTest = (testUserName = 'genMan', password = '1') => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
let masterKey = null
|
|
|
|
let testUserId = null
|
|
|
|
|
|
|
|
User.login(testUserName, password)
|
|
|
|
.then( ({ token, userId }) => {
|
|
|
|
testUserId = userId
|
|
|
|
console.log('Test: Create/Login User - Pass')
|
|
|
|
return User.getMasterKey(testUserId, password)
|
|
|
|
})
|
|
|
|
.then(newMasterKey => {
|
|
|
|
masterKey = newMasterKey
|
|
|
|
console.log('Test: Generate/Decrypt Master Key - Pass')
|
|
|
|
return User.generateKeypair(testUserId, masterKey)
|
|
|
|
})
|
|
|
|
.then(({publicKey, privateKey}) => {
|
|
|
|
|
|
|
|
const publicKeyMessage = 'Test: Public key decrypt - Pass'
|
|
|
|
const privateKeyMessage = 'Test: Private key decrypt - Pass'
|
|
|
|
|
|
|
|
//Encrypt Message with private Key
|
|
|
|
const privateKeyEncrypted = crypto.privateEncrypt(privateKey, Buffer.from(privateKeyMessage, 'utf8')).toString('base64')
|
|
|
|
const decryptedPrivate = crypto.publicDecrypt(publicKey, Buffer.from(privateKeyEncrypted, 'base64'))
|
|
|
|
//Conver back to a string
|
|
|
|
console.log(decryptedPrivate.toString('utf8'))
|
|
|
|
|
|
|
|
//Encrypt with public key
|
|
|
|
const pubEncrMsc = crypto.publicEncrypt(publicKey, Buffer.from(publicKeyMessage, 'utf8')).toString('base64')
|
|
|
|
const publicDeccryptMessage = crypto.privateDecrypt(privateKey, Buffer.from(pubEncrMsc, 'base64') )
|
|
|
|
//Convert it back to string
|
|
|
|
console.log(publicDeccryptMessage.toString('utf8'))
|
|
|
|
|
|
|
|
resolve({testUserId, masterKey})
|
|
|
|
})
|
|
|
|
})
|
2019-07-19 13:51:57 -07:00
|
|
|
}
|