Upgraded password hashing algo to argon2id.

This commit is contained in:
rainbow napkin 2025-10-18 09:42:08 -04:00
parent 895a8201a5
commit 5caa679b92
7 changed files with 56 additions and 33 deletions

View file

@ -6,11 +6,15 @@
"protocol": "http", "protocol": "http",
"domain": "localhost", "domain": "localhost",
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp", "ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
"sessionSecret": "CHANGE_ME",
"altchaSecret": "CHANGE_ME",
"ipSecret": "CHANGE_ME",
"migrate": false, "migrate": false,
"dropLegacyTokes": false, "dropLegacyTokes": false,
"secrets":{
"passwordSecret": "CHANGE_ME",
"rememberMeSecret": "CHANGE_ME",
"sessionSecret": "CHANGE_ME",
"altchaSecret": "CHANGE_ME",
"ipSecret": "CHANGE_ME"
},
"ssl":{ "ssl":{
"cert": "./server.cert", "cert": "./server.cert",
"key": "./server.key" "key": "./server.key"

View file

@ -16,14 +16,6 @@
//Path to YT-DLP Executable for scraping youtube, dailymotion, and vimeo //Path to YT-DLP Executable for scraping youtube, dailymotion, and vimeo
//Dailymotion and Vimeo could work using official apis w/o keys, but you wouldn't have any raw file playback options :P //Dailymotion and Vimeo could work using official apis w/o keys, but you wouldn't have any raw file playback options :P
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp", "ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
//Be careful with what you keep in secrets, you should use special chars, but test your deployment, as some chars may break account registration
//An update to either kill the server and bitch about the issue in console is planned so it's not so confusing for new admins
//Session secret used to secure session keys
"sessionSecret": "CHANGE_ME",
//Altacha secret used to generate altcha challenges
"altchaSecret": "CHANGE_ME",
//IP Secret used to salt IP Hashes
"ipSecret": "CHANGE_ME",
//Enable to migrate legacy DB and toke files dumped into the ./migration/ directory //Enable to migrate legacy DB and toke files dumped into the ./migration/ directory
//WARNING: The migration folder is cleared after server boot, whether or not a migration took place or this option is enabled. //WARNING: The migration folder is cleared after server boot, whether or not a migration took place or this option is enabled.
//Keep your backups in a safe place, preferably a machine that DOESN'T have open inbound ports exposed to the internet/a publically accessible reverse proxy! //Keep your backups in a safe place, preferably a machine that DOESN'T have open inbound ports exposed to the internet/a publically accessible reverse proxy!
@ -32,6 +24,21 @@
//Requires migration to be disabled before it takes effect. //Requires migration to be disabled before it takes effect.
//WARNING: this does NOT affect user toke counts, migrated or otherwise. Use carefully! //WARNING: this does NOT affect user toke counts, migrated or otherwise. Use carefully!
"dropLegacyTokes": false, "dropLegacyTokes": false,
//Server Secrets
//Be careful with what you keep in secrets, you should use special chars, but test your deployment, as some chars may break account registration
//An update to either kill the server and bitch about the issue in console is planned so it's not so confusing for new admins
"secrets":{
//Password secret used to pepper password hashes
"passwordSecret": "CHANGE_ME",
//Password secret used to pepper rememberMe token hashes
"rememberMeSecret": "CHANGE_ME",
//Session secret used to secure session keys
"sessionSecret": "CHANGE_ME",
//Altacha secret used to generate altcha challenges
"altchaSecret": "CHANGE_ME",
//IP Secret used to pepper IP Hashes
"ipSecret": "CHANGE_ME"
},
//SSL cert and key locations //SSL cert and key locations
"ssl":{ "ssl":{
"cert": "./server.cert", "cert": "./server.cert",

View file

@ -164,7 +164,7 @@ userSchema.pre('save', async function (next){
//If the password was changed //If the password was changed
if(this.isModified("pass")){ if(this.isModified("pass")){
//Hash that sunnovabitch, no questions asked. //Hash that sunnovabitch, no questions asked.
this.pass = hashUtil.hashPassword(this.pass); this.pass = await hashUtil.hashPassword(this.pass);
} }
//If the flair was changed //If the flair was changed
@ -321,7 +321,7 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use
} }
//Check our password is correct //Check our password is correct
if(userDB.checkPass(pass)){ if(await userDB.checkPass(pass)){
return userDB; return userDB;
}else{ }else{
//if not scream and shout //if not scream and shout
@ -492,8 +492,8 @@ userSchema.statics.processAgedIPRecords = async function(){
* @param {String} pass - Password to authenticate * @param {String} pass - Password to authenticate
* @returns {Boolean} True if authenticated * @returns {Boolean} True if authenticated
*/ */
userSchema.methods.checkPass = function(pass){ userSchema.methods.checkPass = async function(pass){
return hashUtil.comparePassword(pass, this.pass) return await hashUtil.comparePassword(pass, this.pass)
} }
/** /**
@ -824,7 +824,7 @@ userSchema.methods.killAllSessions = async function(reason = "A full log-out fro
* @param {Object} passChange - passChange object handed down from Browser * @param {Object} passChange - passChange object handed down from Browser
*/ */
userSchema.methods.changePassword = async function(passChange){ userSchema.methods.changePassword = async function(passChange){
if(this.checkPass(passChange.oldPass)){ if(await this.checkPass(passChange.oldPass)){
if(passChange.newPass == passChange.confirmPass){ if(passChange.newPass == passChange.confirmPass){
//Note: We don't have to worry about hashing here because the schema is written to do it auto-magically //Note: We don't have to worry about hashing here because the schema is written to do it auto-magically
this.pass = passChange.newPass; this.pass = passChange.newPass;
@ -877,7 +877,7 @@ userSchema.methods.nuke = async function(pass){
} }
//Check that the password is correct //Check that the password is correct
if(this.checkPass(pass)){ if(await this.checkPass(pass)){
//delete the user //delete the user
var oldUser = await this.deleteOne(); var oldUser = await this.deleteOne();
}else{ }else{

View file

@ -84,7 +84,7 @@ module.exports.store = mongoStore.create({mongoUrl: dbUrl});
//define sessionMiddleware //define sessionMiddleware
const sessionMiddleware = session({ const sessionMiddleware = session({
secret: config.sessionSecret, secret: config.secrets.sessionSecret,
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
store: module.exports.store store: module.exports.store

View file

@ -44,7 +44,7 @@ module.exports.genCaptcha = async function(difficulty = 2, uniqueSecret = ''){
//Generate Altcha Challenge //Generate Altcha Challenge
return await createChallenge({ return await createChallenge({
hmacKey: [config.altchaSecret, uniqueSecret].join(''), hmacKey: [config.secrets.altchaSecret, uniqueSecret].join(''),
maxNumber: 100000 * difficulty, maxNumber: 100000 * difficulty,
expires: expiration expires: expiration
}); });
@ -73,5 +73,5 @@ module.exports.verify = async function(payload, uniqueSecret = ''){
setTimeout(() => {spent.splice(payloadIndex,1);}, lifetime * 60 * 1000); setTimeout(() => {spent.splice(payloadIndex,1);}, lifetime * 60 * 1000);
//Return verification results //Return verification results
return await verifySolution(payload, [config.altchaSecret, uniqueSecret].join('')); return await verifySolution(payload, [config.secrets.altchaSecret, uniqueSecret].join(''));
} }

View file

@ -40,18 +40,28 @@ module.exports.securityCheck = function(){
loggerUtil.consoleWarn("Mail transport security disabled! This server should be used for development purposes only!"); loggerUtil.consoleWarn("Mail transport security disabled! This server should be used for development purposes only!");
} }
//check password pepper
if(!validator.isStrongPassword(config.secrets.passwordSecret) || config.secrets.passwordSecret == "CHANGE_ME"){
loggerUtil.consoleWarn("Insecure Password Secret! Change Password Secret!");
}
//check RememberMe pepper
if(!validator.isStrongPassword(config.secrets.rememberMeSecret) || config.secrets.rememberMeSecret == "CHANGE_ME"){
loggerUtil.consoleWarn("Insecure RememberMe Secret! Change RememberMe Secret!");
}
//check session secret //check session secret
if(!validator.isStrongPassword(config.sessionSecret) || config.sessionSecret == "CHANGE_ME"){ if(!validator.isStrongPassword(config.secrets.sessionSecret) || config.secrets.sessionSecret == "CHANGE_ME"){
loggerUtil.consoleWarn("Insecure Session Secret! Change Session Secret!"); loggerUtil.consoleWarn("Insecure Session Secret! Change Session Secret!");
} }
//check altcha secret //check altcha secret
if(!validator.isStrongPassword(config.altchaSecret) || config.altchaSecret == "CHANGE_ME"){ if(!validator.isStrongPassword(config.secrets.altchaSecret) || config.secrets.altchaSecret == "CHANGE_ME"){
loggerUtil.consoleWarn("Insecure Altcha Secret! Change Altcha Secret!"); loggerUtil.consoleWarn("Insecure Altcha Secret! Change Altcha Secret!");
} }
//check ipHash secret //check ipHash secret
if(!validator.isStrongPassword(config.ipSecret) || config.ipSecret == "CHANGE_ME"){ if(!validator.isStrongPassword(config.secrets.ipSecret) || config.secrets.ipSecret == "CHANGE_ME"){
loggerUtil.consoleWarn("Insecure IP Hashing Secret! Change IP Hashing Secret!"); loggerUtil.consoleWarn("Insecure IP Hashing Secret! Change IP Hashing Secret!");
} }

View file

@ -29,9 +29,9 @@ const bcrypt = require('bcrypt');
* @param {String} pass - Password to hash * @param {String} pass - Password to hash
* @returns {String} Hashed/Salted password * @returns {String} Hashed/Salted password
*/ */
module.exports.hashPassword = function(pass){ module.exports.hashPassword = async function(pass){
const salt = bcrypt.genSaltSync(); //Hash password with argon2id
return bcrypt.hashSync(pass, salt); return await argon2.hash(pass, {secret: Buffer.from(config.secrets.passwordSecret)});
} }
/** /**
@ -40,8 +40,9 @@ module.exports.hashPassword = function(pass){
* @param {String} hash - Salty Hash * @param {String} hash - Salty Hash
* @returns {Boolean} True if authentication success * @returns {Boolean} True if authentication success
*/ */
module.exports.comparePassword = function(pass, hash){ module.exports.comparePassword = async function(pass, hash){
return bcrypt.compareSync(pass, hash); //Verify password against argon2 hash
return await argon2.verify(hash, pass, {secret: Buffer.from(config.secrets.passwordSecret)});
} }
/** /**
@ -59,14 +60,14 @@ module.exports.compareLegacyPassword = function(pass, hash){
* *
* Provides a basic level of privacy by only logging salted hashes of IP's * Provides a basic level of privacy by only logging salted hashes of IP's
* @param {String} ip - IP to hash * @param {String} ip - IP to hash
* @returns {String} Hashed/Salted IP Adress * @returns {String} Hashed/Peppered IP Adress
*/ */
module.exports.hashIP = function(ip){ module.exports.hashIP = function(ip){
//Create hash object //Create hash object
const hashObj = crypto.createHash('sha512'); const hashObj = crypto.createHash('sha512');
//add IP and salt to the hash //add IP and pepper to the hash
hashObj.update(`${ip}${config.ipSecret}`); hashObj.update(`${ip}${config.secrets.ipSecret}`);
//return the IP hash as a string //return the IP hash as a string
return hashObj.digest('hex'); return hashObj.digest('hex');
@ -78,7 +79,8 @@ module.exports.hashIP = function(ip){
* @returns {String} - Hashed token * @returns {String} - Hashed token
*/ */
module.exports.hashRememberMeToken = async function(token){ module.exports.hashRememberMeToken = async function(token){
return await argon2.hash(token); //hash token with argon2id
return await argon2.hash(token, {secret: Buffer.from(config.secrets.rememberMeSecret)});
} }
/** /**
@ -89,5 +91,5 @@ module.exports.hashRememberMeToken = async function(token){
*/ */
module.exports.compareRememberMeToken = async function(token, hash){ module.exports.compareRememberMeToken = async function(token, hash){
//Compare hash and return result //Compare hash and return result
return await argon2.verify(hash, token); return await argon2.verify(hash, token, {secret: Buffer.from(config.secrets.rememberMeSecret)});
} }