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>
Signed| @@ -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": "显示为", |
| @@ -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 | }; |
| 617 | 613 | ||
| 618 | /** | 614 | /** |
| @@ -631,16 +627,6 @@ async function loadPlugins() { | |||
| 631 | } | 627 | } |
| 632 | } | 628 | } |
| 633 | 629 | ||
| 634 | if (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 window | 631 | * Set the title of the terminal window |
| 646 | * @param {string} title Desired title for the window | 632 | * @param {string} title Desired title for the window |
| @@ -654,10 +640,53 @@ function setWindowTitle(title) { | |||
| 654 | } | 640 | } |
| 655 | } | 641 | } |
| 656 | 642 | ||
| 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 | */ | ||
| 648 | function 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 | |||
| 658 | async 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 server | 685 | // User storage module needs to be initialized before starting the server |
| 658 | userModule.initUserStorage(dataRoot) | 686 | userModule.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) { |
| @@ -681,27 +681,27 @@ async function createBackupArchive(handle, response) { | |||
| 681 | } | 681 | } |
| 682 | 682 | ||
| 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 | */ |
| 687 | async function checkAccountsProtection() { | 687 | async 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 | */ | ||
| 702 | async function getAllEnabledUsers() { | ||
| 703 | const users = await getAllUsers(); | ||
| 704 | return users.filter(x => x.enabled); | ||
| 705 | } | 705 | } |
| 706 | 706 | ||
| 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 | }; |