diff --git a/client/src/api/oauthScopes.test.ts b/client/src/api/oauthScopes.test.ts index 81712c63f..f0efbbaab 100644 --- a/client/src/api/oauthScopes.test.ts +++ b/client/src/api/oauthScopes.test.ts @@ -1,9 +1,9 @@ -// FE-OAUTH-SCOPES-001 to FE-OAUTH-SCOPES-011 +// FE-OAUTH-SCOPES-001 to FE-OAUTH-SCOPES-014 import { describe, it, expect } from 'vitest' import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { SCOPE_GROUPS, ALL_SCOPES, SCOPE_GROUP_NAMES, getScopesByGroup } from './oauthScopes' +import { SCOPE_GROUPS, ALL_SCOPES, SCOPE_GROUP_NAMES, getScopesByGroup, PRESET_OPT_IN_ONLY, PRESET_SCOPES_DEFAULT, PRESET_SCOPES_READONLY } from './oauthScopes' // The consent page mirrors the server's scope list by hand, so read the server // file and compare. Parsing keeps this a client-only test (no server import). @@ -31,6 +31,7 @@ describe('SCOPE_GROUPS', () => { 'vacay:read', 'vacay:write', 'geo:read', 'weather:read', 'journey:read', 'journey:write', 'journey:share', + 'plugins:use', ] for (const scope of expected) { expect(SCOPE_GROUPS).toHaveProperty(scope) @@ -123,3 +124,26 @@ describe('getScopesByGroup', () => { expect(groups['oauth.scope.group.trips']).toBeUndefined() }) }) + +describe('client presets', () => { + // The presets are written as "everything except deletes", so a new scope joins + // them silently. That is right for a scope over the user's own data and wrong + // for one that runs third-party code, so the exclusion gets a test. + it('FE-OAUTH-SCOPES-012: never offers an opt-in-only scope by default', () => { + expect(PRESET_OPT_IN_ONLY.size).toBeGreaterThan(0) + for (const scope of PRESET_OPT_IN_ONLY) { + expect(PRESET_SCOPES_DEFAULT).not.toContain(scope) + expect(PRESET_SCOPES_READONLY).not.toContain(scope) + } + }) + + it('FE-OAUTH-SCOPES-013: still offers every other non-destructive scope', () => { + const expected = ALL_SCOPES.filter(s => !s.includes(':delete') && !PRESET_OPT_IN_ONLY.has(s)) + expect(PRESET_SCOPES_DEFAULT).toEqual(expected) + expect(PRESET_SCOPES_DEFAULT).toContain('trips:write') + }) + + it('FE-OAUTH-SCOPES-014: the read-only preset stays read-only', () => { + expect(PRESET_SCOPES_READONLY.every(s => s.endsWith(':read'))).toBe(true) + }) +}) diff --git a/client/src/api/oauthScopes.ts b/client/src/api/oauthScopes.ts index 3f266e519..eefce47dd 100644 --- a/client/src/api/oauthScopes.ts +++ b/client/src/api/oauthScopes.ts @@ -48,10 +48,27 @@ export const SCOPE_GROUPS: Record = { 'files:content': { labelKey: 'oauth.scope.files:content.label', descriptionKey: 'oauth.scope.files:content.description', groupKey: 'oauth.scope.group.files' }, 'settings:read': { labelKey: 'oauth.scope.settings:read.label', descriptionKey: 'oauth.scope.settings:read.description', groupKey: 'oauth.scope.group.settings' }, 'settings:write': { labelKey: 'oauth.scope.settings:write.label', descriptionKey: 'oauth.scope.settings:write.description', groupKey: 'oauth.scope.group.settings' }, + 'plugins:use': { labelKey: 'oauth.scope.plugins:use.label', descriptionKey: 'oauth.scope.plugins:use.description', groupKey: 'oauth.scope.group.plugins' }, } export const ALL_SCOPES = Object.keys(SCOPE_GROUPS) +/** + * Scopes a client preset must never tick for you. + * + * The presets are written as "everything except deletes", so a new scope joins + * them silently. That is right for a new read or write scope on data the user + * already owns, and wrong for plugins:use: it turns on third-party tool + * execution, which nobody would have chosen by installing an editor. + */ +export const PRESET_OPT_IN_ONLY = new Set(['plugins:use']) + +/** The five full-access presets: everything destructive or opt-in-only removed. */ +export const PRESET_SCOPES_DEFAULT = ALL_SCOPES.filter(s => !s.includes(':delete') && !PRESET_OPT_IN_ONLY.has(s)) + +/** The read-only preset (VS Code). Kept beside the default so the pair stays visible. */ +export const PRESET_SCOPES_READONLY = ALL_SCOPES.filter(s => s.endsWith(':read') && !PRESET_OPT_IN_ONLY.has(s)) + // Group all scopes for the client registration form export const SCOPE_GROUP_NAMES = [...new Set(Object.values(SCOPE_GROUPS).map(s => s.groupKey))] diff --git a/client/src/components/Settings/IntegrationsTab.tsx b/client/src/components/Settings/IntegrationsTab.tsx index 965130177..28ef1e62f 100644 --- a/client/src/components/Settings/IntegrationsTab.tsx +++ b/client/src/components/Settings/IntegrationsTab.tsx @@ -9,7 +9,7 @@ import PhotoProvidersSection from './PhotoProvidersSection' import AirTrailConnectionSection from './AirTrailConnectionSection' import LlmConnectionSection from './LlmConnectionSection' import ApiKeysSection from './ApiKeysSection' -import { ALL_SCOPES } from '../../api/oauthScopes' +import { PRESET_SCOPES_DEFAULT, PRESET_SCOPES_READONLY } from '../../api/oauthScopes' import ScopeGroupPicker from '../OAuth/ScopeGroupPicker' import { useAuthStore } from '../../store/authStore' @@ -27,42 +27,42 @@ const OAUTH_PRESETS: OAuthPreset[] = [ label: 'Claude.ai', name: 'Claude.ai', uris: 'https://claude.ai/api/mcp/auth_callback', - scopes: ALL_SCOPES.filter(s => !s.includes(':delete')), + scopes: PRESET_SCOPES_DEFAULT, }, { id: 'claude-desktop', label: 'Claude Desktop', name: 'Claude Desktop', uris: 'http://localhost', - scopes: ALL_SCOPES.filter(s => !s.includes(':delete')), + scopes: PRESET_SCOPES_DEFAULT, }, { id: 'cursor', label: 'Cursor', name: 'Cursor', uris: 'http://localhost', - scopes: ALL_SCOPES.filter(s => !s.includes(':delete')), + scopes: PRESET_SCOPES_DEFAULT, }, { id: 'vscode', label: 'VS Code', name: 'VS Code / Copilot', uris: 'http://localhost', - scopes: ALL_SCOPES.filter(s => s.endsWith(':read')), + scopes: PRESET_SCOPES_READONLY, }, { id: 'windsurf', label: 'Windsurf', name: 'Windsurf', uris: 'http://localhost', - scopes: ALL_SCOPES.filter(s => !s.includes(':delete')), + scopes: PRESET_SCOPES_DEFAULT, }, { id: 'zed', label: 'Zed', name: 'Zed', uris: 'http://localhost', - scopes: ALL_SCOPES.filter(s => !s.includes(':delete')), + scopes: PRESET_SCOPES_DEFAULT, }, ] diff --git a/client/src/mobile/screens/settings/MSettingsMcp.tsx b/client/src/mobile/screens/settings/MSettingsMcp.tsx index 0e3e0ca2c..b4304cf6e 100644 --- a/client/src/mobile/screens/settings/MSettingsMcp.tsx +++ b/client/src/mobile/screens/settings/MSettingsMcp.tsx @@ -3,7 +3,7 @@ import { Check, ChevronDown, ChevronRight, Copy, KeyRound, Plus, RefreshCw, Term import { useTranslation } from '../../../i18n' import { useToast } from '../../../components/shared/Toast' import { authApi, oauthApi } from '../../../api/client' -import { ALL_SCOPES } from '../../../api/oauthScopes' +import { PRESET_SCOPES_DEFAULT, PRESET_SCOPES_READONLY } from '../../../api/oauthScopes' import MScopeGroupPicker from './MScopeGroupPicker' import MSheet from '../../components/MSheet' import MToggle from '../../components/MToggle' @@ -21,12 +21,12 @@ interface OAuthPreset { } const OAUTH_PRESETS: OAuthPreset[] = [ - { id: 'claude-web', label: 'Claude.ai', name: 'Claude.ai', uris: 'https://claude.ai/api/mcp/auth_callback', scopes: ALL_SCOPES.filter((s) => !s.includes(':delete')) }, - { id: 'claude-desktop', label: 'Claude Desktop', name: 'Claude Desktop', uris: 'http://localhost', scopes: ALL_SCOPES.filter((s) => !s.includes(':delete')) }, - { id: 'cursor', label: 'Cursor', name: 'Cursor', uris: 'http://localhost', scopes: ALL_SCOPES.filter((s) => !s.includes(':delete')) }, - { id: 'vscode', label: 'VS Code', name: 'VS Code / Copilot', uris: 'http://localhost', scopes: ALL_SCOPES.filter((s) => s.endsWith(':read')) }, - { id: 'windsurf', label: 'Windsurf', name: 'Windsurf', uris: 'http://localhost', scopes: ALL_SCOPES.filter((s) => !s.includes(':delete')) }, - { id: 'zed', label: 'Zed', name: 'Zed', uris: 'http://localhost', scopes: ALL_SCOPES.filter((s) => !s.includes(':delete')) }, + { id: 'claude-web', label: 'Claude.ai', name: 'Claude.ai', uris: 'https://claude.ai/api/mcp/auth_callback', scopes: PRESET_SCOPES_DEFAULT }, + { id: 'claude-desktop', label: 'Claude Desktop', name: 'Claude Desktop', uris: 'http://localhost', scopes: PRESET_SCOPES_DEFAULT }, + { id: 'cursor', label: 'Cursor', name: 'Cursor', uris: 'http://localhost', scopes: PRESET_SCOPES_DEFAULT }, + { id: 'vscode', label: 'VS Code', name: 'VS Code / Copilot', uris: 'http://localhost', scopes: PRESET_SCOPES_READONLY }, + { id: 'windsurf', label: 'Windsurf', name: 'Windsurf', uris: 'http://localhost', scopes: PRESET_SCOPES_DEFAULT }, + { id: 'zed', label: 'Zed', name: 'Zed', uris: 'http://localhost', scopes: PRESET_SCOPES_DEFAULT }, ] interface OAuthClient { diff --git a/client/src/pages/OAuthAuthorizePage.tsx b/client/src/pages/OAuthAuthorizePage.tsx index 158bba85c..cbf6af6cd 100644 --- a/client/src/pages/OAuthAuthorizePage.tsx +++ b/client/src/pages/OAuthAuthorizePage.tsx @@ -4,6 +4,20 @@ import { Lock, ShieldCheck, AlertTriangle, Loader2, LogIn } from 'lucide-react' import { useTranslation } from '../i18n' import { useOAuthAuthorize } from './oauthAuthorize/useOAuthAuthorize' +/** + * The glyph beside a scope on the consent screen, keyed by its mode. + * + * ':use' reads as an action rather than a level of access to your data, so it + * does not belong on the read/write/delete ladder: falling through to the eye + * would tell the user a tool-running scope only looks at things. + */ +function scopeGlyph(scope: string): string { + if (scope.endsWith(':delete')) return '🗑️' + if (scope.endsWith(':use')) return '🧩' + if (scope.endsWith(':write')) return '✏️' + return '👁️' +} + export default function OAuthAuthorizePage(): React.ReactElement { const { t } = useTranslation() // Page = wiring container: the validate→consent state machine lives in the hook. @@ -160,7 +174,7 @@ export default function OAuthAuthorizePage(): React.ReactElement { className="mt-0.5 rounded flex-shrink-0" /> - {s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'} + {scopeGlyph(s)}

{keys ? t(keys.labelKey) : s}

@@ -186,7 +200,7 @@ export default function OAuthAuthorizePage(): React.ReactElement { return (
- {s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'} + {scopeGlyph(s)}

{keys ? t(keys.labelKey) : s}

diff --git a/server/src/mcp/scopes.ts b/server/src/mcp/scopes.ts index 760021e28..5863e384a 100644 --- a/server/src/mcp/scopes.ts +++ b/server/src/mcp/scopes.ts @@ -42,6 +42,17 @@ export const SCOPES = { FILES_CONTENT: 'files:content', SETTINGS_READ: 'settings:read', SETTINGS_WRITE: 'settings:write', + // One coarse scope, and a fourth mode rather than a per-plugin group. Which + // plugins are installed is runtime data, so a per-plugin scope cannot be + // statically enumerated: it would break the ${group}:${mode} derivation and + // the AssertExact guards in nest-mcp-policy.ts, and its consent copy could + // not be written ahead of time for a third-party plugin name. Tokens issued + // before a plugin existed could never carry its scope either. + // + // Per-plugin granularity already exists one layer down, as the admin's + // mcp:tools grant, and that is the real boundary: this scope only decides + // whether plugin tools are advertised to a client at all. + PLUGINS_USE: 'plugins:use', } as const; export type Scope = typeof SCOPES[keyof typeof SCOPES]; @@ -92,6 +103,7 @@ export const SCOPE_INFO: Record = { 'files:content': { label: 'Read file contents', description: 'Read what is inside an uploaded document, such as a booking PDF or a ticket', group: 'Files' }, 'settings:read': { label: 'View your preferences', description: 'Read units, time format, language, default currency, and start page', group: 'Settings' }, 'settings:write': { label: 'Change your preferences', description: 'Change units, time format, language, default currency, and start page. Never stored API keys', group: 'Settings' }, + 'plugins:use': { label: 'Run plugin tools', description: 'Let this client call tools published by the plugins an administrator installed and approved. Each plugin acts with the access it was already granted, not with the scopes on this token', group: 'Plugins' }, }; // --------------------------------------------------------------------------- diff --git a/server/tests/unit/mcp/scopes.test.ts b/server/tests/unit/mcp/scopes.test.ts index f922f641d..339fb96e2 100644 --- a/server/tests/unit/mcp/scopes.test.ts +++ b/server/tests/unit/mcp/scopes.test.ts @@ -46,7 +46,7 @@ describe('ALL_SCOPES', () => { expect(ALL_SCOPES.length).toBeGreaterThan(0); }); - it('derives exactly the 16 known scope groups (ScopeGroup lockstep)', () => { + it('derives exactly the 17 known scope groups (ScopeGroup lockstep)', () => { // The runtime half of the ScopeGroup lockstep — the type half is // MCP_ACCESS_GROUPS_MATCH_SCOPE_GROUPS in src/mcp/nest-mcp-policy.ts, // covered by `npm run typecheck`. If this list changes, the MCP @@ -63,6 +63,7 @@ describe('ALL_SCOPES', () => { 'notifications', 'packing', 'places', + 'plugins', 'reservations', 'settings', 'todos', diff --git a/shared/src/i18n/ar/oauth.ts b/shared/src/i18n/ar/oauth.ts index 54036bfdc..bd5c753e3 100644 --- a/shared/src/i18n/ar/oauth.ts +++ b/shared/src/i18n/ar/oauth.ts @@ -105,5 +105,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'قراءة الوحدات وتنسيق الوقت واللغة والعملة الافتراضية وصفحة البداية', 'oauth.scope.settings:write.label': 'تغيير تفضيلاتك', 'oauth.scope.settings:write.description': 'تغيير الوحدات وتنسيق الوقت واللغة والعملة الافتراضية وصفحة البداية. لا مفاتيح API المخزنة أبدًا', + 'oauth.scope.group.plugins': 'الإضافات', + 'oauth.scope.plugins:use.label': 'تشغيل أدوات الإضافات', + 'oauth.scope.plugins:use.description': 'السماح لهذا التطبيق باستدعاء الأدوات التي توفرها الإضافات التي ثبّتها المسؤول ووافق عليها. تعمل كل إضافة بالصلاحيات الممنوحة لها مسبقًا، وليس بنطاقات هذا الرمز', }; export default oauth; diff --git a/shared/src/i18n/br/oauth.ts b/shared/src/i18n/br/oauth.ts index f67f140b5..eebc2bc2e 100644 --- a/shared/src/i18n/br/oauth.ts +++ b/shared/src/i18n/br/oauth.ts @@ -107,5 +107,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Ler unidades, formato de hora, idioma, moeda padrão e página inicial', 'oauth.scope.settings:write.label': 'Alterar suas preferências', 'oauth.scope.settings:write.description': 'Alterar unidades, formato de hora, idioma, moeda padrão e página inicial. Nunca as chaves de API salvas', + 'oauth.scope.group.plugins': 'Plugins', + 'oauth.scope.plugins:use.label': 'Executar ferramentas de plugins', + 'oauth.scope.plugins:use.description': 'Permite que este cliente chame ferramentas publicadas pelos plugins que um administrador instalou e aprovou. Cada plugin age com as permissões que já recebeu, não com os escopos deste token', }; export default oauth; diff --git a/shared/src/i18n/ca/oauth.ts b/shared/src/i18n/ca/oauth.ts index 49228d9a4..5661b590c 100644 --- a/shared/src/i18n/ca/oauth.ts +++ b/shared/src/i18n/ca/oauth.ts @@ -109,5 +109,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Llegir unitats, format d’hora, idioma, moneda per defecte i pàgina d’inici', 'oauth.scope.settings:write.label': 'Canviar les teves preferències', 'oauth.scope.settings:write.description': 'Canviar unitats, format d’hora, idioma, moneda per defecte i pàgina d’inici. Mai les claus API desades', + 'oauth.scope.group.plugins': 'Connectors', + 'oauth.scope.plugins:use.label': 'Executar eines de connectors', + 'oauth.scope.plugins:use.description': 'Permet que aquest client cridi eines publicades pels connectors que un administrador ha instal·lat i aprovat. Cada connector actua amb els permisos que ja tenia concedits, no amb els àmbits d\'aquest testimoni', }; export default oauth; diff --git a/shared/src/i18n/cs/oauth.ts b/shared/src/i18n/cs/oauth.ts index 29914ad9d..314f15cad 100644 --- a/shared/src/i18n/cs/oauth.ts +++ b/shared/src/i18n/cs/oauth.ts @@ -105,5 +105,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Číst jednotky, formát času, jazyk, výchozí měnu a úvodní stránku', 'oauth.scope.settings:write.label': 'Změnit vaše předvolby', 'oauth.scope.settings:write.description': 'Změnit jednotky, formát času, jazyk, výchozí měnu a úvodní stránku. Nikdy uložené API klíče', + 'oauth.scope.group.plugins': 'Pluginy', + 'oauth.scope.plugins:use.label': 'Spouštět nástroje pluginů', + 'oauth.scope.plugins:use.description': 'Umožní tomuto klientovi volat nástroje zveřejněné pluginy, které správce nainstaloval a schválil. Každý plugin jedná s oprávněními, která už dostal, nikoli s rozsahy tohoto tokenu', }; export default oauth; diff --git a/shared/src/i18n/de/oauth.ts b/shared/src/i18n/de/oauth.ts index 99beea982..6e7cafa29 100644 --- a/shared/src/i18n/de/oauth.ts +++ b/shared/src/i18n/de/oauth.ts @@ -109,5 +109,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Einheiten, Zeitformat, Sprache, Standardwährung und Startseite lesen', 'oauth.scope.settings:write.label': 'Einstellungen ändern', 'oauth.scope.settings:write.description': 'Einheiten, Zeitformat, Sprache, Standardwährung und Startseite ändern. Niemals gespeicherte API-Schlüssel', + 'oauth.scope.group.plugins': 'Plugins', + 'oauth.scope.plugins:use.label': 'Plugin-Tools ausführen', + 'oauth.scope.plugins:use.description': 'Erlaubt diesem Client, Tools aufzurufen, die von den durch einen Administrator installierten und freigegebenen Plugins bereitgestellt werden. Jedes Plugin handelt mit den ihm bereits erteilten Rechten, nicht mit den Berechtigungen dieses Tokens', }; export default oauth; diff --git a/shared/src/i18n/en/oauth.ts b/shared/src/i18n/en/oauth.ts index 350598c8f..2c162552c 100644 --- a/shared/src/i18n/en/oauth.ts +++ b/shared/src/i18n/en/oauth.ts @@ -106,5 +106,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Read units, time format, language, default currency, and start page', 'oauth.scope.settings:write.label': 'Change your preferences', 'oauth.scope.settings:write.description': 'Change units, time format, language, default currency, and start page. Never stored API keys', + 'oauth.scope.group.plugins': 'Plugins', + 'oauth.scope.plugins:use.label': 'Run plugin tools', + 'oauth.scope.plugins:use.description': 'Let this client call tools published by the plugins an administrator installed and approved. Each plugin acts with the access it was already granted, not with the scopes on this token', }; export default oauth; diff --git a/shared/src/i18n/es/oauth.ts b/shared/src/i18n/es/oauth.ts index 8746c3d5b..40e749336 100644 --- a/shared/src/i18n/es/oauth.ts +++ b/shared/src/i18n/es/oauth.ts @@ -106,5 +106,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Leer unidades, formato de hora, idioma, moneda predeterminada y página de inicio', 'oauth.scope.settings:write.label': 'Cambiar tus preferencias', 'oauth.scope.settings:write.description': 'Cambiar unidades, formato de hora, idioma, moneda predeterminada y página de inicio. Nunca las claves API guardadas', + 'oauth.scope.group.plugins': 'Complementos', + 'oauth.scope.plugins:use.label': 'Ejecutar herramientas de complementos', + 'oauth.scope.plugins:use.description': 'Permite a este cliente llamar a las herramientas publicadas por los complementos que un administrador instaló y aprobó. Cada complemento actúa con los permisos que ya tenía concedidos, no con los ámbitos de este token', }; export default oauth; diff --git a/shared/src/i18n/fr/oauth.ts b/shared/src/i18n/fr/oauth.ts index 5dd222a52..c2d43a920 100644 --- a/shared/src/i18n/fr/oauth.ts +++ b/shared/src/i18n/fr/oauth.ts @@ -109,5 +109,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Lire les unités, le format horaire, la langue, la devise par défaut et la page d’accueil', 'oauth.scope.settings:write.label': 'Modifier vos préférences', 'oauth.scope.settings:write.description': 'Modifier les unités, le format horaire, la langue, la devise par défaut et la page d’accueil. Jamais les clés API enregistrées', + 'oauth.scope.group.plugins': 'Extensions', + 'oauth.scope.plugins:use.label': 'Exécuter les outils d\'extension', + 'oauth.scope.plugins:use.description': 'Autorise ce client à appeler les outils publiés par les extensions installées et approuvées par un administrateur. Chaque extension agit avec les accès qui lui ont déjà été accordés, et non avec les portées de ce jeton', }; export default oauth; diff --git a/shared/src/i18n/gr/oauth.ts b/shared/src/i18n/gr/oauth.ts index 133ab0b81..b38c55a70 100644 --- a/shared/src/i18n/gr/oauth.ts +++ b/shared/src/i18n/gr/oauth.ts @@ -111,5 +111,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Ανάγνωση μονάδων, μορφής ώρας, γλώσσας, προεπιλεγμένου νομίσματος και αρχικής σελίδας', 'oauth.scope.settings:write.label': 'Αλλαγή προτιμήσεων', 'oauth.scope.settings:write.description': 'Αλλαγή μονάδων, μορφής ώρας, γλώσσας, προεπιλεγμένου νομίσματος και αρχικής σελίδας. Ποτέ αποθηκευμένων κλειδιών API', + 'oauth.scope.group.plugins': 'Πρόσθετα', + 'oauth.scope.plugins:use.label': 'Εκτέλεση εργαλείων προσθέτων', + 'oauth.scope.plugins:use.description': 'Επιτρέπει σε αυτήν την εφαρμογή να καλεί εργαλεία που δημοσιεύουν τα πρόσθετα που εγκατέστησε και ενέκρινε ένας διαχειριστής. Κάθε πρόσθετο ενεργεί με τα δικαιώματα που του έχουν ήδη δοθεί, όχι με τα εύρη αυτού του διακριτικού', }; export default oauth; diff --git a/shared/src/i18n/hu/oauth.ts b/shared/src/i18n/hu/oauth.ts index f16fd6034..03fa54edf 100644 --- a/shared/src/i18n/hu/oauth.ts +++ b/shared/src/i18n/hu/oauth.ts @@ -109,5 +109,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Mértékegységek, időformátum, nyelv, alapértelmezett pénznem és kezdőoldal olvasása', 'oauth.scope.settings:write.label': 'Beállítások módosítása', 'oauth.scope.settings:write.description': 'Mértékegységek, időformátum, nyelv, alapértelmezett pénznem és kezdőoldal módosítása. Soha nem a tárolt API-kulcsok', + 'oauth.scope.group.plugins': 'Bővítmények', + 'oauth.scope.plugins:use.label': 'Bővítményeszközök futtatása', + 'oauth.scope.plugins:use.description': 'Engedélyezi, hogy ez az ügyfél meghívja a rendszergazda által telepített és jóváhagyott bővítmények eszközeit. Minden bővítmény a már megkapott jogosultságaival működik, nem a token hatóköreivel', }; export default oauth; diff --git a/shared/src/i18n/id/oauth.ts b/shared/src/i18n/id/oauth.ts index 4b1b2bff4..32ebb5b3b 100644 --- a/shared/src/i18n/id/oauth.ts +++ b/shared/src/i18n/id/oauth.ts @@ -106,5 +106,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Membaca satuan, format waktu, bahasa, mata uang bawaan, dan halaman awal', 'oauth.scope.settings:write.label': 'Ubah preferensi Anda', 'oauth.scope.settings:write.description': 'Mengubah satuan, format waktu, bahasa, mata uang bawaan, dan halaman awal. Tidak pernah kunci API tersimpan', + 'oauth.scope.group.plugins': 'Plugin', + 'oauth.scope.plugins:use.label': 'Jalankan alat plugin', + 'oauth.scope.plugins:use.description': 'Izinkan klien ini memanggil alat yang disediakan oleh plugin yang dipasang dan disetujui administrator. Setiap plugin bertindak dengan akses yang sudah diberikan kepadanya, bukan dengan cakupan token ini', }; export default oauth; diff --git a/shared/src/i18n/it/oauth.ts b/shared/src/i18n/it/oauth.ts index fc743448e..76116f88a 100644 --- a/shared/src/i18n/it/oauth.ts +++ b/shared/src/i18n/it/oauth.ts @@ -107,5 +107,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Leggere unità, formato orario, lingua, valuta predefinita e pagina iniziale', 'oauth.scope.settings:write.label': 'Modificare le tue preferenze', 'oauth.scope.settings:write.description': 'Modificare unità, formato orario, lingua, valuta predefinita e pagina iniziale. Mai le chiavi API salvate', + 'oauth.scope.group.plugins': 'Plugin', + 'oauth.scope.plugins:use.label': 'Eseguire gli strumenti dei plugin', + 'oauth.scope.plugins:use.description': 'Consente a questo client di chiamare gli strumenti pubblicati dai plugin installati e approvati da un amministratore. Ogni plugin agisce con i permessi che gli sono già stati concessi, non con gli ambiti di questo token', }; export default oauth; diff --git a/shared/src/i18n/ja/oauth.ts b/shared/src/i18n/ja/oauth.ts index 6fe50bedf..373e059ab 100644 --- a/shared/src/i18n/ja/oauth.ts +++ b/shared/src/i18n/ja/oauth.ts @@ -106,5 +106,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': '単位、時刻表示、言語、既定通貨、開始ページの読み取り', 'oauth.scope.settings:write.label': '環境設定を変更', 'oauth.scope.settings:write.description': '単位、時刻表示、言語、既定通貨、開始ページの変更。保存されたAPIキーは対象外', + 'oauth.scope.group.plugins': 'プラグイン', + 'oauth.scope.plugins:use.label': 'プラグインのツールを実行', + 'oauth.scope.plugins:use.description': '管理者がインストールして承認したプラグインが公開するツールを、このクライアントから呼び出せるようにします。各プラグインは、このトークンのスコープではなく、すでに付与されている権限で動作します', }; export default oauth; diff --git a/shared/src/i18n/ko/oauth.ts b/shared/src/i18n/ko/oauth.ts index 5fcf3be70..95b00a466 100644 --- a/shared/src/i18n/ko/oauth.ts +++ b/shared/src/i18n/ko/oauth.ts @@ -104,5 +104,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': '단위, 시간 형식, 언어, 기본 통화, 시작 페이지 읽기', 'oauth.scope.settings:write.label': '환경설정 변경', 'oauth.scope.settings:write.description': '단위, 시간 형식, 언어, 기본 통화, 시작 페이지 변경. 저장된 API 키는 제외', + 'oauth.scope.group.plugins': '플러그인', + 'oauth.scope.plugins:use.label': '플러그인 도구 실행', + 'oauth.scope.plugins:use.description': '관리자가 설치하고 승인한 플러그인이 제공하는 도구를 이 클라이언트가 호출할 수 있도록 허용합니다. 각 플러그인은 이 토큰의 범위가 아니라 이미 부여받은 권한으로 동작합니다', }; export default oauth; diff --git a/shared/src/i18n/nl/oauth.ts b/shared/src/i18n/nl/oauth.ts index 940379624..0b3c4d5b4 100644 --- a/shared/src/i18n/nl/oauth.ts +++ b/shared/src/i18n/nl/oauth.ts @@ -108,5 +108,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Eenheden, tijdnotatie, taal, standaardvaluta en startpagina lezen', 'oauth.scope.settings:write.label': 'Je voorkeuren wijzigen', 'oauth.scope.settings:write.description': 'Eenheden, tijdnotatie, taal, standaardvaluta en startpagina wijzigen. Nooit opgeslagen API-sleutels', + 'oauth.scope.group.plugins': 'Plug-ins', + 'oauth.scope.plugins:use.label': 'Plug-intools uitvoeren', + 'oauth.scope.plugins:use.description': 'Laat deze client tools aanroepen die worden aangeboden door de plug-ins die een beheerder heeft geïnstalleerd en goedgekeurd. Elke plug-in handelt met de rechten die deze al had, niet met de scopes van dit token', }; export default oauth; diff --git a/shared/src/i18n/pl/oauth.ts b/shared/src/i18n/pl/oauth.ts index 19e20eba3..1b5430b7a 100644 --- a/shared/src/i18n/pl/oauth.ts +++ b/shared/src/i18n/pl/oauth.ts @@ -107,5 +107,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Odczyt jednostek, formatu czasu, języka, waluty domyślnej i strony startowej', 'oauth.scope.settings:write.label': 'Zmiana preferencji', 'oauth.scope.settings:write.description': 'Zmiana jednostek, formatu czasu, języka, waluty domyślnej i strony startowej. Nigdy zapisanych kluczy API', + 'oauth.scope.group.plugins': 'Wtyczki', + 'oauth.scope.plugins:use.label': 'Uruchamianie narzędzi wtyczek', + 'oauth.scope.plugins:use.description': 'Pozwala temu klientowi wywoływać narzędzia udostępniane przez wtyczki zainstalowane i zatwierdzone przez administratora. Każda wtyczka działa z uprawnieniami, które już otrzymała, a nie z zakresami tego tokenu', }; export default oauth; diff --git a/shared/src/i18n/ru/oauth.ts b/shared/src/i18n/ru/oauth.ts index ece34f24b..8826b1af9 100644 --- a/shared/src/i18n/ru/oauth.ts +++ b/shared/src/i18n/ru/oauth.ts @@ -107,5 +107,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Чтение единиц измерения, формата времени, языка, валюты по умолчанию и стартовой страницы', 'oauth.scope.settings:write.label': 'Изменение ваших настроек', 'oauth.scope.settings:write.description': 'Изменение единиц измерения, формата времени, языка, валюты по умолчанию и стартовой страницы. Никогда сохранённых ключей API', + 'oauth.scope.group.plugins': 'Плагины', + 'oauth.scope.plugins:use.label': 'Запуск инструментов плагинов', + 'oauth.scope.plugins:use.description': 'Позволяет этому клиенту вызывать инструменты, которые предоставляют плагины, установленные и одобренные администратором. Каждый плагин действует с уже выданными ему правами, а не с областями доступа этого токена', }; export default oauth; diff --git a/shared/src/i18n/sv/oauth.ts b/shared/src/i18n/sv/oauth.ts index b9dcf97ed..7166ee9e9 100644 --- a/shared/src/i18n/sv/oauth.ts +++ b/shared/src/i18n/sv/oauth.ts @@ -112,5 +112,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Läsa enheter, tidsformat, språk, standardvaluta och startsida', 'oauth.scope.settings:write.label': 'Ändra dina inställningar', 'oauth.scope.settings:write.description': 'Ändra enheter, tidsformat, språk, standardvaluta och startsida. Aldrig sparade API-nycklar', + 'oauth.scope.group.plugins': 'Tillägg', + 'oauth.scope.plugins:use.label': 'Kör tilläggsverktyg', + 'oauth.scope.plugins:use.description': 'Låter den här klienten anropa verktyg som publiceras av de tillägg en administratör har installerat och godkänt. Varje tillägg agerar med de rättigheter det redan har fått, inte med det här tokenets omfattningar', }; export default oauth; diff --git a/shared/src/i18n/tr/oauth.ts b/shared/src/i18n/tr/oauth.ts index 231f7fb1e..db0551d55 100644 --- a/shared/src/i18n/tr/oauth.ts +++ b/shared/src/i18n/tr/oauth.ts @@ -110,5 +110,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Birimleri, saat biçimini, dili, varsayılan para birimini ve başlangıç sayfasını oku', 'oauth.scope.settings:write.label': 'Tercihlerini değiştir', 'oauth.scope.settings:write.description': 'Birimleri, saat biçimini, dili, varsayılan para birimini ve başlangıç sayfasını değiştir. Kayıtlı API anahtarlarını asla', + 'oauth.scope.group.plugins': 'Eklentiler', + 'oauth.scope.plugins:use.label': 'Eklenti araçlarını çalıştır', + 'oauth.scope.plugins:use.description': 'Bu istemcinin, bir yöneticinin kurup onayladığı eklentilerin sunduğu araçları çağırmasına izin verir. Her eklenti, bu belirtecin kapsamlarıyla değil, kendisine önceden verilmiş yetkilerle çalışır', }; export default oauth; diff --git a/shared/src/i18n/uk/oauth.ts b/shared/src/i18n/uk/oauth.ts index d9e3f4786..79cfcdaba 100644 --- a/shared/src/i18n/uk/oauth.ts +++ b/shared/src/i18n/uk/oauth.ts @@ -106,5 +106,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Читання одиниць вимірювання, формату часу, мови, валюти за умовчанням і стартової сторінки', 'oauth.scope.settings:write.label': 'Зміна ваших налаштувань', 'oauth.scope.settings:write.description': 'Зміна одиниць вимірювання, формату часу, мови, валюти за умовчанням і стартової сторінки. Ніколи збережених ключів API', + 'oauth.scope.group.plugins': 'Плагіни', + 'oauth.scope.plugins:use.label': 'Запуск інструментів плагінів', + 'oauth.scope.plugins:use.description': 'Дозволяє цьому клієнту викликати інструменти, які надають плагіни, встановлені та схвалені адміністратором. Кожен плагін діє з уже наданими йому правами, а не з областями доступу цього токена', }; export default oauth; diff --git a/shared/src/i18n/vi/oauth.ts b/shared/src/i18n/vi/oauth.ts index 84723220b..2e775a8f6 100644 --- a/shared/src/i18n/vi/oauth.ts +++ b/shared/src/i18n/vi/oauth.ts @@ -109,5 +109,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': 'Đọc đơn vị, định dạng giờ, ngôn ngữ, tiền tệ mặc định và trang khởi đầu', 'oauth.scope.settings:write.label': 'Thay đổi tuỳ chọn của bạn', 'oauth.scope.settings:write.description': 'Thay đổi đơn vị, định dạng giờ, ngôn ngữ, tiền tệ mặc định và trang khởi đầu. Không bao giờ khoá API đã lưu', + 'oauth.scope.group.plugins': 'Tiện ích', + 'oauth.scope.plugins:use.label': 'Chạy công cụ của tiện ích', + 'oauth.scope.plugins:use.description': 'Cho phép ứng dụng này gọi các công cụ do những tiện ích mà quản trị viên đã cài đặt và phê duyệt cung cấp. Mỗi tiện ích hoạt động với quyền đã được cấp trước đó, không phải với phạm vi của mã thông báo này', }; export default oauth; diff --git a/shared/src/i18n/zh-TW/oauth.ts b/shared/src/i18n/zh-TW/oauth.ts index 9aa758ce2..588bb4641 100644 --- a/shared/src/i18n/zh-TW/oauth.ts +++ b/shared/src/i18n/zh-TW/oauth.ts @@ -104,5 +104,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': '讀取單位、時間格式、語言、預設貨幣和起始頁', 'oauth.scope.settings:write.label': '更改你的偏好設定', 'oauth.scope.settings:write.description': '更改單位、時間格式、語言、預設貨幣和起始頁。絕不涉及已儲存的 API 金鑰', + 'oauth.scope.group.plugins': '外掛', + 'oauth.scope.plugins:use.label': '執行外掛工具', + 'oauth.scope.plugins:use.description': '允許此用戶端呼叫由管理員安裝並核准的外掛所發布的工具。每個外掛都以其已獲授予的權限運作,而非以此權杖的權限範圍運作', }; export default oauth; diff --git a/shared/src/i18n/zh/oauth.ts b/shared/src/i18n/zh/oauth.ts index 1410c6933..5d94de9db 100644 --- a/shared/src/i18n/zh/oauth.ts +++ b/shared/src/i18n/zh/oauth.ts @@ -104,5 +104,8 @@ const oauth: TranslationStrings = { 'oauth.scope.settings:read.description': '读取单位、时间格式、语言、默认货币和起始页', 'oauth.scope.settings:write.label': '更改你的偏好设置', 'oauth.scope.settings:write.description': '更改单位、时间格式、语言、默认货币和起始页。绝不涉及已保存的 API 密钥', + 'oauth.scope.group.plugins': '插件', + 'oauth.scope.plugins:use.label': '运行插件工具', + 'oauth.scope.plugins:use.description': '允许此客户端调用由管理员安装并批准的插件所发布的工具。每个插件都以其已获授予的权限运行,而不是以此令牌的权限范围运行', }; export default oauth;