Enable CSRF for public endpoints. Split users module. Add rate limiter.

This commit is contained in:
Cohee
2024-04-09 21:58:16 +03:00
parent 497f38111f
commit 411a8ef8a7
12 changed files with 596 additions and 378 deletions

View File

@ -1,15 +1,46 @@
/**
* CRSF token for requests.
*/
let csrfToken = '';
/**
* Gets a CSRF token from the server.
* @returns {Promise<string>} CSRF token
*/
async function getCsrfToken() {
const response = await fetch('/csrf-token');
const data = await response.json();
return data.token;
}
/**
* Gets a list of users from the server.
* @returns {Promise<object>} List of users
*/
async function getUserList() {
const response = await fetch('/api/users/list');
const response = await fetch('/api/users/list', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
});
const userListObj = await response.json();
console.log(userListObj);
return userListObj;
}
/**
* Requests a recovery code for the user.
* @param {string} handle User handle
* @returns {Promise<void>}
*/
async function sendRecoveryPart1(handle) {
const response = await fetch('/api/users/recover-step1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ handle }),
});
@ -22,6 +53,13 @@ async function sendRecoveryPart1(handle) {
showRecoveryBlock();
}
/**
* Sets a new password for the user using the recovery code.
* @param {string} handle User handle
* @param {string} code Recovery code
* @param {string} newPassword New password
* @returns {Promise<void>}
*/
async function sendRecoveryPart2(handle, code, newPassword) {
const recoveryData = {
handle,
@ -33,6 +71,7 @@ async function sendRecoveryPart2(handle, code, newPassword) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify(recoveryData),
});
@ -46,6 +85,50 @@ async function sendRecoveryPart2(handle, code, newPassword) {
await performLogin(handle, newPassword);
}
/**
* Attempts to log in the user.
* @param {string} handle User's handle
* @param {string} password User's password
* @returns {Promise<void>}
*/
async function performLogin(handle, password) {
const userInfo = {
handle: handle,
password: password,
};
try {
const response = await fetch('/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify(userInfo),
});
if (!response.ok) {
const errorData = await response.json();
return displayError(errorData.error || 'An error occurred');
}
const data = await response.json();
if (data.handle) {
console.log(`Successfully logged in as ${handle}!`);
redirectToHome();
}
} catch (error) {
console.error('Error logging in:', error);
displayError(String(error));
}
}
/**
* Handles the user selection event.
* @param {object} user User object
* @returns {Promise<void>}
*/
async function onUserSelected(user) {
// No password, just log in
if (!user.password) {
@ -72,52 +155,33 @@ async function onUserSelected(user) {
displayError('');
}
/**
* Displays an error message to the user.
* @param {string} message Error message
*/
function displayError(message) {
$('#errorMessage').text(message);
}
async function performLogin(handle, password) {
const userInfo = {
handle: handle,
password: password,
};
try {
const response = await fetch('/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userInfo),
});
if (!response.ok) {
const errorData = await response.json();
return displayError(errorData.error || 'An error occurred');
}
const data = await response.json();
if (data.handle) {
console.log(`Successfully logged in as ${handle}!`);
redirectToHome();
}
} catch (error) {
console.error('Error logging in:', error);
displayError(String(error));
}
}
/**
* Redirects the user to the home page.
*/
function redirectToHome() {
window.location.href = '/';
}
/**
* Hides the password entry block and shows the password recovery block.
*/
function showRecoveryBlock() {
$('#passwordEntryBlock').hide();
$('#passwordRecoveryBlock').show();
displayError('');
}
/**
* Hides the password recovery block and shows the password entry block.
*/
function onCancelRecoveryClick() {
$('#passwordRecoveryBlock').hide();
$('#passwordEntryBlock').show();
@ -125,6 +189,7 @@ function onCancelRecoveryClick() {
}
(async function () {
csrfToken = await getCsrfToken();
const userList = await getUserList();
console.log(userList);
for (const user of userList) {
@ -137,6 +202,6 @@ function onCancelRecoveryClick() {
userBlock.on('click', () => onUserSelected(user));
$('#userList').append(userBlock);
}
document.body.style.display = '';
document.getElementById('shadow_popup').style.opacity = '';
$('#cancelRecovery').on('click', onCancelRecoveryClick);
})();