Finished up with remember me middleware.

This commit is contained in:
rainbow napkin 2025-10-21 00:10:17 -04:00
parent e00e5a608b
commit 61ec3ffc52
4 changed files with 107 additions and 11 deletions

View file

@ -62,10 +62,13 @@ module.exports.post = async function(req, res){
//Check config for protocol
const secure = config.protocol.toLowerCase() == "https";
//Create expiration date for cookies (180 days)
const expires = new Date(Date.now() + (1000 * 60 * 60 * 24 * 180))
//Set remember me ID and token as browser-side cookies for safe-keeping
res.cookie("rememberme.id", authToken.id, {sameSite: 'strict', httpOnly: true, secure});
res.cookie("rememberme.id", authToken.id, {sameSite: 'strict', httpOnly: true, secure, expires});
//This should be the servers last interaction with the plaintext token before saving the hashed copy, and dropping it out of RAM
res.cookie("rememberme.token", authToken.token, {sameSite: 'strict', httpOnly: true, secure});
res.cookie("rememberme.token", authToken.token, {sameSite: 'strict', httpOnly: true, secure, expires});
}
//Tell the browser everything is dandy

View file

@ -81,6 +81,12 @@ rememberMeToken.pre('save', async function (next){
next();
});
//Methods
rememberMeToken.methods.checkToken = async function(token){
//Compare ingested token to saved hash
return await hashUtil.compareRememberMeToken(token, this.token);
}
//statics
rememberMeToken.statics.genToken = async function(user, pass){
//Authenticate user and pull document
@ -104,4 +110,45 @@ rememberMeToken.statics.genToken = async function(user, pass){
}
}
/**
* Authenticates an id and token pair
* @param {String} id - id of token auth against
* @param {String} token - token string to auth against
* @param {String} failLine - Line to paste into custom error upon login failure
* @returns {Mongoose.Document} - User DB Document upon success
*/
rememberMeToken.statics.authenticate = async function(id, token, failLine = "Bad Username or Password."){
//check for missing pass
if(!id || !token){
throw loggerUtils.exceptionSmith("Missing id/token.", "validation");
}
//get the token if it exists
const tokenDB = await this.findOne({id});
//if not scream and shout
if(!tokenDB){
badLogin();
}
//Check our password is correct
if(await tokenDB.checkToken(token)){
//Populate the user field
await tokenDB.populate('user');
//Return the user doc
return tokenDB.user;
}else{
//Nuke the token for security
await tokenDB.deleteOne();
//if not scream and shout
badLogin();
}
//standardize bad login response so it's unknown which is bad for security reasons.
function badLogin(){
throw loggerUtils.exceptionSmith(failLine, "unauthorized");
}
}
module.exports = mongoose.model("rememberMe", rememberMeToken);

View file

@ -39,6 +39,7 @@ const pmHandler = require('./app/pm/pmHandler');
const configCheck = require('./utils/configCheck');
const scheduler = require('./utils/scheduler');
const {errorMiddleware} = require('./utils/loggerUtils');
const sessionUtils = require('./utils/sessionUtils');
//Validator
const accountValidator = require('./validators/accountValidator');
//DB Model
@ -143,6 +144,14 @@ mongoose.set("sanitizeFilter", true).connect(dbUrl).then(() => {
process.exit();
});
//Static File Server, set this up first to avoid middleware running on top of it
//Serve client-side libraries
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons'))); //Icon set
app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external'))); //Self-Hosted PoW-based Captcha
app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist'))); //HLS Media Handler
//Server public 'www' folder
app.use(express.static(path.join(__dirname, '../www')));
//Set View Engine
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
@ -164,6 +173,9 @@ io.engine.use(sessionMiddleware);
app.use(accountValidator.rememberMeID());
app.use(accountValidator.rememberMeToken());
//Use remember me middleware
app.use(sessionUtils.rememberMeMiddleware);
//Routes
//Humie-Friendly
app.use('/', indexRouter);
@ -183,14 +195,6 @@ app.use('/tooltip', tooltipRouter);
//Bot-Ready
app.use('/api', apiRouter);
//Static File Server
//Serve client-side libraries
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons'))); //Icon set
app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external'))); //Self-Hosted PoW-based Captcha
app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist'))); //HLS Media Handler
//Server public 'www' folder
app.use(express.static(path.join(__dirname, '../www')));
//Handle error checking
app.use(errorMiddleware);

View file

@ -14,6 +14,9 @@ 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/>.*/
//npm imports
const {validationResult, matchedData} = require('express-validator');
//Local Imports
const config = require('../../config.json');
const {userModel} = require('../schemas/user/userSchema.js');
@ -101,7 +104,7 @@ module.exports.authenticateSession = async function(identifier, secret, req, use
//If we're using remember me tokens
if(useRememberMeToken){
userDB = await rememberMeModel.authenticate(identifier, secret);
//Otherwise
}else{
//Fallback on to username/password authentication
@ -211,5 +214,44 @@ module.exports.processExpiredAttempts = function(){
}
}
module.exports.rememberMeMiddleware = function(req, res, next){
//if we have an un-authenticated user
if(req.session.user == null || req.session.user == ""){
//Check validation result
const validResult = validationResult(req);
//if we don't have errors
if(validResult.isEmpty()){
//Pull verified data from request
const data = matchedData(req);
//If we have a valid remember me id and token
if(data.rememberme != null && data.rememberme.id != null && data.rememberme.token != null){
//Authenticate against standard auth function in remember me mode
module.exports.authenticateSession(data.rememberme.id, data.rememberme.token, req, true).then((userDB)=>{
//Jump to next middleware
next();
}).catch((err)=>{
//Clear out remember me fields
res.clearCookie('rememberme.id');
res.clearCookie('rememberme.token');
//Bitch, Moan, and guess what? That's fuckin' right! COMPLAIN!!!!
return loggerUtils.exceptionHandler(res, err);
});
}else{
//Jump to next middleware, this looks gross but it's only because they made me use .then like a bunch of fucking dicks
next();
}
}else{
//Jump to next middleware
next();
}
}else{
//Jump to next middleware
next();
}
}
module.exports.throttleAttempts = throttleAttempts;
module.exports.maxAttempts = maxAttempts;