277 lines
7.7 KiB
JavaScript
277 lines
7.7 KiB
JavaScript
/**
|
|
* CRSF token for requests.
|
|
*/
|
|
let csrfToken = '';
|
|
let discreetLogin = false;
|
|
|
|
/**
|
|
* 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', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfToken,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
return displayError(errorData.error || 'An error occurred');
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
discreetLogin = true;
|
|
return [];
|
|
}
|
|
|
|
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 }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
return displayError(errorData.error || 'An error occurred');
|
|
}
|
|
|
|
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,
|
|
code,
|
|
newPassword,
|
|
};
|
|
|
|
const response = await fetch('/api/users/recover-step2', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfToken,
|
|
},
|
|
body: JSON.stringify(recoveryData),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
return displayError(errorData.error || 'An error occurred');
|
|
}
|
|
|
|
console.log(`Successfully recovered password for ${handle}!`);
|
|
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) {
|
|
return await performLogin(user.handle, '');
|
|
}
|
|
|
|
$('#passwordRecoveryBlock').hide();
|
|
$('#passwordEntryBlock').show();
|
|
$('#loginButton').off('click').on('click', async () => {
|
|
const password = String($('#userPassword').val());
|
|
await performLogin(user.handle, password);
|
|
});
|
|
|
|
$('#recoverPassword').off('click').on('click', async () => {
|
|
await sendRecoveryPart1(user.handle);
|
|
});
|
|
|
|
$('#sendRecovery').off('click').on('click', async () => {
|
|
const code = String($('#recoveryCode').val());
|
|
const newPassword = String($('#newPassword').val());
|
|
await sendRecoveryPart2(user.handle, code, newPassword);
|
|
});
|
|
|
|
displayError('');
|
|
}
|
|
|
|
/**
|
|
* Displays an error message to the user.
|
|
* @param {string} message Error message
|
|
*/
|
|
function displayError(message) {
|
|
$('#errorMessage').text(message);
|
|
}
|
|
|
|
/**
|
|
* Redirects the user to the home page.
|
|
* Preserves the query string.
|
|
*/
|
|
function redirectToHome() {
|
|
window.location.href = '/' + window.location.search;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
displayError('');
|
|
}
|
|
|
|
/**
|
|
* Configures the login page for normal login.
|
|
* @param {import('../../src/users').UserViewModel[]} userList List of users
|
|
*/
|
|
function configureNormalLogin(userList) {
|
|
console.log('Discreet login is disabled');
|
|
$('#handleEntryBlock').hide();
|
|
$('#normalLoginPrompt').show();
|
|
$('#discreetLoginPrompt').hide();
|
|
console.log(userList);
|
|
for (const user of userList) {
|
|
const userBlock = $('<div></div>').addClass('userSelect');
|
|
const avatarBlock = $('<div></div>').addClass('avatar');
|
|
avatarBlock.append($('<img>').attr('src', user.avatar));
|
|
userBlock.append(avatarBlock);
|
|
userBlock.append($('<span></span>').addClass('userName').text(user.name));
|
|
userBlock.append($('<small></small>').addClass('userHandle').text(user.handle));
|
|
userBlock.on('click', () => onUserSelected(user));
|
|
$('#userList').append(userBlock);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configures the login page for discreet login.
|
|
*/
|
|
function configureDiscreetLogin() {
|
|
console.log('Discreet login is enabled');
|
|
$('#handleEntryBlock').show();
|
|
$('#normalLoginPrompt').hide();
|
|
$('#discreetLoginPrompt').show();
|
|
$('#userList').hide();
|
|
$('#passwordRecoveryBlock').hide();
|
|
$('#passwordEntryBlock').show();
|
|
$('#loginButton').off('click').on('click', async () => {
|
|
const handle = String($('#userHandle').val());
|
|
const password = String($('#userPassword').val());
|
|
await performLogin(handle, password);
|
|
});
|
|
|
|
$('#recoverPassword').off('click').on('click', async () => {
|
|
const handle = String($('#userHandle').val());
|
|
await sendRecoveryPart1(handle);
|
|
});
|
|
|
|
$('#sendRecovery').off('click').on('click', async () => {
|
|
const handle = String($('#userHandle').val());
|
|
const code = String($('#recoveryCode').val());
|
|
const newPassword = String($('#newPassword').val());
|
|
await sendRecoveryPart2(handle, code, newPassword);
|
|
});
|
|
}
|
|
|
|
(async function () {
|
|
csrfToken = await getCsrfToken();
|
|
const userList = await getUserList();
|
|
|
|
if (discreetLogin) {
|
|
configureDiscreetLogin();
|
|
} else {
|
|
configureNormalLogin(userList);
|
|
}
|
|
document.getElementById('shadow_popup').style.opacity = '';
|
|
$('#cancelRecovery').on('click', onCancelRecoveryClick);
|
|
$(document).on('keydown', (evt) => {
|
|
if (evt.key === 'Enter' && document.activeElement.tagName === 'INPUT') {
|
|
if ($('#passwordRecoveryBlock').is(':visible')) {
|
|
$('#sendRecovery').trigger('click');
|
|
} else {
|
|
$('#loginButton').trigger('click');
|
|
}
|
|
}
|
|
});
|
|
})();
|