diff --git a/package.json b/package.json
index 86045a3..0076162 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"bcrypt": "^5.1.1",
"bootstrap-icons": "^1.11.3",
"connect-mongo": "^5.1.0",
+ "cookie-parser": "^1.4.7",
"csrf-sync": "^4.0.3",
"ejs": "^3.1.10",
"express": "^4.18.2",
diff --git a/src/controllers/api/account/loginController.js b/src/controllers/api/account/loginController.js
index f5f2130..e7c5345 100644
--- a/src/controllers/api/account/loginController.js
+++ b/src/controllers/api/account/loginController.js
@@ -22,6 +22,7 @@ const {validationResult, matchedData} = require('express-validator');
//local imports
const migrationModel = require('../../../schemas/user/migrationSchema.js');
+const rememberMeModel = require('../../../schemas/user/rememberMeSchema.js');
const sessionUtils = require('../../../utils/sessionUtils');
const hashUtils = require('../../../utils/hashUtils.js');
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
@@ -35,10 +36,39 @@ module.exports.post = async function(req, res){
//if we don't have errors
if(validResult.isEmpty()){
//Pull sanatzied/validated data
- const {user, pass} = matchedData(req);
-
- //try to authenticate the session, and return a successful code if it works
- await sessionUtils.authenticateSession(user, pass, req);
+ const data = matchedData(req);
+
+ //try to authenticate the session, throwing an error and breaking the current code block if user is un-authorized
+ await sessionUtils.authenticateSession(data.user, data.pass, req);
+
+ //If the user already has a remember me token
+ if(data.rememberme != null && data.rememberme.id != null){
+ //Fucking nuke the bitch
+ await rememberMeModel.deleteOne({id: data.rememberme.id})
+
+ //Tell the client to drop the token
+ res.clearCookie("rememberme.id");
+ res.clearCookie("rememberme.token");
+ }
+
+ //If the user requested a rememberMe token (I'm not validation checking a fucking boolean)
+ if(req.body.rememberMe){
+ //Gen user token
+ //requires second DB call, but this enforces password requirement for toke generation while ensuring we only
+ //need one function in the userModel for authentication, even if the second woulda just been a wrapper.
+ //Less attack surface is less attack surface, and this isn't something thats going to be getting constantly called
+ const authToken = await rememberMeModel.genToken(data.user, data.pass);
+
+ //Check config for protocol
+ const secure = config.protocol.toLowerCase() == "https";
+
+ //Set remember me ID and token as browser-side cookies for safe-keeping
+ res.cookie("rememberme.id", authToken.id, {sameSite: 'strict', httpOnly: true, secure});
+ //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});
+ }
+
+ //Tell the browser everything is dandy
return res.sendStatus(200);
}else{
res.status(400);
@@ -64,22 +94,22 @@ module.exports.post = async function(req, res){
return res.sendStatus(301);
}
}
-
+
//Get login attempts
const attempts = sessionUtils.getLoginAttempts(user)
//if we've gone over max attempts
- if(attempts.count > sessionUtils.throttleAttempts){
+ if(attempts != null && attempts.count > sessionUtils.throttleAttempts){
//tell client it needs a captcha
return res.sendStatus(429);
+ }else{
+ //Scream about any un-caught errors
+ return exceptionHandler(res, err);
}
}else{
res.status(400);
return res.send({errors: validResult.array()})
}
-
- //Scream about any un-caught errors
- return exceptionHandler(res, err);
}
}
\ No newline at end of file
diff --git a/src/schemas/user/rememberMeSchema.js b/src/schemas/user/rememberMeSchema.js
index 6b63942..3ac78ef 100644
--- a/src/schemas/user/rememberMeSchema.js
+++ b/src/schemas/user/rememberMeSchema.js
@@ -27,7 +27,7 @@ const crypto = require("node:crypto");
const {mongoose} = require('mongoose');
//Local Imports
-const userSchema = require('./userSchema');
+const {userModel} = require('./userSchema');
const hashUtil = require('../../utils/hashUtils');
const loggerUtils = require('../../utils/loggerUtils');
@@ -67,10 +67,14 @@ const rememberMeToken = new mongoose.Schema({
* Pre-Save function for rememberMeSchema
*/
rememberMeToken.pre('save', async function (next){
+ //Ensure tokens ALWAYS get a new UUID and creation date
+ this.id = crypto.randomUUID();
+ this.date = new Date();
+
//If the token was changed
if(this.isModified("token")){
//Hash that sunnovabitch, no questions asked.
- this.token = hashUtil.hashRememberMeToken(this.token);
+ this.token = await hashUtil.hashRememberMeToken(this.token);
}
//All is good, continue on saving.
@@ -79,10 +83,10 @@ rememberMeToken.pre('save', async function (next){
//statics
rememberMeToken.statics.genToken = async function(user, pass){
- try{
- //Authenticate user and pull document
- const userDB = await userSchema.authenticate(user, pass);
+ //Authenticate user and pull document
+ const userDB = await userModel.authenticate(user, pass);
+ try{
//Generate a cryptographically secure string of 32 bytes in hexidecimal
const token = crypto.randomBytes(32).toString('hex');
@@ -94,7 +98,7 @@ rememberMeToken.statics.genToken = async function(user, pass){
id: tokenDB.id,
token
};
- //If we failed (most likely for bad login)
+ //If we failed for a non-login reason
}catch(err){
return loggerUtils.localExceptionHandler(err);
}
diff --git a/src/server.js b/src/server.js
index 8aedf91..87472a2 100644
--- a/src/server.js
+++ b/src/server.js
@@ -25,6 +25,7 @@ const fs = require('fs');
const express = require('express');
const session = require('express-session');
const {createServer } = require('http');
+const cookieParser = require('cookie-parser');
const { Server } = require('socket.io');
const path = require('path');
const mongoStore = require('connect-mongo');
@@ -38,6 +39,8 @@ const pmHandler = require('./app/pm/pmHandler');
const configCheck = require('./utils/configCheck');
const scheduler = require('./utils/scheduler');
const {errorMiddleware} = require('./utils/loggerUtils');
+//Validator
+const accountValidator = require('./validators/accountValidator');
//DB Model
const statModel = require('./schemas/statSchema');
const flairModel = require('./schemas/flairSchema');
@@ -87,7 +90,11 @@ const sessionMiddleware = session({
secret: config.secrets.sessionSecret,
resave: false,
saveUninitialized: false,
- store: module.exports.store
+ store: module.exports.store,
+ cookie: {
+ sameSite: "strict",
+ secure: config.protocol.toLowerCase() == "https"
+ }
});
//Declare web server
@@ -143,7 +150,9 @@ app.set('views', __dirname + '/views');
//Middlware
//Enable Express
app.use(express.json());
-//app.use(express.urlencoded());
+
+//Enable Express Ccokie-Parser
+app.use(cookieParser());
//Enable Express-Sessions
app.use(sessionMiddleware);
@@ -151,6 +160,10 @@ app.use(sessionMiddleware);
//Enable Express-Session w/ Socket.IO
io.engine.use(sessionMiddleware);
+//Use rememberMe validators accross all requests.
+app.use(accountValidator.rememberMeID());
+app.use(accountValidator.rememberMeToken());
+
//Routes
//Humie-Friendly
app.use('/', indexRouter);
diff --git a/src/utils/sessionUtils.js b/src/utils/sessionUtils.js
index 584411d..970718b 100644
--- a/src/utils/sessionUtils.js
+++ b/src/utils/sessionUtils.js
@@ -18,6 +18,7 @@ along with this program. If not, see