Add discreet login mode

This commit is contained in:
Cohee 2024-04-10 22:00:08 +03:00
parent bd4d8847ce
commit 2306a4e34d
5 changed files with 83 additions and 8 deletions

View File

@ -22,6 +22,8 @@ basicAuthUser:
enableCorsProxy: false
# Enable multi-user mode
enableUserAccounts: false
# Enable discreet login mode: hides user list on the login screen
enableDiscreetLogin: false
# Used to sign session cookies. Will be auto-generated if not set
cookieSecret: ''
# Disable security checks - NOT RECOMMENDED

View File

@ -15,5 +15,6 @@
"**/node_modules/*",
"public/lib",
"backups/*",
"data/*"
]
}

View File

@ -38,12 +38,20 @@
<img src="img/logo.png" alt="SillyTavern" class="logo">
<span>Welcome to SillyTavern</span>
</h2>
<h3>Select a User</h3>
<h3 id="normalLoginPrompt">
Select a User
</h3>
<h3 id="discreetLoginPrompt">
Enter Login Details
</h3>
<div id="userListBlock" class="wide100p">
<div id="userList" class="flex-container justifyCenter"></div>
<div id="handleEntryBlock" style="display:none;" class="flex-container flexFlowColumn alignItemsCenter">
<input id="userHandle" class="text_pole" type="text" placeholder="User handle" autocomplete="username">
</div>
<div id="passwordEntryBlock" style="display:none;"
class="flex-container flexFlowColumn alignItemsCenter">
<input id="userPassword" class="text_pole" type="password" placeholder="Enter a password...">
<input id="userPassword" class="text_pole" type="password" placeholder="Password" autocomplete="current-password">
<div class="flex-container">
<div id="loginButton" class="menu_button">Login</div>
<div id="recoverPassword" class="menu_button">Recover</div>
@ -55,8 +63,8 @@
Recovery code has been posted to the server console.
</div>
<input id="recoveryCode" class="text_pole" type="text" placeholder="Recovery code">
<input id="newPassword" class="text_pole" type="password" placeholder="New password...">
<div class="flex-container">
<input id="newPassword" class="text_pole" type="password" placeholder="New password" autocomplete="new-password">
<div class="flex-container flexGap10">
<div id="sendRecovery" class="menu_button">Send</div>
<div id="cancelRecovery" class="menu_button">Cancel</div>
</div>

View File

@ -2,6 +2,7 @@
* CRSF token for requests.
*/
let csrfToken = '';
let discreetLogin = false;
/**
* Gets a CSRF token from the server.
@ -25,6 +26,17 @@ async function getUserList() {
'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;
@ -188,9 +200,15 @@ function onCancelRecoveryClick() {
displayError('');
}
(async function () {
csrfToken = await getCsrfToken();
const userList = await getUserList();
/**
* 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');
@ -202,6 +220,47 @@ function onCancelRecoveryClick() {
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);
})();

View File

@ -3,9 +3,10 @@ const storage = require('node-persist');
const express = require('express');
const { RateLimiterMemory, RateLimiterRes } = require('rate-limiter-flexible');
const { jsonParser, getIpFromRequest } = require('../express-common');
const { color, Cache } = require('../util');
const { color, Cache, getConfigValue } = require('../util');
const { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } = require('../users');
const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false);
const MFA_CACHE = new Cache(5 * 60 * 1000);
const router = express.Router();
@ -20,6 +21,10 @@ const recoverLimiter = new RateLimiterMemory({
router.post('/list', async (_request, response) => {
try {
if (DISCREET_LOGIN) {
return response.sendStatus(204);
}
/** @type {import('../users').User[]} */
const users = await storage.values(x => x.key.startsWith(KEY_PREFIX));
const viewModels = users