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 enableCorsProxy: false
# Enable multi-user mode # Enable multi-user mode
enableUserAccounts: false 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 # Used to sign session cookies. Will be auto-generated if not set
cookieSecret: '' cookieSecret: ''
# Disable security checks - NOT RECOMMENDED # Disable security checks - NOT RECOMMENDED

View File

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

View File

@ -38,12 +38,20 @@
<img src="img/logo.png" alt="SillyTavern" class="logo"> <img src="img/logo.png" alt="SillyTavern" class="logo">
<span>Welcome to SillyTavern</span> <span>Welcome to SillyTavern</span>
</h2> </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="userListBlock" class="wide100p">
<div id="userList" class="flex-container justifyCenter"></div> <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;" <div id="passwordEntryBlock" style="display:none;"
class="flex-container flexFlowColumn alignItemsCenter"> 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 class="flex-container">
<div id="loginButton" class="menu_button">Login</div> <div id="loginButton" class="menu_button">Login</div>
<div id="recoverPassword" class="menu_button">Recover</div> <div id="recoverPassword" class="menu_button">Recover</div>
@ -55,8 +63,8 @@
Recovery code has been posted to the server console. Recovery code has been posted to the server console.
</div> </div>
<input id="recoveryCode" class="text_pole" type="text" placeholder="Recovery code"> <input id="recoveryCode" class="text_pole" type="text" placeholder="Recovery code">
<input id="newPassword" class="text_pole" type="password" placeholder="New password..."> <input id="newPassword" class="text_pole" type="password" placeholder="New password" autocomplete="new-password">
<div class="flex-container"> <div class="flex-container flexGap10">
<div id="sendRecovery" class="menu_button">Send</div> <div id="sendRecovery" class="menu_button">Send</div>
<div id="cancelRecovery" class="menu_button">Cancel</div> <div id="cancelRecovery" class="menu_button">Cancel</div>
</div> </div>

View File

@ -2,6 +2,7 @@
* CRSF token for requests. * CRSF token for requests.
*/ */
let csrfToken = ''; let csrfToken = '';
let discreetLogin = false;
/** /**
* Gets a CSRF token from the server. * Gets a CSRF token from the server.
@ -25,6 +26,17 @@ async function getUserList() {
'X-CSRF-Token': csrfToken, '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(); const userListObj = await response.json();
console.log(userListObj); console.log(userListObj);
return userListObj; return userListObj;
@ -188,9 +200,15 @@ function onCancelRecoveryClick() {
displayError(''); displayError('');
} }
(async function () { /**
csrfToken = await getCsrfToken(); * Configures the login page for normal login.
const userList = await getUserList(); * @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); console.log(userList);
for (const user of userList) { for (const user of userList) {
const userBlock = $('<div></div>').addClass('userSelect'); const userBlock = $('<div></div>').addClass('userSelect');
@ -202,6 +220,47 @@ function onCancelRecoveryClick() {
userBlock.on('click', () => onUserSelected(user)); userBlock.on('click', () => onUserSelected(user));
$('#userList').append(userBlock); $('#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 = ''; document.getElementById('shadow_popup').style.opacity = '';
$('#cancelRecovery').on('click', onCancelRecoveryClick); $('#cancelRecovery').on('click', onCancelRecoveryClick);
})(); })();

View File

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