mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(mcp): plugins:use OAuth scope
One coarse scope rather than per-plugin granularity. 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.
'use' is a fourth mode, which widens ScopeMode. Nothing else moves:
McpAccessGroup and McpAccessMode derive from Scope, so both AssertExact
guards self-heal, and the policy's fallback branch already covers a third
mode the way it covers files:content.
The presets needed an explicit exclusion. Five of the six are written as
"everything except deletes", so plugins:use would have joined Claude.ai,
Cursor, Windsurf and Zed silently and handed out third-party tool
execution without anyone choosing it. That filter was duplicated across
the desktop and mobile files with nothing pinning it, so both now use one
shared constant, and FE-OAUTH-SCOPES-012 holds the line for the next
scope like this one.
The consent copy says what the scope does not do: the RPC host authorises
from grants frozen at construction, so this scope plus weather:read still
reaches whatever the plugin itself was granted.
Two i18n gates, not one. The dot-only group key is tracked by
i18n-parity.mjs, whose regex has no colon in its character class; the
colon-bearing label/description pair is invisible to it but is compared by
client/tests/unit/i18n/parity.test.ts, which diffs merged bundles. All 23
locales get all three keys, including the three in neither gate.
The consent glyph gets a ':use' branch. Falling through to the eye would
tell the user a tool-running scope only looks at things.
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -48,10 +48,27 @@ export const SCOPE_GROUPS: Record<string, ScopeKeys> = {
|
||||
'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))]
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<span className="mt-0.5 text-base leading-none flex-shrink-0">
|
||||
{s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'}
|
||||
{scopeGlyph(s)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>{keys ? t(keys.labelKey) : s}</p>
|
||||
@@ -186,7 +200,7 @@ export default function OAuthAuthorizePage(): React.ReactElement {
|
||||
return (
|
||||
<div key={s} className="flex items-start gap-2.5 px-3 py-2 rounded-lg" style={{ background: 'var(--bg-secondary)' }}>
|
||||
<span className="mt-0.5 text-base leading-none flex-shrink-0">
|
||||
{s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'}
|
||||
{scopeGlyph(s)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>{keys ? t(keys.labelKey) : s}</p>
|
||||
|
||||
@@ -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<Scope, ScopeInfo> = {
|
||||
'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' },
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user