Started work on Remember Me Tokens.

This commit is contained in:
rainbow napkin 2025-10-18 09:15:00 -04:00
parent 7f6abdf8e2
commit 895a8201a5
2 changed files with 124 additions and 0 deletions

View file

@ -0,0 +1,103 @@
/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//You could make an argument for making this part of the userModel
//However, this is so rarely used the preformance benefits aren't worth the extra clutter
//Config
const config = require('../../../config.json');
//Node Imports
const crypto = require("node:crypto");
//NPM Imports
const {mongoose} = require('mongoose');
//Local Imports
const userSchema = require('./userSchema');
const hashUtil = require('../../utils/hashUtils');
const loggerUtils = require('../../utils/loggerUtils');
/**
* Password reset token retention time
*
* Lasts about half a year
*/
const daysToExpire = 182;
/**
* DB Schema for documents containing a single expiring password reset token
*/
const rememberMeToken = new mongoose.Schema({
id: {
type: mongoose.SchemaTypes.UUID,
required: true,
default: crypto.randomUUID()
},
user: {
type: mongoose.SchemaTypes.ObjectID,
ref: "user",
required: true
},
token: {
type: mongoose.SchemaTypes.String,
required: true
},
date: {
type: mongoose.SchemaTypes.Date,
required: true,
default: new Date()
}
});
/**
* Pre-Save function for rememberMeSchema
*/
rememberMeToken.pre('save', async function (next){
//If the token was changed
if(this.isModified("token")){
//Hash that sunnovabitch, no questions asked.
this.token = hashUtil.hashRememberMeToken(this.token);
}
//All is good, continue on saving.
next();
});
//statics
rememberMeToken.statics.genToken = async function(user, pass){
try{
//Authenticate user and pull document
const userDB = await userSchema.authenticate(user, pass);
//Generate a cryptographically secure string of 32 bytes in hexidecimal
const token = crypto.randomBytes(32).toString('hex');
//Create token document off of user and token string
const tokenDB = await this.create({user: userDB._id, token});
//Return token document UUID w/ plaintext token for browser consumption
return {
id: tokenDB.id,
token
};
//If we failed (most likely for bad login)
}catch(err){
return loggerUtils.localExceptionHandler(err);
}
}
module.exports = mongoose.model("rememberMe", rememberMeToken);

View file

@ -21,6 +21,7 @@ const config = require('../../config.json');
const crypto = require('node:crypto'); const crypto = require('node:crypto');
//NPM Imports //NPM Imports
const argon2 = require('argon2');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
/** /**
@ -69,4 +70,24 @@ module.exports.hashIP = function(ip){
//return the IP hash as a string //return the IP hash as a string
return hashObj.digest('hex'); return hashObj.digest('hex');
}
/**
* Site-wide remember-me token hashing function
* @param {String} token - Token to hash
* @returns {String} - Hashed token
*/
module.exports.hashRememberMeToken = async function(token){
return await argon2.hash(token);
}
/**
* Site-wide remember-me token hash comparison function
* @param {String} token - Token to compare
* @param {String} hash - Hash to compare
* @returns {String} - Comparison results
*/
module.exports.compareRememberMeToken = async function(token, hash){
//Compare hash and return result
return await argon2.verify(hash, token);
} }