var crypto = require('crypto') const Note = require('@models/Note') const db = require('@config/database') const Auth = require('@helpers/Auth') const cs = require('@helpers/CryptoString') 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() .query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName]) .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) .then( ({token, userId}) => { return resolve({ token, userId }) }) } if(lookedUpUser && lookedUpUser.salt){ //hash the password and check for a match const salt = new Buffer(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 => { //Passback a json web token const token = Auth.createToken(lookedUpUser.id, masterKey) resolve({ token: token, userId:lookedUpUser.id }) }) } else { reject('Password does not match database') } }) } }) .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() .query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName]) .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') const salt = new Buffer(saltString, 'binary') //Generate Salt hash 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() .query('INSERT INTO user SET ?', new_user) .then((rows, fields) => { if(rows[0].affectedRows == 1){ const userId = rows[0].insertId User.generateMasterKey(userId, password) .then( result => User.getMasterKey(userId, password)) .then(masterKey => { const token = Auth.createToken(userId, masterKey) return resolve({token, userId}) }) } 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) }) } //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, SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes, SUM(encrypted = 1) AS encryptedNotes, SUM(share_user_id IS NULL) AS totalNotes, SUM(share_user_id != ?) AS sharedToNotes, SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes FROM note LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) 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) }) }) } 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() console.log('Generating new key for user', userId) //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) }) }) }