const db = require('@config/database') const jwt = require('jsonwebtoken') const cs = require('@helpers/CryptoString') const speakeasy = require('speakeasy') let Auth = {} const tokenSecretKey = process.env.JSON_KEY const sessionTokenUses = 300 //Defines number of uses each session token has before being refreshed //Creates session token Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { return new Promise((resolve, reject) => { const created = pastCreatedDate ? pastCreatedDate : Math.floor(+new Date/1000) const userHash = cs.hash(String(userId)).toString('base64') //Encrypt Master Password and save it to the server const sessionId = pastId ? pastId : cs.createSmallSalt().slice(0,9) //Use existing session id const salt = cs.createSmallSalt() const tempPass = cs.createSmallSalt() const encryptedMasterPass = cs.encrypt(tempPass, salt, masterKey) //Deactivate all other session keys, they delete after 30 seconds db.promise().query('UPDATE user_active_session SET active = 0 WHERE session_id = ?', [sessionId]) .then((r,f) => { return db.promise().query( 'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', [salt, encryptedMasterPass, created, sessionTokenUses, userHash, sessionId]) }) .then((r,f) => { const sessionNum = r[0].insertId //Required Data for JWT payload const tokenPayload = {userId, tempPass, sessionNum} //Return token const token = jwt.sign(tokenPayload, tokenSecretKey) return resolve(token) }) }) } //Decodes session token Auth.decodeToken = (token, request = null) => { return new Promise((resolve, reject) => { let decodedToken = null //Delete all tokens older than 20 days before continuing or inacive and older than 1 minute const now = (Math.floor((+new Date)/1000)) const twentyDays = (Math.floor((+new Date)/1000)) - (86400 * 20) const fourtyFiveSeconds = (Math.floor((+new Date)/1000)) - (45) //Decode Json web token jwt.verify(token, tokenSecretKey, function(err, decoded){ if(err || decoded.tempPass == undefined || decoded.tempPass.length < 5){ throw new Error('Bad Token') } decodedToken = decoded db.promise().query(`DELETE from user_active_session WHERE (created < ?) OR (active = 0 AND last_used < ?) OR (uses = 0) `, [twentyDays, fourtyFiveSeconds]) .then((r,f) => { //Lookup session data in database db.promise().query('SELECT * FROM user_active_session WHERE id = ? LIMIT 1', [decodedToken.sessionNum]) .then((r,f) => { if(r == undefined || r[0].length == 0){ throw new Error('Active Session not found for token') } const row = r[0][0] // console.log(decodedToken.sessionNum + ' uses -> ' + row.uses) if(row.uses <= 0){ throw new Error('Token is used up') } //Decrypt master key from lookup const masterKey = cs.decrypt(decodedToken.tempPass, row.salt, row.encrypted_master_password) if(masterKey == null){ // console.log('Deleting invalid session') Auth.terminateSession(row.session_id) throw new Error ('Unable to decrypt password for session') } //Async update DB counts and disable session if needed db.promise().query('UPDATE user_active_session SET uses = uses -1, last_used = ? WHERE id = ? LIMIT 1', [now, decodedToken.sessionNum]) .then((r,f) => { let userData = { 'userId': decodedToken.userId, 'masterKey': masterKey, 'sessionId': row.session_id, 'created': row.created, 'remainingUses':(row.uses--), 'active': row.active } //Return token Data return resolve(userData) }) }) .catch(error => { //Token errors result in having sessions deleted // console.log('-- Auth Token Error --') // console.log(error) reject(error) }) }) }) }) } Auth.terminateSession = (sessionId) => { return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId]) } Auth.deletAllLoginKeys = (userId) => { const userHash = cs.hash(String(userId)).toString('base64') 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 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') }) } module.exports = Auth