Updated Version
This commit is contained in:
1 parent
590ae01055
commit
98261207d6
1 file changed
+358
-341
+358
-341
@@ -1,208 +1,210 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require("crypto");
|
||||||
|
|
||||||
const Note = require('@models/Note')
|
const Note = require("@models/Note");
|
||||||
|
|
||||||
const db = require('@config/database')
|
const db = require("@config/database");
|
||||||
const Auth = require('@helpers/Auth')
|
const Auth = require("@helpers/Auth");
|
||||||
const cs = require('@helpers/CryptoString')
|
const cs = require("@helpers/CryptoString");
|
||||||
const speakeasy = require('speakeasy')
|
const speakeasy = require("speakeasy");
|
||||||
|
|
||||||
let User = module.exports = {}
|
let User = (module.exports = {});
|
||||||
|
|
||||||
const version = '3.8.0'
|
const version = "4.0.0";
|
||||||
// 3.7.3 - diff/patch update
|
// 3.7.3 - diff/patch update
|
||||||
|
|
||||||
//Login a user, if that user does not exist create them
|
//Login a user, if that user does not exist create them
|
||||||
//Issues login token
|
//Issues login token
|
||||||
User.login = (username, password, authToken = null) => {
|
User.login = (username, password, authToken = null) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const lowerName = username.toLowerCase();
|
||||||
const lowerName = username.toLowerCase()
|
|
||||||
|
|
||||||
let statusObject = {
|
let statusObject = {
|
||||||
success: false,
|
success: false,
|
||||||
token: null,
|
token: null,
|
||||||
userId: null,
|
userId: null,
|
||||||
verificationRequired: false,
|
verificationRequired: false,
|
||||||
message: 'Incorrect Username or Password'
|
message: "Incorrect Username or Password",
|
||||||
}
|
};
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
.query("SELECT * FROM user WHERE username = ? LIMIT 1", [lowerName])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Login User
|
// Login User
|
||||||
//
|
//
|
||||||
if(rows[0].length == 1){
|
if (rows[0].length == 1) {
|
||||||
|
|
||||||
//Pull out user data from database results
|
//Pull out user data from database results
|
||||||
const lookedUpUser = rows[0][0]
|
const lookedUpUser = rows[0][0];
|
||||||
|
|
||||||
//Verify Token if set
|
//Verify Token if set
|
||||||
const tokenValidates = speakeasy.totp.verify({
|
const tokenValidates = speakeasy.totp.verify({
|
||||||
'secret': lookedUpUser['two_fa_secret'],
|
secret: lookedUpUser["two_fa_secret"],
|
||||||
'encoding': 'base32',
|
encoding: "base32",
|
||||||
'token': authToken,
|
token: authToken,
|
||||||
'window': 2
|
window: 2,
|
||||||
})
|
});
|
||||||
|
|
||||||
if(lookedUpUser.two_fa_enabled == 1 && !authToken){
|
if (lookedUpUser.two_fa_enabled == 1 && !authToken) {
|
||||||
|
statusObject["verificationRequired"] = true;
|
||||||
|
statusObject["message"] = "2FA authentication required.";
|
||||||
|
|
||||||
statusObject['verificationRequired'] = true
|
return resolve(statusObject);
|
||||||
statusObject['message'] = '2FA authentication required.'
|
|
||||||
|
|
||||||
return resolve(statusObject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lookedUpUser.two_fa_enabled == 1 && !tokenValidates){
|
if (lookedUpUser.two_fa_enabled == 1 && !tokenValidates) {
|
||||||
|
statusObject["verificationRequired"] = true;
|
||||||
|
statusObject["message"] = "Invalid Authorization Token.";
|
||||||
|
|
||||||
statusObject['verificationRequired'] = true
|
return resolve(statusObject);
|
||||||
statusObject['message'] = 'Invalid Authorization Token.'
|
|
||||||
|
|
||||||
return resolve(statusObject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lookedUpUser.two_fa_enabled == 0 || (lookedUpUser.two_fa_enabled == 1 && tokenValidates) ){
|
if (
|
||||||
|
lookedUpUser.two_fa_enabled == 0 ||
|
||||||
|
(lookedUpUser.two_fa_enabled == 1 && tokenValidates)
|
||||||
|
) {
|
||||||
//hash the password and check for a match
|
//hash the password and check for a match
|
||||||
|
|
||||||
const salt = Buffer.from(lookedUpUser.salt, 'binary')
|
const salt = Buffer.from(lookedUpUser.salt, "binary");
|
||||||
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
|
crypto.pbkdf2(
|
||||||
if(delivered_key.toString('hex') === lookedUpUser.password){
|
password,
|
||||||
|
salt,
|
||||||
|
lookedUpUser.iterations,
|
||||||
|
512,
|
||||||
|
"sha512",
|
||||||
|
function (err, delivered_key) {
|
||||||
|
if (delivered_key.toString("hex") === lookedUpUser.password) {
|
||||||
User.generateMasterKey(lookedUpUser.id, password)
|
User.generateMasterKey(lookedUpUser.id, password)
|
||||||
.then( result => User.getMasterKey(lookedUpUser.id, password))
|
.then((result) =>
|
||||||
.then(masterKey => {
|
User.getMasterKey(lookedUpUser.id, password),
|
||||||
|
)
|
||||||
User.generateKeypair(lookedUpUser.id, masterKey)
|
.then((masterKey) => {
|
||||||
.then(({publicKey, privateKey}) => {
|
User.generateKeypair(lookedUpUser.id, masterKey).then(
|
||||||
|
({ publicKey, privateKey }) => {
|
||||||
//Passback a json web token
|
//Passback a json web token
|
||||||
Auth.createToken(lookedUpUser.id, masterKey)
|
Auth.createToken(lookedUpUser.id, masterKey).then(
|
||||||
.then(token => {
|
(token) => {
|
||||||
|
statusObject["token"] = token;
|
||||||
statusObject['token'] = token
|
statusObject["userId"] = lookedUpUser.id;
|
||||||
statusObject['userId'] = lookedUpUser.id
|
statusObject["success"] = true;
|
||||||
statusObject['success'] = true
|
|
||||||
|
|
||||||
return resolve(statusObject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
return resolve(statusObject);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return resolve(statusObject)
|
return resolve(statusObject);
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
//If user is not found, say two factor authentication is required
|
//If user is not found, say two factor authentication is required
|
||||||
statusObject['verificationRequired'] = true
|
statusObject["verificationRequired"] = true;
|
||||||
statusObject['message'] = '2FA authentication required.'
|
statusObject["message"] = "2FA authentication required.";
|
||||||
|
|
||||||
//Show fake auth token message
|
//Show fake auth token message
|
||||||
if(authToken){
|
if (authToken) {
|
||||||
statusObject['message'] = 'Invalid Authorization Token.'
|
statusObject["message"] = "Invalid Authorization Token.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(statusObject)
|
return resolve(statusObject);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log);
|
||||||
|
});
|
||||||
})
|
};
|
||||||
}
|
|
||||||
|
|
||||||
//Create user account
|
//Create user account
|
||||||
//Issues login token
|
//Issues login token
|
||||||
User.register = (username, password) => {
|
User.register = (username, password) => {
|
||||||
|
|
||||||
//For some reason, username won't get into the promise. But password will @TODO figure this out
|
//For some reason, username won't get into the promise. But password will @TODO figure this out
|
||||||
const lowerName = username.toLowerCase().trim()
|
const lowerName = username.toLowerCase().trim();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
.query("SELECT * FROM user WHERE username = ? LIMIT 1", [lowerName])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
if (rows[0].length === 0) {
|
||||||
if(rows[0].length === 0){ //No users returned, create new one. Start with hashing password
|
//No users returned, create new one. Start with hashing password
|
||||||
|
|
||||||
//Params for hash function
|
//Params for hash function
|
||||||
let shasum = crypto.createHash('sha512') //Prepare Hash
|
let shasum = crypto.createHash("sha512"); //Prepare Hash
|
||||||
const ran = parseInt(Date.now()) //Get current time in miliseconds
|
const ran = parseInt(Date.now()); //Get current time in miliseconds
|
||||||
const semiRandomInt = Math.floor(Math.random()*11) //Grab a random number
|
const semiRandomInt = Math.floor(Math.random() * 11); //Grab a random number
|
||||||
const otherRandomInt = (ran*semiRandomInt+ran)*semiRandomInt-ran //Mix things up a bit
|
const otherRandomInt =
|
||||||
shasum.update(''+otherRandomInt) //Update Hasd
|
(ran * semiRandomInt + ran) * semiRandomInt - ran; //Mix things up a bit
|
||||||
|
shasum.update("" + otherRandomInt); //Update Hasd
|
||||||
|
|
||||||
const saltString = shasum.digest('hex')
|
const saltString = shasum.digest("hex");
|
||||||
const salt = Buffer.from(saltString, 'binary') //Generate Salt hash
|
const salt = Buffer.from(saltString, "binary"); //Generate Salt hash
|
||||||
const iterations = 25000
|
const iterations = 25000;
|
||||||
|
|
||||||
crypto.pbkdf2(password, salt, iterations, 512, 'sha512', function(err, delivered_key) {
|
|
||||||
|
|
||||||
|
crypto.pbkdf2(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
|
512,
|
||||||
|
"sha512",
|
||||||
|
function (err, delivered_key) {
|
||||||
//Create new user object with freshly salted password
|
//Create new user object with freshly salted password
|
||||||
var currentDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
var currentDate = new Date()
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " ");
|
||||||
var new_user = {
|
var new_user = {
|
||||||
username: lowerName,
|
username: lowerName,
|
||||||
password: delivered_key.toString('hex'),
|
password: delivered_key.toString("hex"),
|
||||||
salt: salt,
|
salt: salt,
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
last_login: currentDate,
|
last_login: currentDate,
|
||||||
created: currentDate
|
created: currentDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
let userId = null
|
let userId = null;
|
||||||
let newMasterKey = null
|
let newMasterKey = null;
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('INSERT INTO user SET ?', new_user)
|
.query("INSERT INTO user SET ?", new_user)
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
userId = rows[0].insertId;
|
||||||
userId = rows[0].insertId
|
return User.generateMasterKey(userId, password);
|
||||||
return User.generateMasterKey(userId, password)
|
|
||||||
})
|
})
|
||||||
.then( result => {
|
.then((result) => {
|
||||||
|
return User.getMasterKey(userId, password);
|
||||||
return User.getMasterKey(userId, password)
|
|
||||||
})
|
})
|
||||||
.then(masterKey => {
|
.then((masterKey) => {
|
||||||
newMasterKey = masterKey
|
newMasterKey = masterKey;
|
||||||
return User.generateKeypair(userId, newMasterKey)
|
return User.generateKeypair(userId, newMasterKey);
|
||||||
})
|
})
|
||||||
.then(({publicKey, privateKey}) => {
|
.then(({ publicKey, privateKey }) => {
|
||||||
|
return Auth.createToken(userId, newMasterKey);
|
||||||
return Auth.createToken(userId, newMasterKey)
|
|
||||||
})
|
})
|
||||||
.then(token => {
|
.then((token) => {
|
||||||
|
return resolve({ token, userId });
|
||||||
return resolve({token, userId})
|
|
||||||
})
|
|
||||||
.catch(console.log)
|
|
||||||
})
|
})
|
||||||
|
.catch(console.log);
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject('Username already in use.')
|
return reject("Username already in use.");
|
||||||
}//END user create
|
} //END user create
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log);
|
||||||
|
});
|
||||||
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
|
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
|
||||||
User.getCounts = (userId, extendedOptions) => {
|
User.getCounts = (userId, extendedOptions) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let countTotals = {
|
let countTotals = {
|
||||||
tags: {}
|
tags: {},
|
||||||
}
|
};
|
||||||
// const userHash = cs.hash(String(userId)).toString('base64')
|
// const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
|
|
||||||
db.promise().query(
|
db.promise()
|
||||||
|
.query(
|
||||||
`SELECT
|
`SELECT
|
||||||
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
|
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
|
||||||
SUM(trashed = 1) AS trashedNotes,
|
SUM(trashed = 1) AS trashedNotes,
|
||||||
@@ -211,22 +213,23 @@ User.getCounts = (userId, extendedOptions) => {
|
|||||||
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
|
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
|
||||||
FROM note
|
FROM note
|
||||||
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||||
WHERE user_id = ?`, [userId, userId, userId, userId])
|
WHERE user_id = ?`,
|
||||||
.then( (rows, fields) => {
|
[userId, userId, userId, userId],
|
||||||
|
)
|
||||||
Object.assign(countTotals, rows[0][0]) //combine results
|
.then((rows, fields) => {
|
||||||
|
Object.assign(countTotals, rows[0][0]); //combine results
|
||||||
//
|
//
|
||||||
// @TODO - Figured out if this is useful
|
// @TODO - Figured out if this is useful
|
||||||
// We want, notes shared with user and note user has shared
|
// We want, notes shared with user and note user has shared
|
||||||
//
|
//
|
||||||
return db.promise().query(
|
return db.promise().query(
|
||||||
`SELECT count(id) AS sharedFromNotes
|
`SELECT count(id) AS sharedFromNotes
|
||||||
FROM note WHERE shared = 2 AND user_id = ? AND trashed = 0`, [userId]
|
FROM note WHERE shared = 2 AND user_id = ? AND trashed = 0`,
|
||||||
)
|
[userId],
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.then( (rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
Object.assign(countTotals, rows[0][0]); //combine results
|
||||||
Object.assign(countTotals, rows[0][0]) //combine results
|
|
||||||
|
|
||||||
return db.promise().query(
|
return db.promise().query(
|
||||||
`SELECT
|
`SELECT
|
||||||
@@ -235,20 +238,26 @@ User.getCounts = (userId, extendedOptions) => {
|
|||||||
COUNT(id) as totalFiles
|
COUNT(id) as totalFiles
|
||||||
FROM attachment WHERE visible = 1
|
FROM attachment WHERE visible = 1
|
||||||
AND user_id = ?
|
AND user_id = ?
|
||||||
`, [userId]
|
`,
|
||||||
)
|
[userId],
|
||||||
}).then( (rows, fields) => {
|
);
|
||||||
|
})
|
||||||
|
.then((rows, fields) => {
|
||||||
|
Object.assign(countTotals, rows[0][0]); //combine results
|
||||||
|
|
||||||
Object.assign(countTotals, rows[0][0]) //combine results
|
return db
|
||||||
|
.promise()
|
||||||
return db.promise().query('SELECT id AS quickNote FROM note WHERE quick_note = 1 AND user_id = ?', [userId])
|
.query(
|
||||||
|
"SELECT id AS quickNote FROM note WHERE quick_note = 1 AND user_id = ?",
|
||||||
}).then( (rows, fields) => {
|
[userId],
|
||||||
|
);
|
||||||
Object.assign(countTotals, rows[0][0]) //combine results
|
})
|
||||||
|
.then((rows, fields) => {
|
||||||
|
Object.assign(countTotals, rows[0][0]); //combine results
|
||||||
|
|
||||||
//Count usages of user tags, sort by most popular
|
//Count usages of user tags, sort by most popular
|
||||||
return db.promise().query(`
|
return db.promise().query(
|
||||||
|
`
|
||||||
SELECT
|
SELECT
|
||||||
tag.text, COUNT(tag_id) AS uses, tag.id
|
tag.text, COUNT(tag_id) AS uses, tag.id
|
||||||
FROM note_tag
|
FROM note_tag
|
||||||
@@ -257,337 +266,345 @@ User.getCounts = (userId, extendedOptions) => {
|
|||||||
GROUP BY tag_id
|
GROUP BY tag_id
|
||||||
ORDER BY uses DESC
|
ORDER BY uses DESC
|
||||||
LIMIT 16
|
LIMIT 16
|
||||||
`, [userId])
|
`,
|
||||||
|
[userId],
|
||||||
}).then( (rows, fields) => {
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Convert everything to an int or 0
|
|
||||||
Object.keys(countTotals).forEach( key => {
|
|
||||||
const count = parseInt(countTotals[key])
|
|
||||||
countTotals[key] = count ? count : 0
|
|
||||||
})
|
})
|
||||||
|
.then((rows, fields) => {
|
||||||
|
//Convert everything to an int or 0
|
||||||
|
Object.keys(countTotals).forEach((key) => {
|
||||||
|
const count = parseInt(countTotals[key]);
|
||||||
|
countTotals[key] = count ? count : 0;
|
||||||
|
});
|
||||||
|
|
||||||
//Build out tags object
|
//Build out tags object
|
||||||
let tagsObject = {}
|
let tagsObject = {};
|
||||||
rows[0].forEach(tagRow => {
|
rows[0].forEach((tagRow) => {
|
||||||
tagsObject[tagRow['text']] = {'id':tagRow.id, 'uses':tagRow.uses}
|
tagsObject[tagRow["text"]] = { id: tagRow.id, uses: tagRow.uses };
|
||||||
})
|
});
|
||||||
|
|
||||||
//Assign after counts are updated
|
//Assign after counts are updated
|
||||||
countTotals['tags'] = tagsObject
|
countTotals["tags"] = tagsObject;
|
||||||
|
|
||||||
countTotals['currentVersion'] = version
|
countTotals["currentVersion"] = version;
|
||||||
|
|
||||||
// Allow for extended options set on page load
|
// Allow for extended options set on page load
|
||||||
if(extendedOptions){
|
if (extendedOptions) {
|
||||||
|
db.promise()
|
||||||
db.promise().query(
|
.query(
|
||||||
`SELECT updated FROM note
|
`SELECT updated FROM note
|
||||||
JOIN note_raw_text ON note_raw_text.id = note.note_raw_text_id
|
JOIN note_raw_text ON note_raw_text.id = note.note_raw_text_id
|
||||||
WHERE note.quick_note = 2
|
WHERE note.quick_note = 2
|
||||||
AND user_id = ?`, [userId])
|
AND user_id = ?`,
|
||||||
.then( (rows, fields) => {
|
[userId],
|
||||||
|
)
|
||||||
|
.then((rows, fields) => {
|
||||||
|
if (rows[0][0] && rows[0][0].updated) {
|
||||||
if(rows[0][0] && rows[0][0].updated){
|
const lastOpened = rows[0][0].updated;
|
||||||
const lastOpened = rows[0][0].updated
|
const timeDiff = Math.round((+new Date() - lastOpened) / 1000);
|
||||||
const timeDiff = Math.round(((+new Date) - (lastOpened))/1000)
|
const hoursInSeconds = 12 * 60 * 60; //12 hours
|
||||||
const hoursInSeconds = (12 * 60 * 60) //12 hours
|
|
||||||
|
|
||||||
// Show metric tracking button if its been 12 hours since last entry
|
// Show metric tracking button if its been 12 hours since last entry
|
||||||
if(lastOpened && timeDiff > hoursInSeconds){
|
if (lastOpened && timeDiff > hoursInSeconds) {
|
||||||
countTotals['showTrackMetricsButton'] = true
|
countTotals["showTrackMetricsButton"] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve(countTotals);
|
||||||
resolve(countTotals)
|
});
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resolve(countTotals)
|
resolve(countTotals);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
})
|
});
|
||||||
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log out user by deleting login token for that active session
|
//Log out user by deleting login token for that active session
|
||||||
User.logout = (sessionId) => {
|
User.logout = (sessionId) => {
|
||||||
console.log('Terminate Session -> ', sessionId)
|
console.log("Terminate Session -> ", sessionId);
|
||||||
return db.promise().query('DELETE FROM user_active_session WHERE (session_id = ?)', [sessionId])
|
return db
|
||||||
}
|
.promise()
|
||||||
|
.query("DELETE FROM user_active_session WHERE (session_id = ?)", [
|
||||||
|
sessionId,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
User.generateMasterKey = (userId, password) => {
|
User.generateMasterKey = (userId, password) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!userId || !password) {
|
||||||
if(!userId || !password){
|
reject("Need userId and password to generate key");
|
||||||
reject('Need userId and password to generate key')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('SELECT count(id) as total FROM user_key WHERE user_id = ?', [userId])
|
.query("SELECT count(id) as total FROM user_key WHERE user_id = ?", [
|
||||||
|
userId,
|
||||||
|
])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
//Entry already exists, you good.
|
//Entry already exists, you good.
|
||||||
if(rows[0][0]['total'] > 0){
|
if (rows[0][0]["total"] > 0) {
|
||||||
return resolve(true)
|
return resolve(true);
|
||||||
// throw new Error('User Encryption key already exists')
|
// throw new Error('User Encryption key already exists')
|
||||||
} else {
|
} else {
|
||||||
// Generate user key, its big and random
|
// Generate user key, its big and random
|
||||||
const masterPassword = cs.createSmallSalt()
|
const masterPassword = cs.createSmallSalt();
|
||||||
|
|
||||||
//Generate a salt because it wants it
|
//Generate a salt because it wants it
|
||||||
const salt = cs.createSmallSalt()
|
const salt = cs.createSmallSalt();
|
||||||
|
|
||||||
// Encrypt master password
|
// Encrypt master password
|
||||||
const encryptedMasterPassword = cs.encrypt(password, salt, masterPassword)
|
const encryptedMasterPassword = cs.encrypt(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
masterPassword,
|
||||||
|
);
|
||||||
|
|
||||||
const created = Math.round((+new Date)/1000)
|
const created = Math.round(+new Date() / 1000);
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(
|
.query(
|
||||||
'INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);',
|
"INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);",
|
||||||
[userId, salt, encryptedMasterPassword, created]
|
[userId, salt, encryptedMasterPassword, created],
|
||||||
)
|
)
|
||||||
.then(results => {
|
.then((results) => {
|
||||||
return resolve(true)
|
return resolve(true);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.log('Create Master Password Error')
|
console.log("Create Master Password Error");
|
||||||
console.log(error)
|
console.log(error);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
})
|
};
|
||||||
}
|
|
||||||
|
|
||||||
User.getMasterKey = (userId, password) => {
|
User.getMasterKey = (userId, password) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!userId || !password) {
|
||||||
if(!userId || !password){
|
reject("Need userId and password to fetch key");
|
||||||
reject('Need userId and password to fetch key')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId])
|
db.promise()
|
||||||
|
.query("SELECT * FROM user_key WHERE user_id = ? LIMIT 1", [userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
const row = rows[0][0];
|
||||||
|
|
||||||
const row = rows[0][0]
|
if (!rows[0] || rows[0].length == 0 || rows[0][0] == undefined) {
|
||||||
|
return reject("Row or salt or something not set");
|
||||||
if(!rows[0] || rows[0].length == 0 || rows[0][0] == undefined){
|
|
||||||
return reject('Row or salt or something not set')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const masterKey = cs.decrypt(password, row['salt'], row['key'])
|
const masterKey = cs.decrypt(password, row["salt"], row["key"]);
|
||||||
|
|
||||||
if(masterKey == null){
|
if (masterKey == null) {
|
||||||
return reject('Unable to decrypt key')
|
return reject("Unable to decrypt key");
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(masterKey)
|
return resolve(masterKey);
|
||||||
|
});
|
||||||
})
|
});
|
||||||
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
User.generateKeypair = (userId, masterKey) => {
|
User.generateKeypair = (userId, masterKey) => {
|
||||||
|
let publicKey = null;
|
||||||
let publicKey = null
|
let privateKey = null;
|
||||||
let privateKey = null
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise().query('SELECT * FROM user_key WHERE user_id = ?', [userId])
|
db.promise()
|
||||||
|
.query("SELECT * FROM user_key WHERE user_id = ?", [userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
const row = rows[0][0];
|
||||||
|
|
||||||
const row = rows[0][0]
|
const salt = row["salt"];
|
||||||
|
publicKey = row["public_key"];
|
||||||
|
privateKey = row["private_key_encrypted"];
|
||||||
|
|
||||||
const salt = row['salt']
|
if (row["public_key"] == null) {
|
||||||
publicKey = row['public_key']
|
const keyPair = crypto.generateKeyPairSync("rsa", {
|
||||||
privateKey = row['private_key_encrypted']
|
|
||||||
|
|
||||||
if(row['public_key'] == null){
|
|
||||||
const keyPair = crypto.generateKeyPairSync('rsa', {
|
|
||||||
modulusLength: 1024,
|
modulusLength: 1024,
|
||||||
publicKeyEncoding: {
|
publicKeyEncoding: {
|
||||||
type: 'spki',
|
type: "spki",
|
||||||
format: 'pem'
|
format: "pem",
|
||||||
},
|
},
|
||||||
privateKeyEncoding: {
|
privateKeyEncoding: {
|
||||||
type: 'pkcs8',
|
type: "pkcs8",
|
||||||
format: 'pem'
|
format: "pem",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
publicKey = keyPair.publicKey
|
publicKey = keyPair.publicKey;
|
||||||
privateKey = keyPair.privateKey
|
privateKey = keyPair.privateKey;
|
||||||
const privateKeyEncrypted = cs.encrypt(masterKey, salt, privateKey)
|
const privateKeyEncrypted = cs.encrypt(masterKey, salt, privateKey);
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(
|
.query(
|
||||||
'UPDATE user_key SET `public_key` = ?, `private_key_encrypted` = ? WHERE user_id = ?;',
|
"UPDATE user_key SET `public_key` = ?, `private_key_encrypted` = ? WHERE user_id = ?;",
|
||||||
[publicKey, privateKeyEncrypted, userId]
|
[publicKey, privateKeyEncrypted, userId],
|
||||||
)
|
)
|
||||||
.then((rows, fields)=>{
|
.then((rows, fields) => {
|
||||||
|
return resolve({ publicKey, privateKey });
|
||||||
return resolve({publicKey, privateKey})
|
});
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
//Decrypt private key
|
//Decrypt private key
|
||||||
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
privateKey = cs.decrypt(masterKey, salt, privateKey);
|
||||||
|
|
||||||
return resolve({publicKey, privateKey})
|
return resolve({ publicKey, privateKey });
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
User.getPublicKey = (userId) => {
|
User.getPublicKey = (userId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise().query('SELECT public_key FROM user_key WHERE user_id = ?', [userId])
|
db.promise()
|
||||||
|
.query("SELECT public_key FROM user_key WHERE user_id = ?", [userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
const row = rows[0][0];
|
||||||
const row = rows[0][0]
|
return resolve(row["public_key"]);
|
||||||
return resolve(row['public_key'])
|
});
|
||||||
|
});
|
||||||
})
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
User.getPrivateKey = (userId, masterKey) => {
|
User.getPrivateKey = (userId, masterKey) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise().query('SELECT salt, private_key_encrypted FROM user_key WHERE user_id = ?', [userId])
|
db.promise()
|
||||||
|
.query(
|
||||||
|
"SELECT salt, private_key_encrypted FROM user_key WHERE user_id = ?",
|
||||||
|
[userId],
|
||||||
|
)
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
const row = rows[0][0];
|
||||||
|
|
||||||
const row = rows[0][0]
|
const salt = row["salt"];
|
||||||
|
privateKey = row["private_key_encrypted"];
|
||||||
const salt = row['salt']
|
|
||||||
privateKey = row['private_key_encrypted']
|
|
||||||
|
|
||||||
//Decrypt private key
|
//Decrypt private key
|
||||||
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
privateKey = cs.decrypt(masterKey, salt, privateKey);
|
||||||
|
|
||||||
return resolve(privateKey)
|
return resolve(privateKey);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
User.getByUserName = (username) => {
|
User.getByUserName = (username) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise().query('SELECT * FROM user WHERE username = ? LIMIT 1', [username.toLowerCase()])
|
db.promise()
|
||||||
|
.query("SELECT * FROM user WHERE username = ? LIMIT 1", [
|
||||||
|
username.toLowerCase(),
|
||||||
|
])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
resolve(rows[0][0])
|
resolve(rows[0][0]);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
User.changePassword = (userId, oldPass, newPass) => {
|
User.changePassword = (userId, oldPass, newPass) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
User.getMasterKey(userId, oldPass)
|
User.getMasterKey(userId, oldPass)
|
||||||
.then(masterKey => {
|
.then((masterKey) => {
|
||||||
User.getPrivateKey(userId, masterKey)
|
User.getPrivateKey(userId, masterKey).then((privateKey) => {
|
||||||
.then(privateKey => {
|
|
||||||
//If success, user has correct password
|
//If success, user has correct password
|
||||||
|
|
||||||
// Generate new master pass, encrypt with new password
|
// Generate new master pass, encrypt with new password
|
||||||
// const masterPassword = cs.createSmallSalt()
|
// const masterPassword = cs.createSmallSalt()
|
||||||
const salt = cs.createSmallSalt()
|
const salt = cs.createSmallSalt();
|
||||||
const encryptedMasterPassword = cs.encrypt(newPass, salt, masterKey)
|
const encryptedMasterPassword = cs.encrypt(newPass, salt, masterKey);
|
||||||
const encryptedPrivateKey = cs.encrypt(masterKey, salt, privateKey)
|
const encryptedPrivateKey = cs.encrypt(masterKey, salt, privateKey);
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(
|
.query(
|
||||||
'UPDATE user_key SET salt = ?, `key` = ?, private_key_encrypted = ? WHERE user_id = ? LIMIT 1',
|
"UPDATE user_key SET salt = ?, `key` = ?, private_key_encrypted = ? WHERE user_id = ? LIMIT 1",
|
||||||
[salt, encryptedMasterPassword, encryptedPrivateKey, userId]
|
[salt, encryptedMasterPassword, encryptedPrivateKey, userId],
|
||||||
).then((r,f) => {
|
)
|
||||||
|
.then((r, f) => {
|
||||||
//Create login using password
|
//Create login using password
|
||||||
let shasum = crypto.createHash('sha512') //Prepare Hash
|
let shasum = crypto.createHash("sha512"); //Prepare Hash
|
||||||
const saltString = shasum.digest('hex')
|
const saltString = shasum.digest("hex");
|
||||||
const passwordSalt = Buffer.from(saltString, 'binary') //Generate Salt hash
|
const passwordSalt = Buffer.from(saltString, "binary"); //Generate Salt hash
|
||||||
const iterations = 25000
|
const iterations = 25000;
|
||||||
|
|
||||||
crypto.pbkdf2(newPass, passwordSalt, iterations, 512, 'sha512', function(err, delivered_key) {
|
crypto.pbkdf2(
|
||||||
|
newPass,
|
||||||
|
passwordSalt,
|
||||||
|
iterations,
|
||||||
|
512,
|
||||||
|
"sha512",
|
||||||
|
function (err, delivered_key) {
|
||||||
|
const deliveredPass = delivered_key.toString("hex");
|
||||||
|
|
||||||
const deliveredPass = delivered_key.toString('hex')
|
db.promise()
|
||||||
|
.query(
|
||||||
db.promise().query('UPDATE user SET password = ?, salt = ? WHERE id = ? LIMIT 1', [deliveredPass, passwordSalt, userId])
|
"UPDATE user SET password = ?, salt = ? WHERE id = ? LIMIT 1",
|
||||||
.then((r,f) => {
|
[deliveredPass, passwordSalt, userId],
|
||||||
return resolve(true)
|
)
|
||||||
|
.then((r, f) => {
|
||||||
|
return resolve(true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
.catch((error) => {
|
||||||
})
|
resolve(false);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
})
|
};
|
||||||
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
resolve(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
User.revokeActiveSessions = (userId, sessionId) => {
|
User.revokeActiveSessions = (userId, sessionId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const userHash = cs.hash(String(userId)).toString("base64");
|
||||||
|
|
||||||
const userHash = cs.hash(String(userId)).toString('base64')
|
db.promise()
|
||||||
|
.query(
|
||||||
db.promise().query('DELETE FROM user_active_session WHERE user_hash = ? AND session_id != ?', [userHash, sessionId])
|
"DELETE FROM user_active_session WHERE user_hash = ? AND session_id != ?",
|
||||||
.then((r,f) => {
|
[userHash, sessionId],
|
||||||
|
)
|
||||||
resolve(true)
|
.then((r, f) => {
|
||||||
})
|
resolve(true);
|
||||||
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
User.deleteUser = (userId, password) => {
|
User.deleteUser = (userId, password) => {
|
||||||
|
if (!userId || !password) {
|
||||||
if(!userId || !password){
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
return resolve('Missing User ID or Password. No Action Taken.')
|
return resolve("Missing User ID or Password. No Action Taken.");
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verify user is correct by decryptig master key with password
|
//Verify user is correct by decryptig master key with password
|
||||||
|
|
||||||
let deletePromises = []
|
let deletePromises = [];
|
||||||
|
|
||||||
//Delete all notes and raw text
|
//Delete all notes and raw text
|
||||||
let noteDelete = db.promise().query(`
|
let noteDelete = db.promise().query(
|
||||||
|
`
|
||||||
DELETE note, note_raw_text
|
DELETE note, note_raw_text
|
||||||
FROM note
|
FROM note
|
||||||
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||||
WHERE note.user_id = ?
|
WHERE note.user_id = ?
|
||||||
`,[userId])
|
`,
|
||||||
deletePromises.push(noteDelete)
|
[userId],
|
||||||
|
);
|
||||||
|
deletePromises.push(noteDelete);
|
||||||
|
|
||||||
//Delete user entry
|
//Delete user entry
|
||||||
let userDelete = db.promise().query(`
|
let userDelete = db.promise().query(
|
||||||
|
`
|
||||||
DELETE FROM user WHERE id = ?
|
DELETE FROM user WHERE id = ?
|
||||||
`,[userId])
|
`,
|
||||||
deletePromises.push(userDelete)
|
[userId],
|
||||||
|
);
|
||||||
|
deletePromises.push(userDelete);
|
||||||
|
|
||||||
//Delete user_key, encrypted search index
|
//Delete user_key, encrypted search index
|
||||||
let tables = ['user_key', 'user_encrypted_search_index']
|
let tables = ["user_key", "user_encrypted_search_index"];
|
||||||
tables.forEach(tableName => {
|
tables.forEach((tableName) => {
|
||||||
|
const query = `DELETE FROM ${tableName} WHERE user_id = ?`;
|
||||||
const query = `DELETE FROM ${tableName} WHERE user_id = ?`
|
const deleteQuery = db.promise().query(query, [userId]);
|
||||||
const deleteQuery = db.promise().query(query, [userId])
|
deletePromises.push(deleteQuery);
|
||||||
deletePromises.push(deleteQuery)
|
});
|
||||||
})
|
|
||||||
|
|
||||||
//Remove all note attachments and files
|
//Remove all note attachments and files
|
||||||
|
|
||||||
return Promise.all(deletePromises)
|
return Promise.all(deletePromises);
|
||||||
}
|
};
|
||||||
Reference in new issue
Block a user