mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
feat(oauth): add client_credentials grant for machine clients and fix PlaceAvatar stale image retry
- Add OAuth 2.0 client_credentials flow so AI agents and scripts can obtain tokens directly via client_id + client_secret without any browser interaction - New DB column allows_client_credentials on oauth_clients; machine clients skip redirect URI requirement and are forced confidential - New issueClientCredentialsToken() issues access-only tokens (no refresh token, RFC 6749 §4.4) - UI: "Machine client" checkbox in create-client modal, hides redirect URI field, shows indigo badge on existing machine clients - Advertise client_credentials in OAuth discovery document - 8 new integration tests (OAUTH-CC-001–008) - i18n: 4 new keys across all 15 languages - Fix PlaceAvatar: re-fetch photo via API on image_url load failure before falling back to initials - Update MCP wiki docs with new Option B machine client setup guide
This commit is contained in:
@@ -330,6 +330,10 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'تم إلغاء الجلسة',
|
||||
'settings.oauth.toast.revokeError': 'فشل إلغاء الجلسة',
|
||||
'settings.oauth.toast.rotateError': 'فشل تجديد سر العميل',
|
||||
'settings.oauth.modal.machineClient': 'عميل آلي (بدون تسجيل دخول عبر المتصفح)',
|
||||
'settings.oauth.modal.machineClientHint': 'استخدام منحة client_credentials — لا تحتاج إلى عناوين إعادة التوجيه. يُصدر الرمز المميز مباشرةً عبر client_id + client_secret ويعمل بصلاحياتك ضمن النطاقات المحددة.',
|
||||
'settings.oauth.modal.machineClientUsage': 'للحصول على رمز مميز: POST /oauth/token مع grant_type=client_credentials وclient_id وclient_secret. بدون متصفح، بدون رمز تحديث.',
|
||||
'settings.oauth.badge.machine': 'آلي',
|
||||
'settings.account': 'الحساب',
|
||||
'settings.about': 'حول',
|
||||
'settings.about.reportBug': 'الإبلاغ عن خطأ',
|
||||
|
||||
@@ -402,6 +402,10 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Sessão revogada',
|
||||
'settings.oauth.toast.revokeError': 'Falha ao revogar sessão',
|
||||
'settings.oauth.toast.rotateError': 'Falha ao renovar segredo do cliente',
|
||||
'settings.oauth.modal.machineClient': 'Cliente de máquina (sem login no navegador)',
|
||||
'settings.oauth.modal.machineClientHint': 'Usa o grant client_credentials — sem URIs de redirecionamento. O token é emitido diretamente via client_id + client_secret e age como você dentro dos escopos selecionados.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Obter token: POST /oauth/token com grant_type=client_credentials, client_id e client_secret. Sem navegador, sem refresh token.',
|
||||
'settings.oauth.badge.machine': 'máquina',
|
||||
'settings.mustChangePassword': 'Você deve alterar sua senha antes de continuar. Defina uma nova senha abaixo.',
|
||||
|
||||
// Login
|
||||
|
||||
@@ -281,6 +281,10 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Relace odvolána',
|
||||
'settings.oauth.toast.revokeError': 'Odvolání relace se nezdařilo',
|
||||
'settings.oauth.toast.rotateError': 'Obnovení tajného klíče klienta se nezdařilo',
|
||||
'settings.oauth.modal.machineClient': 'Strojový klient (bez přihlášení v prohlížeči)',
|
||||
'settings.oauth.modal.machineClientHint': 'Používá grant client_credentials — bez URI pro přesměrování. Token je vydán přímo přes client_id + client_secret a funguje jako vy v rámci vybraných oborů.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Získat token: POST /oauth/token s grant_type=client_credentials, client_id a client_secret. Bez prohlížeče, bez obnovovacího tokenu.',
|
||||
'settings.oauth.badge.machine': 'strojový',
|
||||
'settings.account': 'Účet',
|
||||
'settings.about': 'O aplikaci',
|
||||
'settings.about.reportBug': 'Nahlásit chybu',
|
||||
|
||||
@@ -330,6 +330,10 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Session widerrufen',
|
||||
'settings.oauth.toast.revokeError': 'Session konnte nicht widerrufen werden',
|
||||
'settings.oauth.toast.rotateError': 'Client-Secret konnte nicht erneuert werden',
|
||||
'settings.oauth.modal.machineClient': 'Maschineller Client (kein Browser-Login)',
|
||||
'settings.oauth.modal.machineClientHint': 'Verwendet den client_credentials Grant — keine Redirect-URIs erforderlich. Das Token wird direkt über client_id + client_secret ausgestellt und handelt in Ihrem Namen innerhalb der gewählten Scopes.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Token abrufen: POST /oauth/token mit grant_type=client_credentials, client_id und client_secret. Kein Browser, kein Refresh-Token.',
|
||||
'settings.oauth.badge.machine': 'Maschine',
|
||||
'settings.account': 'Konto',
|
||||
'settings.about': 'Über',
|
||||
'settings.about.reportBug': 'Bug melden',
|
||||
|
||||
@@ -403,6 +403,10 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Session revoked',
|
||||
'settings.oauth.toast.revokeError': 'Failed to revoke session',
|
||||
'settings.oauth.toast.rotateError': 'Failed to rotate client secret',
|
||||
'settings.oauth.modal.machineClient': 'Machine client (no browser login)',
|
||||
'settings.oauth.modal.machineClientHint': 'Use client_credentials grant — no redirect URIs needed. The token is issued directly via client_id + client_secret and acts as you within the selected scopes.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Get a token: POST /oauth/token with grant_type=client_credentials, client_id, and client_secret. No browser, no refresh token.',
|
||||
'settings.oauth.badge.machine': 'machine',
|
||||
'settings.account': 'Account',
|
||||
'settings.about': 'About',
|
||||
'settings.about.reportBug': 'Report a Bug',
|
||||
|
||||
@@ -326,6 +326,10 @@ const es: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': 'Sesión revocada',
|
||||
'settings.oauth.toast.revokeError': 'Error al revocar la sesión',
|
||||
'settings.oauth.toast.rotateError': 'Error al renovar el secreto del cliente',
|
||||
'settings.oauth.modal.machineClient': 'Cliente de máquina (sin inicio de sesión en el navegador)',
|
||||
'settings.oauth.modal.machineClientHint': 'Usa el grant client_credentials — sin URIs de redirección. El token se emite directamente vía client_id + client_secret y actúa como tú dentro de los alcances seleccionados.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Obtener token: POST /oauth/token con grant_type=client_credentials, client_id y client_secret. Sin navegador, sin token de actualización.',
|
||||
'settings.oauth.badge.machine': 'máquina',
|
||||
'settings.account': 'Cuenta',
|
||||
'settings.about': 'Acerca de',
|
||||
'settings.about.reportBug': 'Reportar un error',
|
||||
|
||||
@@ -325,6 +325,10 @@ const fr: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': 'Session révoquée',
|
||||
'settings.oauth.toast.revokeError': 'Impossible de révoquer la session',
|
||||
'settings.oauth.toast.rotateError': 'Impossible de renouveler le secret client',
|
||||
'settings.oauth.modal.machineClient': 'Client machine (sans connexion navigateur)',
|
||||
'settings.oauth.modal.machineClientHint': 'Utilise le grant client_credentials — aucune URI de redirection requise. Le token est émis directement via client_id + client_secret et agit en votre nom dans les portées sélectionnées.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Obtenir un token : POST /oauth/token avec grant_type=client_credentials, client_id et client_secret. Sans navigateur, sans token de rafraîchissement.',
|
||||
'settings.oauth.badge.machine': 'machine',
|
||||
'settings.account': 'Compte',
|
||||
'settings.about': 'À propos',
|
||||
'settings.about.reportBug': 'Signaler un bug',
|
||||
|
||||
@@ -280,6 +280,10 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Munkamenet visszavonva',
|
||||
'settings.oauth.toast.revokeError': 'A munkamenet visszavonása sikertelen',
|
||||
'settings.oauth.toast.rotateError': 'A kliens titok megújítása sikertelen',
|
||||
'settings.oauth.modal.machineClient': 'Gépi kliens (böngészős bejelentkezés nélkül)',
|
||||
'settings.oauth.modal.machineClientHint': 'client_credentials grant használata — nincs szükség átirányítási URI-kra. A token közvetlenül client_id + client_secret segítségével kerül kiállításra, és a kiválasztott hatókörökön belül az Ön nevében jár el.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Token lekérése: POST /oauth/token a grant_type=client_credentials, client_id és client_secret értékekkel. Böngésző és frissítési token nélkül.',
|
||||
'settings.oauth.badge.machine': 'gépi',
|
||||
'settings.account': 'Fiók',
|
||||
'settings.about': 'Névjegy',
|
||||
'settings.about.reportBug': 'Hiba bejelentése',
|
||||
|
||||
@@ -387,6 +387,10 @@ const id: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Sesi dicabut',
|
||||
'settings.oauth.toast.revokeError': 'Gagal mencabut sesi',
|
||||
'settings.oauth.toast.rotateError': 'Gagal memutar ulang client secret',
|
||||
'settings.oauth.modal.machineClient': 'Klien mesin (tanpa login browser)',
|
||||
'settings.oauth.modal.machineClientHint': 'Menggunakan grant client_credentials — tidak perlu URI pengalihan. Token diterbitkan langsung melalui client_id + client_secret dan bertindak sebagai Anda dalam cakupan yang dipilih.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Dapatkan token: POST /oauth/token dengan grant_type=client_credentials, client_id, dan client_secret. Tanpa browser, tanpa refresh token.',
|
||||
'settings.oauth.badge.machine': 'mesin',
|
||||
'settings.account': 'Akun',
|
||||
'settings.about': 'Tentang',
|
||||
'settings.about.reportBug': 'Laporkan Bug',
|
||||
|
||||
@@ -280,6 +280,10 @@ const it: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Sessione revocata',
|
||||
'settings.oauth.toast.revokeError': 'Impossibile revocare la sessione',
|
||||
'settings.oauth.toast.rotateError': 'Impossibile rinnovare il segreto client',
|
||||
'settings.oauth.modal.machineClient': 'Client macchina (senza login nel browser)',
|
||||
'settings.oauth.modal.machineClientHint': 'Usa il grant client_credentials — nessun URI di reindirizzamento necessario. Il token viene emesso direttamente tramite client_id + client_secret e agisce come te negli ambiti selezionati.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Ottieni token: POST /oauth/token con grant_type=client_credentials, client_id e client_secret. Senza browser, senza token di aggiornamento.',
|
||||
'settings.oauth.badge.machine': 'macchina',
|
||||
'settings.account': 'Account',
|
||||
'settings.about': 'Informazioni',
|
||||
'settings.about.reportBug': 'Segnala un bug',
|
||||
|
||||
@@ -325,6 +325,10 @@ const nl: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': 'Sessie ingetrokken',
|
||||
'settings.oauth.toast.revokeError': 'Sessie kon niet worden ingetrokken',
|
||||
'settings.oauth.toast.rotateError': 'Clientgeheim kon niet worden vernieuwd',
|
||||
'settings.oauth.modal.machineClient': 'Machineclient (zonder browserinlog)',
|
||||
'settings.oauth.modal.machineClientHint': "Gebruikt de client_credentials grant — geen redirect-URI's nodig. Het token wordt direct verstrekt via client_id + client_secret en handelt namens jou binnen de geselecteerde scopes.",
|
||||
'settings.oauth.modal.machineClientUsage': 'Token ophalen: POST /oauth/token met grant_type=client_credentials, client_id en client_secret. Geen browser, geen vernieuwingstoken.',
|
||||
'settings.oauth.badge.machine': 'machine',
|
||||
'settings.account': 'Account',
|
||||
'settings.about': 'Over',
|
||||
'settings.about.reportBug': 'Bug melden',
|
||||
|
||||
@@ -295,6 +295,10 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.toast.revoked': 'Sesja unieważniona',
|
||||
'settings.oauth.toast.revokeError': 'Nie udało się unieważnić sesji',
|
||||
'settings.oauth.toast.rotateError': 'Nie udało się odnowić sekretu klienta',
|
||||
'settings.oauth.modal.machineClient': 'Klient maszynowy (bez logowania przez przeglądarkę)',
|
||||
'settings.oauth.modal.machineClientHint': 'Używa grantu client_credentials — nie są potrzebne URI przekierowania. Token jest wystawiany bezpośrednio przez client_id + client_secret i działa w Twoim imieniu w ramach wybranych zakresów.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Pobierz token: POST /oauth/token z grant_type=client_credentials, client_id i client_secret. Bez przeglądarki, bez tokenu odświeżania.',
|
||||
'settings.oauth.badge.machine': 'maszynowy',
|
||||
'settings.account': 'Konto',
|
||||
'settings.about': 'O aplikacji',
|
||||
'settings.about.reportBug': 'Zgłoś błąd',
|
||||
|
||||
@@ -325,6 +325,10 @@ const ru: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': 'Сессия отозвана',
|
||||
'settings.oauth.toast.revokeError': 'Не удалось отозвать сессию',
|
||||
'settings.oauth.toast.rotateError': 'Не удалось обновить секрет клиента',
|
||||
'settings.oauth.modal.machineClient': 'Машинный клиент (без входа через браузер)',
|
||||
'settings.oauth.modal.machineClientHint': 'Использует грант client_credentials — URI перенаправления не требуются. Токен выдаётся напрямую через client_id + client_secret и действует от вашего имени в пределах выбранных областей.',
|
||||
'settings.oauth.modal.machineClientUsage': 'Получить токен: POST /oauth/token с grant_type=client_credentials, client_id и client_secret. Без браузера, без токена обновления.',
|
||||
'settings.oauth.badge.machine': 'машинный',
|
||||
'settings.account': 'Аккаунт',
|
||||
'settings.about': 'О приложении',
|
||||
'settings.about.reportBug': 'Сообщить об ошибке',
|
||||
|
||||
@@ -325,6 +325,10 @@ const zh: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': '会话已撤销',
|
||||
'settings.oauth.toast.revokeError': '撤销会话失败',
|
||||
'settings.oauth.toast.rotateError': '轮换客户端密钥失败',
|
||||
'settings.oauth.modal.machineClient': '机器客户端(无需浏览器登录)',
|
||||
'settings.oauth.modal.machineClientHint': '使用 client_credentials 授权——无需重定向 URI。令牌通过 client_id + client_secret 直接颁发,并在所选范围内以您的身份运行。',
|
||||
'settings.oauth.modal.machineClientUsage': '获取令牌:向 /oauth/token 发送 POST 请求,携带 grant_type=client_credentials、client_id 和 client_secret。无需浏览器,无刷新令牌。',
|
||||
'settings.oauth.badge.machine': '机器',
|
||||
'settings.account': '账户',
|
||||
'settings.about': '关于',
|
||||
'settings.about.reportBug': '报告错误',
|
||||
|
||||
@@ -384,6 +384,10 @@ const zhTw: Record<string, string> = {
|
||||
'settings.oauth.toast.revoked': '工作階段已撤銷',
|
||||
'settings.oauth.toast.revokeError': '撤銷工作階段失敗',
|
||||
'settings.oauth.toast.rotateError': '輪換客戶端密鑰失敗',
|
||||
'settings.oauth.modal.machineClient': '機器客戶端(無需瀏覽器登入)',
|
||||
'settings.oauth.modal.machineClientHint': '使用 client_credentials 授權——無需重新導向 URI。令牌透過 client_id + client_secret 直接簽發,並在所選範圍內以您的身份運行。',
|
||||
'settings.oauth.modal.machineClientUsage': '取得令牌:向 /oauth/token 發送 POST 請求,攜帶 grant_type=client_credentials、client_id 和 client_secret。無需瀏覽器,無重整令牌。',
|
||||
'settings.oauth.badge.machine': '機器',
|
||||
'settings.account': '賬戶',
|
||||
'settings.about': '關於',
|
||||
'settings.about.reportBug': '回報錯誤',
|
||||
|
||||
Reference in New Issue
Block a user