redo ant csrf token

ensure anti csrf token and session is only created after login
This commit is contained in:
BIG2EYEZ
2024-01-13 01:17:07 +08:00
parent f2a9facfaf
commit 183e73eca2
7 changed files with 96 additions and 47 deletions

View File

@ -19,32 +19,19 @@ const PORT = process.env.PORT || 3000;
require("dotenv").config();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(
session({
secret: process.env.key,
resave: false,
saveUninitialized: true,
cookie: {
secure: false, // Make sure to set this to true in a production environment with HTTPS
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // Session duration in milliseconds (here set to 1 day)
},
})
);
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
// Make the CSRF token available in the response context
res.locals.csrfToken = req.session.csrfToken;
console.log(`Server-side CSRF Token: ${req.session.csrfToken}`);
next();
});
app.set("view engine", "ejs");
app.use(session({
secret: process.env.key,
resave: false,
saveUninitialized: true,
cookie: {
secure: false, // Make sure to set this to true in a production environment with HTTPS
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // Session duration in milliseconds (here set to 1 day)
},
}));
function isAuthenticated(req, res, next) {
if (req.session && req.session.authenticated) {
return next();
@ -144,17 +131,11 @@ const logActivity = async (username, success, message) => {
app.post('/login', [
body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'),
body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'),
body('csrf_token').escape().trim().isLength({ min: 1 }).withMessage('CSRF token must not be empty'),
],
async (req, res) => {
try {
const errors = validationResult(req);
// Validate CSRF token
if (req.body.csrf_token !== req.session.csrfToken) {
return res.status(403).send("Invalid CSRF token");
}
if (!errors.isEmpty()) {
// Handle validation errors, e.g., return an error message to the client
return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken });
@ -222,17 +203,11 @@ async (req, res) => {
// OTP verification route
app.post("/verify-otp", [
body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'),
body('csrf_token').escape().trim().isLength({ min: 1 }).withMessage('CSRF token must not be empty'),
],
async (req, res) => {
try {
const errors = validationResult(req);
// Validate CSRF token
if (req.body.csrf_token !== req.session.csrfToken) {
return res.status(403).send("Invalid CSRF token");
}
if (!errors.isEmpty()) {
// Handle validation errors, e.g., return an error message to the client
return res.render('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken });
@ -240,10 +215,16 @@ async (req, res) => {
const enteredOTP = req.body.otp;
if (!req.session) {
// If session is not defined, handle accordingly
console.error("Session is not defined.");
return res.status(500).send("Internal Server Error");
}
if (enteredOTP === req.session.otp) {
// Log successful OTP entry and login
// Log successful OTP entry
if (req.body.username) {
await logActivity(req.body.username, true, "OTP entered correctly. Successful login");
await logActivity(req.body.username, true, "OTP entered correctly");
}
// Correct OTP, generate a session token
@ -253,8 +234,22 @@ async (req, res) => {
req.session.authenticated = true;
req.session.username = req.body.username;
req.session.sessionToken = sessionToken;
res.locals.csrfToken = req.session.csrfToken;
console.log(`Server-side CSRF Token: ${req.session.csrfToken}`);
// Generate and store anti-CSRF token in the session
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
// Set anti-CSRF token in res.locals
res.locals.csrfToken = req.session.csrfToken;
// Log anti-CSRF token
console.log(`Generated Anti-CSRF Token: ${req.session.csrfToken}`);
// Implement secure session handling:
// 1. Set secure, HttpOnly, and SameSite flags
// 2. Set an expiration time for the session token
// 3. Regenerate the session after authentication
res.cookie('sessionToken', sessionToken, { secure: true, httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // Expires in 1 day
console.log(`Generated Session Token: ${sessionToken}`);
// Redirect to home page with session token
@ -273,7 +268,9 @@ async (req, res) => {
res.status(500).send("Internal Server Error");
}
});
app.get("/logout", (req, res) => {
try {
const username = req.session.username || "Unknown User";

View File

@ -146,6 +146,7 @@
</div>
<script>
const allUsers = <%- JSON.stringify(allUsers) %>;
</script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>

View File

@ -1,6 +1,5 @@
$(document).ready(function () {
$('#resetPasswordLink').on('click', function () {
$('#resetPasswordFormContainer').show();
@ -429,5 +428,7 @@ $('#resetPasswordForm').on('submit', function (e) {
});
});

View File

@ -86,7 +86,6 @@ button:hover {
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="Enter your password" required>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<button type="submit">Login</button>
</form>

View File

@ -69,7 +69,7 @@
<label for="otp">OTP:</label>
<input type="text" id="otp" name="otp" required>
<br>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<button type="submit">Submit OTP</button>
</form>
</body>