From ab338e80a76dc7aa08c4a057e3550145df72a048 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:16:50 +0800 Subject: [PATCH] Add custom access type for bearer keys to support combined group and server scoping (#530) Co-authored-by: samanhappy --- frontend/src/pages/SettingsPage.tsx | 247 ++++++++++++++++++------- frontend/src/types/index.ts | 2 +- locales/en.json | 1 + locales/fr.json | 1 + locales/tr.json | 1 + locales/zh.json | 1 + src/controllers/bearerKeyController.ts | 4 +- src/db/entities/BearerKey.ts | 2 +- src/services/sseService.ts | 27 ++- src/types/index.ts | 6 +- 10 files changed, 216 insertions(+), 76 deletions(-) diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index f431039..bec2369 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -25,7 +25,7 @@ interface BearerKeyRowProps { name: string; token: string; enabled: boolean; - accessType: 'all' | 'groups' | 'servers'; + accessType: 'all' | 'groups' | 'servers' | 'custom'; allowedGroups: string; allowedServers: string; }, @@ -47,7 +47,7 @@ const BearerKeyRow: React.FC = ({ const [name, setName] = useState(keyData.name); const [token, setToken] = useState(keyData.token); const [enabled, setEnabled] = useState(keyData.enabled); - const [accessType, setAccessType] = useState<'all' | 'groups' | 'servers'>( + const [accessType, setAccessType] = useState<'all' | 'groups' | 'servers' | 'custom'>( keyData.accessType || 'all', ); const [selectedGroups, setSelectedGroups] = useState(keyData.allowedGroups || []); @@ -105,6 +105,13 @@ const BearerKeyRow: React.FC = ({ ); return; } + if (accessType === 'custom' && selectedGroups.length === 0 && selectedServers.length === 0) { + showToast( + t('settings.selectAtLeastOneGroupOrServer') || 'Please select at least one group or server', + 'error', + ); + return; + } setSaving(true); try { @@ -135,6 +142,31 @@ const BearerKeyRow: React.FC = ({ }; const isGroupsMode = accessType === 'groups'; + const isCustomMode = accessType === 'custom'; + + // Helper function to format access type display text + const formatAccessTypeDisplay = (key: BearerKey): string => { + if (key.accessType === 'all') { + return t('settings.bearerKeyAccessAll') || 'All Resources'; + } + if (key.accessType === 'groups') { + return `${t('settings.bearerKeyAccessGroups') || 'Groups'}: ${key.allowedGroups}`; + } + if (key.accessType === 'servers') { + return `${t('settings.bearerKeyAccessServers') || 'Servers'}: ${key.allowedServers}`; + } + if (key.accessType === 'custom') { + const parts: string[] = []; + if (key.allowedGroups && key.allowedGroups.length > 0) { + parts.push(`${t('settings.bearerKeyAccessGroups') || 'Groups'}: ${key.allowedGroups}`); + } + if (key.allowedServers && key.allowedServers.length > 0) { + parts.push(`${t('settings.bearerKeyAccessServers') || 'Servers'}: ${key.allowedServers}`); + } + return `${t('settings.bearerKeyAccessCustom') || 'Custom'}: ${parts.join('; ')}`; + } + return ''; + }; if (isEditing) { return ( @@ -194,7 +226,9 @@ const BearerKeyRow: React.FC = ({ -
- - -
+ {/* Show single selector for groups or servers mode */} + {!isCustomMode && ( +
+ + +
+ )} + + {/* Show both selectors for custom mode */} + {isCustomMode && ( + <> +
+ + +
+
+ + +
+ + )}
-
- - -
+ {newBearerKey.accessType !== 'custom' && ( +
+ + +
+ )} + + {newBearerKey.accessType === 'custom' && ( + <> +
+ + +
+
+ + +
+ + )}