Update server.js to trust UserAccounts securely (#2447) * Update server.js to trust UserAccounts securely * Update zh-cn.json btw * Clarify security logic * update logic * Fix filtering of enabled users. * Fix account name logging * More friendly log * Even friendlier message * Revert deleted keys --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

46c91bec679bba44a0593b6ea9649cf6bea97fd7

steve green <steve_green@qq.com>

Signed
3 files changed, +71 -32Showing whitespace changes
public/locales/zh-cn.json+13 -4
@@ -334,6 +334,9 @@
334 "vLLM API key": "vLLM API 密钥",334 "vLLM API key": "vLLM API 密钥",
335 "Example: 127.0.0.1:8000": "例如:http://127.0.0.1:8000",335 "Example: 127.0.0.1:8000": "例如:http://127.0.0.1:8000",
336 "vLLM Model": "vLLM 模型",336 "vLLM Model": "vLLM 模型",
337 "HuggingFace Token": "HuggingFace 代币",
338 "Endpoint URL": "端点 URL",
339 "Example: https://****.endpoints.huggingface.cloud": "例如:https://****.endpoints.huggingface.cloud",
337 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(用于OpenAI API的包装器)",340 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(用于OpenAI API的包装器)",
338 "Aphrodite API key": "Aphrodite API 密钥",341 "Aphrodite API key": "Aphrodite API 密钥",
339 "Aphrodite Model": "Aphrodite 模型",342 "Aphrodite Model": "Aphrodite 模型",
@@ -419,6 +422,8 @@
419 "Prompt Post-Processing": "提示词后处理",422 "Prompt Post-Processing": "提示词后处理",
420 "Applies additional processing to the prompt before sending it to the API.": "在将提示词发送到 API 之前对其进行额外处理。",423 "Applies additional processing to the prompt before sending it to the API.": "在将提示词发送到 API 之前对其进行额外处理。",
421 "prompt_post_processing_none": "未选择",424 "prompt_post_processing_none": "未选择",
425 "01.AI API Key": "01.AI API密钥",
426 "01.AI Model": "01.AI模型",
422 "Additional Parameters": "附加参数",427 "Additional Parameters": "附加参数",
423 "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "通过发送简短的测试消息验证您的API连接。请注意,您将因此而消耗额度!",428 "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "通过发送简短的测试消息验证您的API连接。请注意,您将因此而消耗额度!",
424 "Test Message": "发送测试消息",429 "Test Message": "发送测试消息",
@@ -1033,6 +1038,8 @@
1033 "Sticky": "粘性",1038 "Sticky": "粘性",
1034 "Entries with a cooldown can't be activated N messages after being triggered.": "具有冷却时间的条目在触发后 N 条消息内无法被激活。",1039 "Entries with a cooldown can't be activated N messages after being triggered.": "具有冷却时间的条目在触发后 N 条消息内无法被激活。",
1035 "Cooldown": "冷却",1040 "Cooldown": "冷却",
1041 "Entries with a delay can't be activated until there are N messages present in the chat.": "直到聊天中出现 N 条消息时,延迟的条目才能被激活。",
1042 "Delay": "延迟",
1036 "Filter to Character(s)": "应用到角色",1043 "Filter to Character(s)": "应用到角色",
1037 "Character Exclusion": "反选角色",1044 "Character Exclusion": "反选角色",
1038 "-- Characters not found --": "-- 未找到角色 --",1045 "-- Characters not found --": "-- 未找到角色 --",
@@ -1077,6 +1084,7 @@
1077 "Move message up": "将消息上移",1084 "Move message up": "将消息上移",
1078 "Move message down": "将消息下移",1085 "Move message down": "将消息下移",
1079 "Enlarge": "放大",1086 "Enlarge": "放大",
1087 "Caption": "标题",
1080 "Welcome to SillyTavern!": "欢迎来到 SillyTavern!",1088 "Welcome to SillyTavern!": "欢迎来到 SillyTavern!",
1081 "welcome_message_part_1": "阅读",1089 "welcome_message_part_1": "阅读",
1082 "welcome_message_part_2": "官方文档",1090 "welcome_message_part_2": "官方文档",
@@ -1113,10 +1121,6 @@
1113 "alternate_greetings_hint_2": "按钮即可开始!",1121 "alternate_greetings_hint_2": "按钮即可开始!",
1114 "Alternate Greeting #": "额外问候语 #",1122 "Alternate Greeting #": "额外问候语 #",
1115 "(This will be the first message from the character that starts every chat)": "(这将是角色在每次聊天开始时发送的第一条消息)",1123 "(This will be the first message from the character that starts every chat)": "(这将是角色在每次聊天开始时发送的第一条消息)",
1116 "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
1117 "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
1118 "Always forbidden": "始终禁止",
1119 "Always allowed": "始终允许",
1120 "View contents": "查看内容",1124 "View contents": "查看内容",
1121 "Remove the file": "删除文件",1125 "Remove the file": "删除文件",
1122 "Unique to this chat": "此聊天独有",1126 "Unique to this chat": "此聊天独有",
@@ -1240,6 +1244,7 @@
1240 "Message Template": "消息模板",1244 "Message Template": "消息模板",
1241 "(use _space": "(使用",1245 "(use _space": "(使用",
1242 "macro)": "宏指令)",1246 "macro)": "宏指令)",
1247 "Automatically caption images": "自动为图像添加标题",
1243 "Edit captions before saving": "保存前编辑标题",1248 "Edit captions before saving": "保存前编辑标题",
1244 "Character Expressions": "角色表情",1249 "Character Expressions": "角色表情",
1245 "Translate text to English before classification": "分类之前将文本翻译成英文",1250 "Translate text to English before classification": "分类之前将文本翻译成英文",
@@ -1579,6 +1584,10 @@
1579 "Warning:": "警告:",1584 "Warning:": "警告:",
1580 "This action is irreversible.": "此操作不可逆。",1585 "This action is irreversible.": "此操作不可逆。",
1581 "Type the user's handle below to confirm:": "在下面输入用户的名称以确认:",1586 "Type the user's handle below to confirm:": "在下面输入用户的名称以确认:",
1587 "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
1588 "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
1589 "Always forbidden": "始终禁止",
1590 "Always allowed": "始终允许",
1582 "help_format_1": "文本格式化命令:",1591 "help_format_1": "文本格式化命令:",
1583 "help_format_2": "*文本*",1592 "help_format_2": "*文本*",
1584 "help_format_3": "显示为",1593 "help_format_3": "显示为",
server.js+43 -14
@@ -609,10 +609,6 @@ const postSetupTasks = async function () {
609 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));609 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
610 }610 }
611 }611 }
612
613 if (listen && !basicAuthMode && enableAccounts) {
614 await userModule.checkAccountsProtection();
615 }
616};612};
617613
618/**614/**
@@ -631,16 +627,6 @@ async function loadPlugins() {
631 }627 }
632}628}
633629
634if (listen && !enableWhitelist && !basicAuthMode) {
635 if (getConfigValue('securityOverride', false)) {
636 console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
637 }
638 else {
639 console.error(color.red('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.'));
640 process.exit(1);
641 }
642}
643
644/**630/**
645 * Set the title of the terminal window631 * Set the title of the terminal window
646 * @param {string} title Desired title for the window632 * @param {string} title Desired title for the window
@@ -654,10 +640,53 @@ function setWindowTitle(title) {
654 }640 }
655}641}
656642
643/**
644 * Prints an error message and exits the process if necessary
645 * @param {string} message The error message to print
646 * @returns {void}
647 */
648function logSecurityAlert(message) {
649 if (basicAuthMode || enableWhitelist) return; // safe!
650 console.error(color.red(message));
651 if (getConfigValue('securityOverride', false)) {
652 console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
653 return;
654 }
655 process.exit(1);
656}
657
658async function verifySecuritySettings() {
659 // Skip all security checks as listen is set to false
660 if (!listen) {
661 return;
662 }
663
664 if (!enableAccounts) {
665 logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.');
666 }
667
668 const users = await userModule.getAllEnabledUsers();
669 const unprotectedUsers = users.filter(x => !x.password);
670 const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin);
671
672 if (unprotectedUsers.length > 0) {
673 console.warn(color.blue('A friendly reminder that the following users are not password protected:'));
674 unprotectedUsers.map(x => `${color.yellow(x.handle)} ${color.red(x.admin ? '(admin)' : '')}`).forEach(x => console.warn(x));
675 console.log();
676 console.warn(`Consider setting a password in the admin panel or by using the ${color.blue('recover.js')} script.`);
677 console.log();
678
679 if (unprotectedAdminUsers.length > 0) {
680 logSecurityAlert('If you are not using basic authentication or whitelisting, you should set a password for all admin users.');
681 }
682 }
683}
684
657// User storage module needs to be initialized before starting the server685// User storage module needs to be initialized before starting the server
658userModule.initUserStorage(dataRoot)686userModule.initUserStorage(dataRoot)
659 .then(userModule.ensurePublicDirectoriesExist)687 .then(userModule.ensurePublicDirectoriesExist)
660 .then(userModule.migrateUserData)688 .then(userModule.migrateUserData)
689 .then(verifySecuritySettings)
661 .then(preSetupTasks)690 .then(preSetupTasks)
662 .finally(() => {691 .finally(() => {
663 if (cliArguments.ssl) {692 if (cliArguments.ssl) {
src/users.js+15 -14
@@ -681,27 +681,27 @@ async function createBackupArchive(handle, response) {
681}681}
682682
683/**683/**
684 * Checks if any admin users are not password protected. If so, logs a warning.684 * Gets all of the users.
685 * @returns {Promise<void>}685 * @returns {Promise<User[]>}
686 */686 */
687async function checkAccountsProtection() {687async function getAllUsers() {
688 if (!ENABLE_ACCOUNTS) {688 if (!ENABLE_ACCOUNTS) {
689 return;689 return [];
690 }690 }
691
692 /**691 /**
693 * @type {User[]}692 * @type {User[]}
694 */693 */
695 const users = await storage.values();694 const users = await storage.values();
696 const unprotectedUsers = users.filter(x => x.enabled && x.admin && !x.password);695 return users;
697 if (unprotectedUsers.length > 0) {
698 console.warn(color.red('The following admin users are not password protected:'));
699 unprotectedUsers.forEach(x => console.warn(color.yellow(x.handle)));
700 console.log();
701 console.warn('Please disable them or set a password in the admin panel.');
702 console.log();
703 await delay(3000);
704}696}
697
698/**
699 * Gets all of the enabled users.
700 * @returns {Promise<User[]>}
701 */
702async function getAllEnabledUsers() {
703 const users = await getAllUsers();
704 return users.filter(x => x.enabled);
705}705}
706706
707/**707/**
@@ -738,6 +738,7 @@ module.exports = {
738 shouldRedirectToLogin,738 shouldRedirectToLogin,
739 createBackupArchive,739 createBackupArchive,
740 tryAutoLogin,740 tryAutoLogin,
741 checkAccountsProtection,741 getAllUsers,
742 getAllEnabledUsers,
742 router,743 router,
743};744};