ANTI CSRF FUNCTION

CSRF PROTECTION DONE FOR USER CREATION USER DELETION AND MANUAL PASSWORD RESET FUNCTION
This commit is contained in:
BIG2EYEZ 2024-01-13 15:34:16 +08:00
parent f91e99330a
commit bfa005b08b
3 changed files with 205 additions and 231 deletions

View File

@ -243,7 +243,8 @@ async (req, res) => {
// Log anti-CSRF token // Log anti-CSRF token
console.log(`Generated Anti-CSRF Token: ${req.session.csrfToken}`); console.log(`Generated Anti-CSRF Token: ${req.session.csrfToken}`);
// Set CSRF token as a cookie
// Implement secure session handling: // Implement secure session handling:
// 1. Set secure, HttpOnly, and SameSite flags // 1. Set secure, HttpOnly, and SameSite flags
// 2. Set an expiration time for the session token // 2. Set an expiration time for the session token
@ -269,7 +270,12 @@ async (req, res) => {
} }
}); });
function setCSRFToken(req, res, next) {
res.locals.csrfToken = req.session.csrfToken;
next();
}
app.use(setCSRFToken);
app.get("/logout", (req, res) => { app.get("/logout", (req, res) => {
try { try {
@ -342,7 +348,7 @@ app.get("/inusers", isAuthenticated, (req, res) => {
} }
// Render the inusers page with JSON data // Render the inusers page with JSON data
res.render("inusers", { allUsers }); res.render("inusers", { allUsers ,csrfToken: req.session.csrfToken });
}); });
}); });
function isStrongPassword(password) { function isStrongPassword(password) {
@ -399,201 +405,144 @@ const logUserCreationActivity = async (creatorUsername, success, message) => {
}; };
app.post( app.post(
'/createUser', '/createUser',
[ [
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(), body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(),
body('email').isEmail().withMessage('Invalid email address').normalizeEmail(), body('email').isEmail().withMessage('Invalid email address').normalizeEmail(),
body('password').custom((value) => { body('password').custom((value) => {
if (!isStrongPassword(value)) { if (!isStrongPassword(value)) { throw new Error('Password does not meet complexity requirements'); } return true;
throw new Error('Password does not meet complexity requirements'); }),
} body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(),
return true; ],
}), async (req, res) => {
body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(), try {
], const errors = validationResult(req);
async (req, res) => {
try { if (!errors.isEmpty()) {
const errors = validationResult(req); return res.status(400).json({ errors: errors.array() });
}
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() }); // Validate the anti-CSRF token
} const submittedCSRFToken = req.body.csrf_token;
const { name, username, email, password, jobTitle } = req.body; if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
console.log("Sanitized Input:", { return res.status(403).json({ error: 'CSRF token mismatch' });
name, }
username,
email, // Extract user input
password: "*****", // Avoid logging passwords const { name, username, email, password, jobTitle } = req.body;
jobTitle,
}); // Extract the username of the user creating a new user
// Extract the username of the user creating a new user const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session
const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session
// Additional password complexity check
// Validate password complexity (additional check) if (!isStrongPassword(password)) {
if (!isStrongPassword(password)) { return res.status(400).json({ error: "Password does not meet complexity requirements" });
return res }
.status(400)
.json({ error: "Password does not meet complexity requirements" }); // Check if the username is already taken
} const checkUsernameQuery = "SELECT * FROM users WHERE username = ?";
connection.query(checkUsernameQuery, [username], (usernameQueryErr, usernameResults) => {
// Check if the username is already taken if (usernameQueryErr) {
const checkUsernameQuery = "SELECT * FROM users WHERE username = ?"; console.error("Error checking username:", usernameQueryErr);
connection.query( return res.status(500).json({ error: "Internal Server Error" });
checkUsernameQuery, }
[username],
(usernameQueryErr, usernameResults) => { if (usernameResults.length > 0) {
if (usernameQueryErr) { // Log unsuccessful user creation due to username taken
console.error("Error checking username:", usernameQueryErr); logUserCreationActivity(creatorUsername, false, "username taken");
return res.status(500).json({ error: "Internal Server Error" }); return res.status(400).json({
} error: "Username is already taken",
message: "Username is already taken. Please choose a different username."
if (usernameResults.length > 0) { });
// Log unsuccessful user creation due to username taken }
logUserCreationActivity(creatorUsername, false, "username taken");
return res // Check if the email is already taken
.status(400) const checkEmailQuery = "SELECT * FROM users WHERE email = ?";
.json({ connection.query(checkEmailQuery, [email], (emailQueryErr, emailResults) => {
error: "Username is already taken", if (emailQueryErr) {
message: "Username is already taken. Please choose a different username.", console.error("Error checking email:", emailQueryErr);
}); return res.status(500).json({ error: "Internal Server Error" });
} }
// Check if the email is already taken if (emailResults.length > 0) {
const checkEmailQuery = "SELECT * FROM users WHERE email = ?"; // Log unsuccessful user creation due to email taken
connection.query( logUserCreationActivity(creatorUsername, false, "email taken");
checkEmailQuery, return res.status(400).json({
[email], error: "Email is already in use",
(emailQueryErr, emailResults) => { message: "Email is already in use. Please choose another email."
if (emailQueryErr) { });
console.error("Error checking email:", emailQueryErr); }
return res.status(500).json({ error: "Internal Server Error" });
} // Hash the password before storing it in the database
bcrypt.hash(password, 10, (hashError, hashedPassword) => {
if (emailResults.length > 0) { if (hashError) {
// Log unsuccessful user creation due to email taken console.error("Error hashing password:", hashError);
logUserCreationActivity(creatorUsername, false, "email taken"); return res.status(500).json({ error: "Internal Server Error" });
return res }
.status(400)
.json({ // Start a transaction
error: "Email is already in use", connection.beginTransaction((transactionErr) => {
message: "Email is already in use. Please choose another email.", if (transactionErr) {
}); console.error("Error starting transaction:", transactionErr);
} return res.status(500).json({ error: "Internal Server Error" });
}
// Hash the password before storing it in the database
bcrypt.hash(password, 10, (hashError, hashedPassword) => { // Define the insert query
if (hashError) { const insertUserQuery =
console.error("Error hashing password:", hashError); "INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)";
return res.status(500).json({ error: "Internal Server Error" });
} // Log the query and its parameters
console.log("Insert Query:", insertUserQuery);
// Start a transaction console.log("Query Parameters:", [name, username, email, hashedPassword, jobTitle]);
connection.beginTransaction((transactionErr) => {
if (transactionErr) { // Execute the query with user data
console.error("Error starting transaction:", transactionErr); connection.query(insertUserQuery, [name, username, email, hashedPassword, jobTitle], (queryErr, results) => {
return res if (queryErr) {
.status(500) console.error("Error executing query:", queryErr);
.json({ error: "Internal Server Error" });
} // Rollback the transaction in case of an error
connection.rollback((rollbackErr) => {
// Define the insert query if (rollbackErr) {
const insertUserQuery = console.error("Error rolling back transaction:", rollbackErr);
"INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)"; }
// Log unsuccessful user creation due to an error
// Log the query and its parameters logUserCreationActivity(creatorUsername, false, "internal error");
console.log("Insert Query:", insertUserQuery); return res.status(500).json({ error: "Internal Server Error" });
console.log("Query Parameters:", [ });
name, return;
username, }
email,
hashedPassword, // Commit the transaction
jobTitle, connection.commit((commitErr) => {
]); if (commitErr) {
console.error("Error committing transaction:", commitErr);
// Execute the query with user data // Log unsuccessful user creation due to an error
connection.query( logUserCreationActivity(creatorUsername, false, "internal error");
insertUserQuery, return res.status(500).json({ error: "Internal Server Error" });
[name, username, email, hashedPassword, jobTitle], }
(queryErr, results) => {
if (queryErr) { // Log successful user creation
console.error("Error executing query:", queryErr); logUserCreationActivity(creatorUsername, true, "user created successfully");
// Rollback the transaction in case of an error // Redirect to "/inusers"
connection.rollback((rollbackErr) => { res.redirect('/inusers');
if (rollbackErr) { });
console.error( });
"Error rolling back transaction:", });
rollbackErr });
); });
} });
// Log unsuccessful user creation due to an error } catch (error) {
logUserCreationActivity( console.error("Error creating user:", error);
creatorUsername, // Log unsuccessful user creation due to an error
false, logUserCreationActivity(req.session.username, false, "internal error"); // Adjust this based on how you store the creator's username in your session
"internal error" res.status(500).json({ error: "Internal Server Error" });
); }
return res }
.status(500) );
.json({ error: "Internal Server Error" });
});
return;
}
// Commit the transaction
connection.commit((commitErr) => {
if (commitErr) {
console.error(
"Error committing transaction:",
commitErr
);
// Log unsuccessful user creation due to an error
logUserCreationActivity(
creatorUsername,
false,
"internal error"
);
return res
.status(500)
.json({ error: "Internal Server Error" });
}
// Log successful user creation
logUserCreationActivity(
creatorUsername,
true,
"user created successfully"
);
// Log the results of the query
console.log("Query Results:", results);
// Respond with a success message
res
.status(201)
.json({ message: "User created successfully" });
});
}
);
});
});
}
);
}
);
} catch (error) {
console.error("Error creating user:", error);
// Log unsuccessful user creation due to an error
logUserCreationActivity(req.session.username, false, "internal error"); // Adjust this based on how you store the creator's username in your session
res.status(500).json({ error: "Internal Server Error" });
}
}
);
app.get("/forgot-password", (req, res) => {
res.render("forgot-password"); // Assuming you have an EJS template for this
});
app.get("/forgot-password", (req, res) => { app.get("/forgot-password", (req, res) => {
res.render("forgot-password", { error: null, success: null }); res.render("forgot-password", { error: null, success: null });
@ -777,8 +726,14 @@ app.get("/reset-password/:token", (req, res) => {
}); });
}); });
app.post("/reset-password", async (req, res) => { app.post("/reset-password", async (req, res) => {
const { username, password, confirmPassword } = req.body;
const { username, password, confirmPassword, csrf_token } = req.body;
const creatorUsername = req.session.username; const creatorUsername = req.session.username;
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
// Sanitize the inputs // Sanitize the inputs
const sanitizedUsername = validator.escape(username); const sanitizedUsername = validator.escape(username);
@ -899,13 +854,20 @@ app.get('/api/users', (req, res) => {
}); });
}); });
// Route to delete a user by username
app.delete('/api/deleteUser/:username', async (req, res) => { app.delete('/api/deleteUser/:username', async (req, res) => {
const { username } = req.params; const { username } = req.params;
const query = 'DELETE FROM users WHERE username = ?'; const query = 'DELETE FROM users WHERE username = ?';
const creatorUsername = req.session.username; const creatorUsername = req.session.username;
try { try {
// Extract CSRF token from the request body
const { csrfToken } = req.body;
// Compare CSRF token with the one stored in the session
if (csrfToken !== req.session.csrfToken) {
return res.status(403).json({ success: false, error: 'CSRF token mismatch' });
}
// Log deletion activity to USER_LOGS // Log deletion activity to USER_LOGS
const deletionActivity = `User ${username} has been successfully deleted`; const deletionActivity = `User ${username} has been successfully deleted`;
const logQuery = 'INSERT INTO USER_LOGS (USERNAME, ACTIVITY, TIMESTAMP) VALUES (?, ?, CURRENT_TIMESTAMP)'; const logQuery = 'INSERT INTO USER_LOGS (USERNAME, ACTIVITY, TIMESTAMP) VALUES (?, ?, CURRENT_TIMESTAMP)';
@ -925,6 +887,7 @@ app.get('/api/users', (req, res) => {
} }
}); });
async function executeQuery(sql, values) { async function executeQuery(sql, values) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
connection.query(sql, values, (err, results) => { connection.query(sql, values, (err, results) => {

View File

@ -122,6 +122,7 @@
<input type="password" name="confirmPassword" id="resetConfirmPassword" placeholder="Confirm new password" required> <input type="password" name="confirmPassword" id="resetConfirmPassword" placeholder="Confirm new password" required>
</div> </div>
</div> </div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button"> <div class="button">
<input type="submit" value="Reset Password"> <input type="submit" value="Reset Password">
</div> </div>
@ -129,6 +130,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div id="deleteUserContainer" style="display: none;"> <div id="deleteUserContainer" style="display: none;">
<h3>Delete User</h3> <h3>Delete User</h3>
<div class="search-container"> <div class="search-container">
@ -137,7 +139,9 @@
</div> </div>
<div id="searchResultsContainer" style="display: none;"> <div id="searchResultsContainer" style="display: none;">
<h4>Search Results</h4> <h4>Search Results</h4>
<ul id="searchResultsList"></ul> <ul id="searchResultsList">
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
</ul>
</div> </div>
</div> </div>

View File

@ -1,5 +1,3 @@
$(document).ready(function () { $(document).ready(function () {
$('#resetPasswordLink').on('click', function () { $('#resetPasswordLink').on('click', function () {
$('#resetPasswordFormContainer').show(); $('#resetPasswordFormContainer').show();
@ -177,13 +175,22 @@ function displaySearchResults(users) {
$('#searchResultsContainer').hide(); $('#searchResultsContainer').hide();
} }
} }
// Event listener for delete user button in search results // Event listener for delete user button in search results
$('#searchResultsList').on('click', '.deleteUserButton', function () { $('#searchResultsList').on('click', '.deleteUserButton', function () {
const usernameToDelete = $(this).data('username'); const usernameToDelete = $(this).data('username');
const csrfToken = $('[name="csrf_token"]').val(); // Access the CSRF token by name
console.log(csrfToken);
console.log('Before fetch for user deletion'); console.log('Before fetch for user deletion');
// Make a fetch request to delete the user
// Make a fetch request to delete the user with CSRF token in headers
fetch(`/api/deleteUser/${usernameToDelete}`, { fetch(`/api/deleteUser/${usernameToDelete}`, {
method: 'DELETE', method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ csrfToken }), // Include CSRF token in the request body
}) })
.then(response => { .then(response => {
console.log('Inside fetch response handler'); console.log('Inside fetch response handler');
@ -277,8 +284,7 @@ function resetFormFields() {
$('#confirmPassword').val(''); $('#confirmPassword').val('');
$('#jobTitle').val(''); $('#jobTitle').val('');
} }
const csrf_token = $('#userForm input[name="csrf_token"]').val();
$('#userForm').on('submit', function (e) { $('#userForm').on('submit', function (e) {
e.preventDefault(); e.preventDefault();
@ -298,38 +304,40 @@ function resetFormFields() {
alert('Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.'); alert('Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.');
return; return;
} }
fetch('/createUser', { fetch('/createUser', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
username: username, username: username,
email: email, email: email,
password: password, password: password,
jobTitle: jobTitle, jobTitle: jobTitle,
csrf_token: csrf_token, // Include the CSRF token in the body
}), }),
}) })
.then(response => { .then(response => {
if (response.status === 201) { if (response.status === 201) {
// Status 201 indicates successful creation // Status 201 indicates successful creation
return response.json(); return response.json();
} else { } else {
return response.json().then(data => { return response.json().then(data => {
throw new Error(data.error || `HTTP error! Status: ${response.status}`); throw new Error(data.error || `HTTP error! Status: ${response.status}`);
}); });
} }
}) })
.then(data => { .then(data => {
console.log('User registration success:', data); console.log('User registration success:', data);
alert('User registered successfully!'); alert('User registered successfully!');
resetFormFields(); resetFormFields();
}) })
.catch(error => { .catch(error => {
console.error('User registration error:', error); console.error('User registration error:', error);
handleRegistrationError(error); handleRegistrationError(error);
}); });
}); });
@ -368,7 +376,7 @@ $('#resetPasswordForm').on('submit', function (e) {
const username = $('#resetUsername').val(); const username = $('#resetUsername').val();
const password = $('#resetPassword').val(); const password = $('#resetPassword').val();
const confirmPassword = $('#resetConfirmPassword').val(); const confirmPassword = $('#resetConfirmPassword').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
console.log('Username:', username); console.log('Username:', username);
console.log('New Password:', password); console.log('New Password:', password);
@ -384,8 +392,7 @@ $('#resetPasswordForm').on('submit', function (e) {
return; return;
} }
// Make a fetch request fetch('/reset-password', {
fetch('/reset-password', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -394,6 +401,7 @@ $('#resetPasswordForm').on('submit', function (e) {
username: username, username: username,
password: password, password: password,
confirmPassword: confirmPassword, confirmPassword: confirmPassword,
csrf_token: csrf_token
}), }),
}) })
.then(response => { .then(response => {
@ -430,5 +438,4 @@ $('#resetPasswordForm').on('submit', function (e) {