diff --git a/apps/web/messages/ar-SA.json b/apps/web/messages/ar-SA.json index 8362e85..f4a2175 100644 --- a/apps/web/messages/ar-SA.json +++ b/apps/web/messages/ar-SA.json @@ -1415,5 +1415,117 @@ } }, "defaultLinkName": "الملفات المستلمة" + }, + "authProviders": { + "title": "مزودي المصادقة", + "description": "تكوين مزودي المصادقة الخارجيين لتسجيل الدخول الموحد", + "enabledCount": "{count} مفعل", + "loadingProviders": "جاري تحميل المزودين...", + "providersConfigured": "{count} مزود تم تكوينه", + "enabledOfTotal": "{enabled} مفعل من {total} مزود", + "hideDisabledProviders": "إخفاء المزودين غير المفعلين", + "addProvider": "إضافة مزود", + "addProviderTitle": "إضافة مزود", + "editProvider": "تحرير المزود", + "deleteProvider": "حذف المزود", + "enabled": "مفعل", + "disabled": "معطل", + "officialProvider": "مزود رسمي", + "dragToReorder": "اسحب لإعادة الترتيب", + "dragDisabledMessage": "السحب والإفلات معطل عند تصفية المزودين. اعرض جميع المزودين لإعادة ترتيبهم.", + "dragEnabledMessage": "اسحب المزودين لإعادة ترتيبهم. سينعكس هذا الترتيب على صفحة تسجيل الدخول.", + "noProvidersEnabled": "لا توجد مزودي مصادقة مفعلين", + "noProvidersConfigured": "لم يتم تكوين أي مزودي مصادقة", + "form": { + "providerName": "اسم المزود", + "providerNamePlaceholder": "مثال: شركتي", + "displayName": "الاسم المعروض", + "displayNamePlaceholder": "مثال: تسجيل دخول موحد لشركتي", + "type": "النوع", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "الأيقونة", + "iconPlaceholder": "اختر أيقونة", + "clientId": "معرف العميل", + "clientIdPlaceholder": "معرف عميل OAuth الخاص بك", + "clientSecret": "سر العميل", + "clientSecretPlaceholder": "سر عميل OAuth الخاص بك", + "oauthScopes": "نطاقات OAuth", + "scopesPlaceholder": "أدخل النطاقات (مثل: openid, profile, email)", + "scopesHelpOidc": "النطاقات مقترحة تلقائياً بناءً على رابط المزود. نطاقات OIDC الشائعة: openid, profile, email, groups", + "scopesHelpOauth2": "النطاقات مقترحة تلقائياً بناءً على رابط المزود. نطاقات OAuth2 الشائعة تعتمد على المزود", + "providerUrl": "رابط المزود", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (سيتم اكتشاف نقاط النهاية تلقائياً)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "سيكتشف النظام تلقائياً نقاط نهاية التفويض والرمز ومعلومات المستخدم", + "manualConfigurationHelp": "الرابط الأساسي للمزود الخاص بك (ستكون نقاط النهاية نسبية لهذا)", + "authorizationEndpoint": "نقطة نهاية التفويض", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "نقطة نهاية الرمز", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "نقطة نهاية معلومات المستخدم", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "طريقة التكوين", + "autoDiscovery": "اكتشاف تلقائي (موصى به)", + "autoDiscoveryDescription": "اكتشاف نقاط النهاية تلقائياً من رابط المزود", + "manualEndpoints": "نقاط نهاية يدوية", + "manualEndpointsDescription": "تكوين نقاط نهاية التفويض والرمز ومعلومات المستخدم يدوياً", + "callbackUrl": "رابط الاستجابة", + "callbackUrlDescription": "استخدم هذا الرابط في تكوين مزود OAuth الخاص بك", + "copyCallbackUrl": "نسخ رابط الاستجابة", + "callbackUrlCopied": "تم نسخ رابط الاستجابة إلى الحافظة!", + "adminEmailDomains": "نطاقات البريد الإلكتروني للمسؤولين", + "adminEmailDomainsPlaceholder": "أدخل النطاقات (مثل: admin.company.com)", + "adminEmailDomainsHelp": "سيتم منح المستخدمين الذين لديهم بريد إلكتروني من هذه النطاقات صلاحيات المسؤول", + "autoRegister": "تسجيل المستخدمين الجدد تلقائياً", + "officialProviderUrlPlaceholder": "استبدل العنصر النائب برابط {displayName} الخاص بك", + "officialProviderHelp": "هذا مزود رسمي. نقاط النهاية مكونة مسبقاً. يمكنك تحرير هذا الرابط فقط.", + "officialProviderIconHelp": "يمكنك تخصيص الأيقونة لهذا المزود الرسمي." + }, + "buttons": { + "cancel": "إلغاء", + "save": "حفظ", + "saving": "جاري الحفظ...", + "adding": "جاري الإضافة...", + "updating": "جاري التحديث...", + "saveProvider": "حفظ المزود", + "delete": "حذف", + "deleting": "جاري الحذف...", + "edit": "تحرير", + "enable": "تفعيل", + "disable": "تعطيل" + }, + "messages": { + "providerAdded": "تمت إضافة المزود بنجاح", + "providerUpdated": "تم تحديث المزود بنجاح", + "providerDeleted": "تم حذف المزود بنجاح", + "providerOrderUpdated": "تم تحديث ترتيب المزود بنجاح", + "fillRequiredFields": "يرجى ملء جميع الحقول المطلوبة (الاسم، الاسم المعروض، معرف العميل، سر العميل)", + "provideUrlOrEndpoints": "قم بتوفير رابط المزود للاكتشاف التلقائي أو جميع نقاط النهاية الثلاث المخصصة", + "chooseDiscoveryOrManual": "اختر إما الاكتشاف التلقائي (رابط المزود) أو نقاط النهاية اليدوية، وليس كليهما", + "loadFailed": "فشل تحميل المزودين", + "addFailed": "فشل إضافة المزود", + "updateFailed": "فشل تحديث المزود", + "deleteFailed": "فشل حذف المزود", + "orderUpdateFailed": "فشل تحديث ترتيب المزود" + }, + "info": { + "title": "معلومات", + "officialProvidersRecommended": "للحصول على وظائف أفضل، نوصي باستخدام المزودين الرسميين. إذا واجهت مشاكل مع مزود مخصص، فكر في فتح مشكلة على", + "github": "GitHub", + "officialProvider": "مزود رسمي", + "officialProviderDescription": "هذا المزود محسن من قبل Palmr. يمكن تعديل بيانات الاعتماد والتكوين فقط.", + "manualConfigTitle": "التكوين اليدوي", + "manualConfigDescription": "أنت تقوم بتوفير جميع نقاط النهاية يدوياً. تأكد من صحتها لمزودك." + }, + "deleteModal": { + "title": "حذف مزود المصادقة", + "confirmMessage": "هل أنت متأكد أنك تريد حذف مزود \"{displayName}\"؟ لا يمكن التراجع عن هذا الإجراء.", + "providerId": "معرف المزود: {name}", + "cancel": "إلغاء", + "delete": "حذف المزود", + "deleting": "جاري الحذف..." + } } } \ No newline at end of file diff --git a/apps/web/messages/de-DE.json b/apps/web/messages/de-DE.json index e76cfdc..bdcac02 100644 --- a/apps/web/messages/de-DE.json +++ b/apps/web/messages/de-DE.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Empfangene Dateien" + }, + "authProviders": { + "title": "Authentifizierungsanbieter", + "description": "Externe Authentifizierungsanbieter für SSO konfigurieren", + "enabledCount": "{count} aktiviert", + "loadingProviders": "Anbieter werden geladen...", + "providersConfigured": "{count} Anbieter konfiguriert", + "enabledOfTotal": "{enabled} von {total} Anbietern aktiviert", + "hideDisabledProviders": "Deaktivierte Anbieter ausblenden", + "addProvider": "Anbieter hinzufügen", + "addProviderTitle": "Anbieter hinzufügen", + "editProvider": "Anbieter bearbeiten", + "deleteProvider": "Anbieter löschen", + "enabled": "Aktiviert", + "disabled": "Deaktiviert", + "officialProvider": "Offizieller Anbieter", + "dragToReorder": "Zum Neuordnen ziehen", + "dragDisabledMessage": "Drag & Drop ist bei gefilterten Anbietern deaktiviert. Zeigen Sie alle Anbieter an, um sie neu zu ordnen.", + "dragEnabledMessage": "Ziehen Sie Anbieter, um sie neu zu ordnen. Diese Reihenfolge wird auf der Anmeldeseite angezeigt.", + "noProvidersEnabled": "Keine Authentifizierungsanbieter aktiviert", + "noProvidersConfigured": "Keine Authentifizierungsanbieter konfiguriert", + "form": { + "providerName": "Anbietername", + "providerNamePlaceholder": "z.B. meinefirma", + "displayName": "Anzeigename", + "displayNamePlaceholder": "z.B. Meine Firma SSO", + "type": "Typ", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Symbol", + "iconPlaceholder": "Symbol auswählen", + "clientId": "Client-ID", + "clientIdPlaceholder": "Ihre OAuth-Client-ID", + "clientSecret": "Client-Secret", + "clientSecretPlaceholder": "Ihr OAuth-Client-Secret", + "oauthScopes": "OAuth-Scopes", + "scopesPlaceholder": "Scopes eingeben (z.B. openid, profile, email)", + "scopesHelpOidc": "Scopes werden basierend auf der Anbieter-URL automatisch vorgeschlagen. Übliche OIDC-Scopes: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes werden basierend auf der Anbieter-URL automatisch vorgeschlagen. Übliche OAuth2-Scopes hängen vom Anbieter ab", + "providerUrl": "Anbieter-URL", + "providerUrlPlaceholder": "https://auth.beispiel.de", + "providerUrlAutoPlaceholder": "https://ihr-anbieter.de (Endpunkte werden automatisch erkannt)", + "providerUrlManualPlaceholder": "https://ihr-anbieter.de", + "autoDiscoveryHelp": "Das System erkennt automatisch die Autorisierungs-, Token- und Userinfo-Endpunkte", + "manualConfigurationHelp": "Basis-URL Ihres Anbieters (Endpunkte werden relativ dazu angegeben)", + "authorizationEndpoint": "Autorisierungs-Endpunkt", + "authorizationEndpointPlaceholder": "https://auth.beispiel.de/auth", + "tokenEndpoint": "Token-Endpunkt", + "tokenEndpointPlaceholder": "https://auth.beispiel.de/token", + "userInfoEndpoint": "Benutzerinfo-Endpunkt", + "userInfoEndpointPlaceholder": "https://auth.beispiel.de/userinfo", + "configurationMethod": "Konfigurationsmethode", + "autoDiscovery": "Automatische Erkennung (Empfohlen)", + "autoDiscoveryDescription": "Endpunkte automatisch von der Anbieter-URL erkennen", + "manualEndpoints": "Manuelle Endpunkte", + "manualEndpointsDescription": "Autorisierungs-, Token- und Benutzerinfo-Endpunkte manuell konfigurieren", + "callbackUrl": "Callback-URL", + "callbackUrlDescription": "Verwenden Sie diese URL in Ihrer OAuth-Anbieterkonfiguration", + "copyCallbackUrl": "Callback-URL kopieren", + "callbackUrlCopied": "Callback-URL in die Zwischenablage kopiert!", + "adminEmailDomains": "Admin-E-Mail-Domains", + "adminEmailDomainsPlaceholder": "Domains eingeben (z.B. admin.firma.de)", + "adminEmailDomainsHelp": "Benutzer mit E-Mails von diesen Domains erhalten Administratorrechte", + "autoRegister": "Neue Benutzer automatisch registrieren", + "officialProviderUrlPlaceholder": "Ersetzen Sie den Platzhalter mit Ihrer {displayName}-URL", + "officialProviderHelp": "Dies ist ein offizieller Anbieter. Die Endpunkte sind vorkonfiguriert. Sie können nur diese URL bearbeiten.", + "officialProviderIconHelp": "Sie können das Symbol für diesen offiziellen Anbieter anpassen." + }, + "buttons": { + "cancel": "Abbrechen", + "save": "Speichern", + "saving": "Wird gespeichert...", + "adding": "Wird hinzugefügt...", + "updating": "Wird aktualisiert...", + "saveProvider": "Anbieter speichern", + "delete": "Löschen", + "deleting": "Wird gelöscht...", + "edit": "Bearbeiten", + "enable": "Aktivieren", + "disable": "Deaktivieren" + }, + "messages": { + "providerAdded": "Anbieter erfolgreich hinzugefügt", + "providerUpdated": "Anbieter erfolgreich aktualisiert", + "providerDeleted": "Anbieter erfolgreich gelöscht", + "providerOrderUpdated": "Anbieterreihenfolge erfolgreich aktualisiert", + "fillRequiredFields": "Bitte füllen Sie alle erforderlichen Felder aus (Name, Anzeigename, Client-ID, Client-Secret)", + "provideUrlOrEndpoints": "Geben Sie entweder eine Anbieter-URL für die automatische Erkennung ODER alle drei benutzerdefinierten Endpunkte an", + "chooseDiscoveryOrManual": "Wählen Sie entweder die automatische Erkennung (Anbieter-URL) ODER manuelle Endpunkte, nicht beides", + "loadFailed": "Fehler beim Laden der Anbieter", + "addFailed": "Fehler beim Hinzufügen des Anbieters", + "updateFailed": "Fehler beim Aktualisieren des Anbieters", + "deleteFailed": "Fehler beim Löschen des Anbieters", + "orderUpdateFailed": "Fehler beim Aktualisieren der Anbieterreihenfolge" + }, + "info": { + "title": "Information", + "officialProvidersRecommended": "Für bessere Funktionalität empfehlen wir die Verwendung offizieller Anbieter. Bei Problemen mit einem benutzerdefinierten Anbieter können Sie ein Issue auf", + "github": "GitHub", + "officialProvider": "Offizieller Anbieter", + "officialProviderDescription": "Dieser Anbieter ist von Palmr optimiert. Nur Zugangsdaten und Konfiguration können geändert werden.", + "manualConfigTitle": "Manuelle Konfiguration", + "manualConfigDescription": "Sie geben alle Endpunkte manuell an. Stellen Sie sicher, dass sie für Ihren Anbieter korrekt sind." + }, + "deleteModal": { + "title": "Authentifizierungsanbieter löschen", + "confirmMessage": "Sind Sie sicher, dass Sie den Anbieter \"{displayName}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "providerId": "Anbieter-ID: {name}", + "cancel": "Abbrechen", + "delete": "Anbieter löschen", + "deleting": "Wird gelöscht..." + } } } \ No newline at end of file diff --git a/apps/web/messages/en-US.json b/apps/web/messages/en-US.json index 944df8e..2d95499 100644 --- a/apps/web/messages/en-US.json +++ b/apps/web/messages/en-US.json @@ -1413,5 +1413,117 @@ "cancelEdit": "Cancel edit" } } + }, + "authProviders": { + "title": "Authentication Providers", + "description": "Configure external authentication providers for SSO", + "enabledCount": "{count} enabled", + "loadingProviders": "Loading providers...", + "providersConfigured": "{count} providers configured", + "enabledOfTotal": "{enabled} enabled of {total} providers", + "hideDisabledProviders": "Hide disabled providers", + "addProvider": "Add Provider", + "addProviderTitle": "Add Provider", + "editProvider": "Edit Provider", + "deleteProvider": "Delete Provider", + "enabled": "Enabled", + "disabled": "Disabled", + "officialProvider": "Official Provider", + "dragToReorder": "Drag to reorder", + "dragDisabledMessage": "Drag and drop is disabled when filtering providers. Show all providers to reorder them.", + "dragEnabledMessage": "Drag providers to reorder them. This order will be reflected on the login page.", + "noProvidersEnabled": "No enabled authentication providers", + "noProvidersConfigured": "No authentication providers configured", + "form": { + "providerName": "Provider Name", + "providerNamePlaceholder": "e.g., mycompany", + "displayName": "Display Name", + "displayNamePlaceholder": "e.g., My Company SSO", + "type": "Type", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Icon", + "iconPlaceholder": "Select an icon", + "clientId": "Client ID", + "clientIdPlaceholder": "Your OAuth client ID", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Your OAuth client secret", + "oauthScopes": "OAuth Scopes", + "scopesPlaceholder": "Enter scopes (e.g., openid, profile, email)", + "scopesHelpOidc": "Scopes auto-suggested based on Provider URL. Common OIDC scopes: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes auto-suggested based on Provider URL. Common OAuth2 scopes depend on the provider", + "providerUrl": "Provider URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (endpoints will be discovered automatically)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "The system will automatically discover authorization, token, and userinfo endpoints", + "manualConfigurationHelp": "Base URL of your provider (endpoints will be relative to this)", + "authorizationEndpoint": "Authorization Endpoint", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Token Endpoint", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "User Info Endpoint", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Configuration Method", + "autoDiscovery": "Auto-discovery (Recommended)", + "autoDiscoveryDescription": "Automatically discover endpoints from Provider URL", + "manualEndpoints": "Manual Endpoints", + "manualEndpointsDescription": "Manually configure authorization, token, and user info endpoints", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "Use this URL in your OAuth provider configuration", + "copyCallbackUrl": "Copy Callback URL", + "callbackUrlCopied": "Callback URL copied to clipboard!", + "adminEmailDomains": "Admin Email Domains", + "adminEmailDomainsPlaceholder": "Enter domains (e.g., admin.company.com)", + "adminEmailDomainsHelp": "Users with emails from these domains will be granted admin privileges", + "autoRegister": "Auto-register new users", + "officialProviderUrlPlaceholder": "Replace placeholder with your {displayName} URL", + "officialProviderHelp": "This is an official provider. Endpoints are pre-configured. You can edit just this URL.", + "officialProviderIconHelp": "You can customize the icon for this official provider." + }, + "buttons": { + "cancel": "Cancel", + "save": "Save", + "saving": "Saving...", + "adding": "Adding...", + "updating": "Updating...", + "saveProvider": "Save Provider", + "delete": "Delete", + "deleting": "Deleting...", + "edit": "Edit", + "enable": "Enable", + "disable": "Disable" + }, + "messages": { + "providerAdded": "Provider added successfully", + "providerUpdated": "Provider updated successfully", + "providerDeleted": "Provider deleted successfully", + "providerOrderUpdated": "Provider order updated successfully", + "fillRequiredFields": "Please fill in all required fields (name, display name, client ID, client secret)", + "provideUrlOrEndpoints": "Either provide a Provider URL for automatic discovery OR all three custom endpoints", + "chooseDiscoveryOrManual": "Choose either automatic discovery (Provider URL) OR manual endpoints, not both", + "loadFailed": "Failed to load providers", + "addFailed": "Failed to add provider", + "updateFailed": "Failed to update provider", + "deleteFailed": "Failed to delete provider", + "orderUpdateFailed": "Failed to update provider order" + }, + "info": { + "title": "Information", + "officialProvidersRecommended": "For better functionality, consider using official providers. If you have issues with a custom provider, consider opening an issue on", + "github": "GitHub", + "officialProvider": "Official Provider", + "officialProviderDescription": "This provider is optimized by Palmr. Only credentials and configuration can be modified.", + "manualConfigTitle": "Manual Configuration", + "manualConfigDescription": "You're providing all endpoints manually. Make sure they're correct for your provider." + }, + "deleteModal": { + "title": "Delete Authentication Provider", + "confirmMessage": "Are you sure you want to delete the \"{displayName}\" provider? This action cannot be undone.", + "providerId": "Provider ID: {name}", + "cancel": "Cancel", + "delete": "Delete Provider", + "deleting": "Deleting..." + } } } \ No newline at end of file diff --git a/apps/web/messages/es-ES.json b/apps/web/messages/es-ES.json index ddb58f8..50820c5 100644 --- a/apps/web/messages/es-ES.json +++ b/apps/web/messages/es-ES.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Archivos recibidos" + }, + "authProviders": { + "title": "Proveedores de Autenticación", + "description": "Configurar proveedores de autenticación externos para SSO", + "enabledCount": "{count} habilitados", + "loadingProviders": "Cargando proveedores...", + "providersConfigured": "{count} proveedores configurados", + "enabledOfTotal": "{enabled} habilitados de {total} proveedores", + "hideDisabledProviders": "Ocultar proveedores deshabilitados", + "addProvider": "Agregar Proveedor", + "addProviderTitle": "Agregar Proveedor", + "editProvider": "Editar Proveedor", + "deleteProvider": "Eliminar Proveedor", + "enabled": "Habilitado", + "disabled": "Deshabilitado", + "officialProvider": "Proveedor Oficial", + "dragToReorder": "Arrastrar para reordenar", + "dragDisabledMessage": "El arrastrar y soltar está deshabilitado al filtrar proveedores. Muestra todos los proveedores para reordenarlos.", + "dragEnabledMessage": "Arrastra los proveedores para reordenarlos. Este orden se reflejará en la página de inicio de sesión.", + "noProvidersEnabled": "No hay proveedores de autenticación habilitados", + "noProvidersConfigured": "No hay proveedores de autenticación configurados", + "form": { + "providerName": "Nombre del Proveedor", + "providerNamePlaceholder": "ej., miempresa", + "displayName": "Nombre para Mostrar", + "displayNamePlaceholder": "ej., SSO de Mi Empresa", + "type": "Tipo", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Ícono", + "iconPlaceholder": "Seleccionar un ícono", + "clientId": "ID del Cliente", + "clientIdPlaceholder": "Tu ID de cliente OAuth", + "clientSecret": "Secreto del Cliente", + "clientSecretPlaceholder": "Tu secreto de cliente OAuth", + "oauthScopes": "Alcances OAuth", + "scopesPlaceholder": "Ingresa alcances (ej., openid, profile, email)", + "scopesHelpOidc": "Alcances sugeridos automáticamente según la URL del Proveedor. Alcances comunes de OIDC: openid, profile, email, groups", + "scopesHelpOauth2": "Alcances sugeridos automáticamente según la URL del Proveedor. Los alcances comunes de OAuth2 dependen del proveedor", + "providerUrl": "URL del Proveedor", + "providerUrlPlaceholder": "https://auth.ejemplo.com", + "providerUrlAutoPlaceholder": "https://tu-proveedor.com (los endpoints se descubrirán automáticamente)", + "providerUrlManualPlaceholder": "https://tu-proveedor.com", + "autoDiscoveryHelp": "El sistema descubrirá automáticamente los endpoints de autorización, token y userinfo", + "manualConfigurationHelp": "URL base de tu proveedor (los endpoints serán relativos a esta)", + "authorizationEndpoint": "Endpoint de Autorización", + "authorizationEndpointPlaceholder": "https://auth.ejemplo.com/auth", + "tokenEndpoint": "Endpoint de Token", + "tokenEndpointPlaceholder": "https://auth.ejemplo.com/token", + "userInfoEndpoint": "Endpoint de Información del Usuario", + "userInfoEndpointPlaceholder": "https://auth.ejemplo.com/userinfo", + "configurationMethod": "Método de Configuración", + "autoDiscovery": "Descubrimiento Automático (Recomendado)", + "autoDiscoveryDescription": "Descubrir endpoints automáticamente desde la URL del Proveedor", + "manualEndpoints": "Endpoints Manuales", + "manualEndpointsDescription": "Configurar manualmente los endpoints de autorización, token e información del usuario", + "callbackUrl": "URL de Retorno", + "callbackUrlDescription": "Usa esta URL en la configuración de tu proveedor OAuth", + "copyCallbackUrl": "Copiar URL de Retorno", + "callbackUrlCopied": "¡URL de retorno copiada al portapapeles!", + "adminEmailDomains": "Dominios de Email de Administrador", + "adminEmailDomainsPlaceholder": "Ingresa dominios (ej., admin.empresa.com)", + "adminEmailDomainsHelp": "Los usuarios con emails de estos dominios recibirán privilegios de administrador", + "autoRegister": "Auto-registrar nuevos usuarios", + "officialProviderUrlPlaceholder": "Reemplaza el marcador de posición con tu URL de {displayName}", + "officialProviderHelp": "Este es un proveedor oficial. Los endpoints están preconfigurados. Solo puedes editar esta URL.", + "officialProviderIconHelp": "Puedes personalizar el ícono para este proveedor oficial." + }, + "buttons": { + "cancel": "Cancelar", + "save": "Guardar", + "saving": "Guardando...", + "adding": "Agregando...", + "updating": "Actualizando...", + "saveProvider": "Guardar Proveedor", + "delete": "Eliminar", + "deleting": "Eliminando...", + "edit": "Editar", + "enable": "Habilitar", + "disable": "Deshabilitar" + }, + "messages": { + "providerAdded": "Proveedor agregado exitosamente", + "providerUpdated": "Proveedor actualizado exitosamente", + "providerDeleted": "Proveedor eliminado exitosamente", + "providerOrderUpdated": "Orden de proveedores actualizado exitosamente", + "fillRequiredFields": "Por favor completa todos los campos requeridos (nombre, nombre para mostrar, ID de cliente, secreto de cliente)", + "provideUrlOrEndpoints": "Proporciona una URL del Proveedor para descubrimiento automático O los tres endpoints personalizados", + "chooseDiscoveryOrManual": "Elige descubrimiento automático (URL del Proveedor) O endpoints manuales, no ambos", + "loadFailed": "Error al cargar los proveedores", + "addFailed": "Error al agregar el proveedor", + "updateFailed": "Error al actualizar el proveedor", + "deleteFailed": "Error al eliminar el proveedor", + "orderUpdateFailed": "Error al actualizar el orden de los proveedores" + }, + "info": { + "title": "Información", + "officialProvidersRecommended": "Para una mejor funcionalidad, considera usar proveedores oficiales. Si tienes problemas con un proveedor personalizado, considera abrir un issue en", + "github": "GitHub", + "officialProvider": "Proveedor Oficial", + "officialProviderDescription": "Este proveedor está optimizado por Palmr. Solo se pueden modificar las credenciales y la configuración.", + "manualConfigTitle": "Configuración Manual", + "manualConfigDescription": "Estás proporcionando todos los endpoints manualmente. Asegúrate de que sean correctos para tu proveedor." + }, + "deleteModal": { + "title": "Eliminar Proveedor de Autenticación", + "confirmMessage": "¿Estás seguro de que deseas eliminar el proveedor \"{displayName}\"? Esta acción no se puede deshacer.", + "providerId": "ID del Proveedor: {name}", + "cancel": "Cancelar", + "delete": "Eliminar Proveedor", + "deleting": "Eliminando..." + } } } \ No newline at end of file diff --git a/apps/web/messages/fr-FR.json b/apps/web/messages/fr-FR.json index 9c9fda8..16835de 100644 --- a/apps/web/messages/fr-FR.json +++ b/apps/web/messages/fr-FR.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Fichiers reçus" + }, + "authProviders": { + "title": "Fournisseurs d'authentification", + "description": "Configurer les fournisseurs d'authentification externes pour le SSO", + "enabledCount": "{count} activés", + "loadingProviders": "Chargement des fournisseurs...", + "providersConfigured": "{count} fournisseurs configurés", + "enabledOfTotal": "{enabled} activés sur {total} fournisseurs", + "hideDisabledProviders": "Masquer les fournisseurs désactivés", + "addProvider": "Ajouter un fournisseur", + "addProviderTitle": "Ajouter un fournisseur", + "editProvider": "Modifier le fournisseur", + "deleteProvider": "Supprimer le fournisseur", + "enabled": "Activé", + "disabled": "Désactivé", + "officialProvider": "Fournisseur officiel", + "dragToReorder": "Glisser pour réorganiser", + "dragDisabledMessage": "Le glisser-déposer est désactivé lors du filtrage des fournisseurs. Affichez tous les fournisseurs pour les réorganiser.", + "dragEnabledMessage": "Faites glisser les fournisseurs pour les réorganiser. Cet ordre sera reflété sur la page de connexion.", + "noProvidersEnabled": "Aucun fournisseur d'authentification activé", + "noProvidersConfigured": "Aucun fournisseur d'authentification configuré", + "form": { + "providerName": "Nom du fournisseur", + "providerNamePlaceholder": "ex: masociete", + "displayName": "Nom d'affichage", + "displayNamePlaceholder": "ex: SSO de Ma Société", + "type": "Type", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Icône", + "iconPlaceholder": "Sélectionner une icône", + "clientId": "ID Client", + "clientIdPlaceholder": "Votre ID client OAuth", + "clientSecret": "Secret Client", + "clientSecretPlaceholder": "Votre secret client OAuth", + "oauthScopes": "Portées OAuth", + "scopesPlaceholder": "Entrez les portées (ex: openid, profile, email)", + "scopesHelpOidc": "Portées suggérées automatiquement selon l'URL du fournisseur. Portées OIDC courantes : openid, profile, email, groups", + "scopesHelpOauth2": "Portées suggérées automatiquement selon l'URL du fournisseur. Les portées OAuth2 courantes dépendent du fournisseur", + "providerUrl": "URL du fournisseur", + "providerUrlPlaceholder": "https://auth.exemple.com", + "providerUrlAutoPlaceholder": "https://votre-fournisseur.com (les points de terminaison seront découverts automatiquement)", + "providerUrlManualPlaceholder": "https://votre-fournisseur.com", + "autoDiscoveryHelp": "Le système découvrira automatiquement les points de terminaison d'autorisation, de jeton et d'informations utilisateur", + "manualConfigurationHelp": "URL de base de votre fournisseur (les points de terminaison seront relatifs à celle-ci)", + "authorizationEndpoint": "Point de terminaison d'autorisation", + "authorizationEndpointPlaceholder": "https://auth.exemple.com/auth", + "tokenEndpoint": "Point de terminaison de jeton", + "tokenEndpointPlaceholder": "https://auth.exemple.com/token", + "userInfoEndpoint": "Point de terminaison d'informations utilisateur", + "userInfoEndpointPlaceholder": "https://auth.exemple.com/userinfo", + "configurationMethod": "Méthode de configuration", + "autoDiscovery": "Découverte automatique (Recommandé)", + "autoDiscoveryDescription": "Découvrir automatiquement les points de terminaison depuis l'URL du fournisseur", + "manualEndpoints": "Points de terminaison manuels", + "manualEndpointsDescription": "Configurer manuellement les points de terminaison d'autorisation, de jeton et d'informations utilisateur", + "callbackUrl": "URL de rappel", + "callbackUrlDescription": "Utilisez cette URL dans la configuration de votre fournisseur OAuth", + "copyCallbackUrl": "Copier l'URL de rappel", + "callbackUrlCopied": "URL de rappel copiée dans le presse-papiers !", + "adminEmailDomains": "Domaines email administrateurs", + "adminEmailDomainsPlaceholder": "Entrez les domaines (ex: admin.entreprise.com)", + "adminEmailDomainsHelp": "Les utilisateurs avec des emails de ces domaines recevront des privilèges d'administrateur", + "autoRegister": "Inscription automatique des nouveaux utilisateurs", + "officialProviderUrlPlaceholder": "Remplacez l'espace réservé par votre URL {displayName}", + "officialProviderHelp": "Ceci est un fournisseur officiel. Les points de terminaison sont préconfigurés. Vous pouvez modifier uniquement cette URL.", + "officialProviderIconHelp": "Vous pouvez personnaliser l'icône de ce fournisseur officiel." + }, + "buttons": { + "cancel": "Annuler", + "save": "Enregistrer", + "saving": "Enregistrement...", + "adding": "Ajout...", + "updating": "Mise à jour...", + "saveProvider": "Enregistrer le fournisseur", + "delete": "Supprimer", + "deleting": "Suppression...", + "edit": "Modifier", + "enable": "Activer", + "disable": "Désactiver" + }, + "messages": { + "providerAdded": "Fournisseur ajouté avec succès", + "providerUpdated": "Fournisseur mis à jour avec succès", + "providerDeleted": "Fournisseur supprimé avec succès", + "providerOrderUpdated": "Ordre des fournisseurs mis à jour avec succès", + "fillRequiredFields": "Veuillez remplir tous les champs obligatoires (nom, nom d'affichage, ID client, secret client)", + "provideUrlOrEndpoints": "Fournissez soit une URL de fournisseur pour la découverte automatique, soit les trois points de terminaison personnalisés", + "chooseDiscoveryOrManual": "Choisissez soit la découverte automatique (URL du fournisseur) SOIT les points de terminaison manuels, pas les deux", + "loadFailed": "Échec du chargement des fournisseurs", + "addFailed": "Échec de l'ajout du fournisseur", + "updateFailed": "Échec de la mise à jour du fournisseur", + "deleteFailed": "Échec de la suppression du fournisseur", + "orderUpdateFailed": "Échec de la mise à jour de l'ordre des fournisseurs" + }, + "info": { + "title": "Information", + "officialProvidersRecommended": "Pour une meilleure fonctionnalité, considérez l'utilisation des fournisseurs officiels. Si vous rencontrez des problèmes avec un fournisseur personnalisé, envisagez d'ouvrir un ticket sur", + "github": "GitHub", + "officialProvider": "Fournisseur officiel", + "officialProviderDescription": "Ce fournisseur est optimisé par Palmr. Seuls les identifiants et la configuration peuvent être modifiés.", + "manualConfigTitle": "Configuration manuelle", + "manualConfigDescription": "Vous fournissez tous les points de terminaison manuellement. Assurez-vous qu'ils sont corrects pour votre fournisseur." + }, + "deleteModal": { + "title": "Supprimer le fournisseur d'authentification", + "confirmMessage": "Êtes-vous sûr de vouloir supprimer le fournisseur \"{displayName}\" ? Cette action ne peut pas être annulée.", + "providerId": "ID du fournisseur : {name}", + "cancel": "Annuler", + "delete": "Supprimer le fournisseur", + "deleting": "Suppression..." + } } } \ No newline at end of file diff --git a/apps/web/messages/hi-IN.json b/apps/web/messages/hi-IN.json index 68f130f..7804beb 100644 --- a/apps/web/messages/hi-IN.json +++ b/apps/web/messages/hi-IN.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "प्राप्त फ़ाइलें" + }, + "authProviders": { + "title": "प्रमाणीकरण प्रदाता", + "description": "एसएसओ के लिए बाहरी प्रमाणीकरण प्रदाताओं को कॉन्फ़िगर करें", + "enabledCount": "{count} सक्षम", + "loadingProviders": "प्रदाताओं को लोड कर रहा है...", + "providersConfigured": "{count} प्रदाता कॉन्फ़िगर किए गए", + "enabledOfTotal": "{total} प्रदाताओं में से {enabled} सक्षम", + "hideDisabledProviders": "अक्षम प्रदाताओं को छिपाएं", + "addProvider": "प्रदाता जोड़ें", + "addProviderTitle": "प्रदाता जोड़ें", + "editProvider": "प्रदाता संपादित करें", + "deleteProvider": "प्रदाता हटाएं", + "enabled": "सक्षम", + "disabled": "अक्षम", + "officialProvider": "आधिकारिक प्रदाता", + "dragToReorder": "पुनर्क्रमित करने के लिए खींचें", + "dragDisabledMessage": "प्रदाताओं को फ़िल्टर करते समय ड्रैग और ड्रॉप अक्षम है। उन्हें पुनर्क्रमित करने के लिए सभी प्रदाताओं को दिखाएं।", + "dragEnabledMessage": "प्रदाताओं को पुनर्क्रमित करने के लिए खींचें। यह क्रम लॉगिन पृष्ठ पर प्रतिबिंबित होगा।", + "noProvidersEnabled": "कोई सक्षम प्रमाणीकरण प्रदाता नहीं", + "noProvidersConfigured": "कोई प्रमाणीकरण प्रदाता कॉन्फ़िगर नहीं किया गया", + "form": { + "providerName": "प्रदाता का नाम", + "providerNamePlaceholder": "उदा., मेरीकंपनी", + "displayName": "प्रदर्शन नाम", + "displayNamePlaceholder": "उदा., मेरी कंपनी एसएसओ", + "type": "प्रकार", + "typeOidc": "ओआईडीसी (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "आइकन", + "iconPlaceholder": "एक आइकन चुनें", + "clientId": "क्लाइंट आईडी", + "clientIdPlaceholder": "आपकी OAuth क्लाइंट आईडी", + "clientSecret": "क्लाइंट सीक्रेट", + "clientSecretPlaceholder": "आपका OAuth क्लाइंट सीक्रेट", + "oauthScopes": "OAuth स्कोप्स", + "scopesPlaceholder": "स्कोप्स दर्ज करें (उदा., openid, profile, email)", + "scopesHelpOidc": "प्रदाता URL के आधार पर स्कोप्स स्वतः सुझाए गए। सामान्य OIDC स्कोप्स: openid, profile, email, groups", + "scopesHelpOauth2": "प्रदाता URL के आधार पर स्कोप्स स्वतः सुझाए गए। सामान्य OAuth2 स्कोप्स प्रदाता पर निर्भर करते हैं", + "providerUrl": "प्रदाता URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (एंडपॉइंट्स स्वचालित रूप से खोजे जाएंगे)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "सिस्टम स्वचालित रूप से प्राधिकरण, टोकन और यूजरइन्फो एंडपॉइंट्स की खोज करेगा", + "manualConfigurationHelp": "आपके प्रदाता का बेस URL (एंडपॉइंट्स इससे सापेक्ष होंगे)", + "authorizationEndpoint": "प्राधिकरण एंडपॉइंट", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "टोकन एंडपॉइंट", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "यूजर इन्फो एंडपॉइंट", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "कॉन्फ़िगरेशन विधि", + "autoDiscovery": "स्वचालित खोज (अनुशंसित)", + "autoDiscoveryDescription": "प्रदाता URL से स्वचालित रूप से एंडपॉइंट्स खोजें", + "manualEndpoints": "मैनुअल एंडपॉइंट्स", + "manualEndpointsDescription": "प्राधिकरण, टोकन और यूजर इन्फो एंडपॉइंट्स को मैनुअल रूप से कॉन्फ़िगर करें", + "callbackUrl": "कॉलबैक URL", + "callbackUrlDescription": "अपने OAuth प्रदाता कॉन्फ़िगरेशन में इस URL का उपयोग करें", + "copyCallbackUrl": "कॉलबैक URL कॉपी करें", + "callbackUrlCopied": "कॉलबैक URL क्लिपबोर्ड पर कॉपी किया गया!", + "adminEmailDomains": "व्यवस्थापक ईमेल डोमेन", + "adminEmailDomainsPlaceholder": "डोमेन दर्ज करें (उदा., admin.company.com)", + "adminEmailDomainsHelp": "इन डोमेन से ईमेल वाले उपयोगकर्ताओं को व्यवस्थापक अधिकार दिए जाएंगे", + "autoRegister": "नए उपयोगकर्ताओं को स्वतः पंजीकृत करें", + "officialProviderUrlPlaceholder": "प्लेसहोल्डर को अपने {displayName} URL से बदलें", + "officialProviderHelp": "यह एक आधिकारिक प्रदाता है। एंडपॉइंट्स पूर्व-कॉन्फ़िगर किए गए हैं। आप केवल इस URL को संपादित कर सकते हैं।", + "officialProviderIconHelp": "आप इस आधिकारिक प्रदाता के लिए आइकन को अनुकूलित कर सकते हैं।" + }, + "buttons": { + "cancel": "रद्द करें", + "save": "सहेजें", + "saving": "सहेज रहा है...", + "adding": "जोड़ रहा है...", + "updating": "अपडेट कर रहा है...", + "saveProvider": "प्रदाता सहेजें", + "delete": "हटाएं", + "deleting": "हटा रहा है...", + "edit": "संपादित करें", + "enable": "सक्षम करें", + "disable": "अक्षम करें" + }, + "messages": { + "providerAdded": "प्रदाता सफलतापूर्वक जोड़ा गया", + "providerUpdated": "प्रदाता सफलतापूर्वक अपडेट किया गया", + "providerDeleted": "प्रदाता सफलतापूर्वक हटाया गया", + "providerOrderUpdated": "प्रदाता क्रम सफलतापूर्वक अपडेट किया गया", + "fillRequiredFields": "कृपया सभी आवश्यक फ़ील्ड भरें (नाम, प्रदर्शन नाम, क्लाइंट आईडी, क्लाइंट सीक्रेट)", + "provideUrlOrEndpoints": "स्वचालित खोज के लिए प्रदाता URL या सभी तीन कस्टम एंडपॉइंट्स प्रदान करें", + "chooseDiscoveryOrManual": "या तो स्वचालित खोज (प्रदाता URL) या मैनुअल एंडपॉइंट्स चुनें, दोनों नहीं", + "loadFailed": "प्रदाताओं को लोड करने में विफल", + "addFailed": "प्रदाता जोड़ने में विफल", + "updateFailed": "प्रदाता अपडेट करने में विफल", + "deleteFailed": "प्रदाता हटाने में विफल", + "orderUpdateFailed": "प्रदाता क्रम अपडेट करने में विफल" + }, + "info": { + "title": "जानकारी", + "officialProvidersRecommended": "बेहतर कार्यक्षमता के लिए, आधिकारिक प्रदाताओं का उपयोग करने पर विचार करें। यदि आपको किसी कस्टम प्रदाता के साथ समस्या है, तो इस पर एक मुद्दा खोलने पर विचार करें", + "github": "GitHub", + "officialProvider": "आधिकारिक प्रदाता", + "officialProviderDescription": "यह प्रदाता Palmr द्वारा अनुकूलित है। केवल क्रेडेंशियल्स और कॉन्फ़िगरेशन को संशोधित किया जा सकता है।", + "manualConfigTitle": "मैनुअल कॉन्फ़िगरेशन", + "manualConfigDescription": "आप सभी एंडपॉइंट्स मैनुअल रूप से प्रदान कर रहे हैं। सुनिश्चित करें कि वे आपके प्रदाता के लिए सही हैं।" + }, + "deleteModal": { + "title": "प्रमाणीकरण प्रदाता हटाएं", + "confirmMessage": "क्या आप वाकई \"{displayName}\" प्रदाता को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।", + "providerId": "प्रदाता आईडी: {name}", + "cancel": "रद्द करें", + "delete": "प्रदाता हटाएं", + "deleting": "हटा रहा है..." + } } } \ No newline at end of file diff --git a/apps/web/messages/it-IT.json b/apps/web/messages/it-IT.json index d06276f..f02407b 100644 --- a/apps/web/messages/it-IT.json +++ b/apps/web/messages/it-IT.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "File ricevuti" + }, + "authProviders": { + "title": "Provider di Autenticazione", + "description": "Configura provider di autenticazione esterni per SSO", + "enabledCount": "{count} abilitati", + "loadingProviders": "Caricamento provider...", + "providersConfigured": "{count} provider configurati", + "enabledOfTotal": "{enabled} abilitati su {total} provider", + "hideDisabledProviders": "Nascondi provider disabilitati", + "addProvider": "Aggiungi Provider", + "addProviderTitle": "Aggiungi Provider", + "editProvider": "Modifica Provider", + "deleteProvider": "Elimina Provider", + "enabled": "Abilitato", + "disabled": "Disabilitato", + "officialProvider": "Provider Ufficiale", + "dragToReorder": "Trascina per riordinare", + "dragDisabledMessage": "Il trascinamento è disabilitato durante il filtraggio dei provider. Mostra tutti i provider per riordinarli.", + "dragEnabledMessage": "Trascina i provider per riordinarli. Quest'ordine sarà riflesso nella pagina di accesso.", + "noProvidersEnabled": "Nessun provider di autenticazione abilitato", + "noProvidersConfigured": "Nessun provider di autenticazione configurato", + "form": { + "providerName": "Nome Provider", + "providerNamePlaceholder": "es. miazienda", + "displayName": "Nome Visualizzato", + "displayNamePlaceholder": "es. SSO Mia Azienda", + "type": "Tipo", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Icona", + "iconPlaceholder": "Seleziona un'icona", + "clientId": "ID Client", + "clientIdPlaceholder": "Il tuo ID client OAuth", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Il tuo client secret OAuth", + "oauthScopes": "Scope OAuth", + "scopesPlaceholder": "Inserisci gli scope (es. openid, profile, email)", + "scopesHelpOidc": "Scope suggeriti automaticamente in base all'URL del Provider. Scope OIDC comuni: openid, profile, email, groups", + "scopesHelpOauth2": "Scope suggeriti automaticamente in base all'URL del Provider. Gli scope OAuth2 comuni dipendono dal provider", + "providerUrl": "URL Provider", + "providerUrlPlaceholder": "https://auth.esempio.com", + "providerUrlAutoPlaceholder": "https://tuo-provider.com (gli endpoint saranno rilevati automaticamente)", + "providerUrlManualPlaceholder": "https://tuo-provider.com", + "autoDiscoveryHelp": "Il sistema rileverà automaticamente gli endpoint di autorizzazione, token e userinfo", + "manualConfigurationHelp": "URL base del tuo provider (gli endpoint saranno relativi a questo)", + "authorizationEndpoint": "Endpoint di Autorizzazione", + "authorizationEndpointPlaceholder": "https://auth.esempio.com/auth", + "tokenEndpoint": "Endpoint Token", + "tokenEndpointPlaceholder": "https://auth.esempio.com/token", + "userInfoEndpoint": "Endpoint Info Utente", + "userInfoEndpointPlaceholder": "https://auth.esempio.com/userinfo", + "configurationMethod": "Metodo di Configurazione", + "autoDiscovery": "Rilevamento Automatico (Consigliato)", + "autoDiscoveryDescription": "Rileva automaticamente gli endpoint dall'URL del Provider", + "manualEndpoints": "Endpoint Manuali", + "manualEndpointsDescription": "Configura manualmente gli endpoint di autorizzazione, token e info utente", + "callbackUrl": "URL di Callback", + "callbackUrlDescription": "Usa questo URL nella configurazione del tuo provider OAuth", + "copyCallbackUrl": "Copia URL di Callback", + "callbackUrlCopied": "URL di Callback copiato negli appunti!", + "adminEmailDomains": "Domini Email Admin", + "adminEmailDomainsPlaceholder": "Inserisci domini (es. admin.azienda.com)", + "adminEmailDomainsHelp": "Gli utenti con email da questi domini riceveranno privilegi di amministratore", + "autoRegister": "Registra automaticamente nuovi utenti", + "officialProviderUrlPlaceholder": "Sostituisci il segnaposto con il tuo URL {displayName}", + "officialProviderHelp": "Questo è un provider ufficiale. Gli endpoint sono pre-configurati. Puoi modificare solo questo URL.", + "officialProviderIconHelp": "Puoi personalizzare l'icona per questo provider ufficiale." + }, + "buttons": { + "cancel": "Annulla", + "save": "Salva", + "saving": "Salvataggio...", + "adding": "Aggiunta...", + "updating": "Aggiornamento...", + "saveProvider": "Salva Provider", + "delete": "Elimina", + "deleting": "Eliminazione...", + "edit": "Modifica", + "enable": "Abilita", + "disable": "Disabilita" + }, + "messages": { + "providerAdded": "Provider aggiunto con successo", + "providerUpdated": "Provider aggiornato con successo", + "providerDeleted": "Provider eliminato con successo", + "providerOrderUpdated": "Ordine dei provider aggiornato con successo", + "fillRequiredFields": "Compila tutti i campi obbligatori (nome, nome visualizzato, ID client, client secret)", + "provideUrlOrEndpoints": "Fornisci un URL Provider per il rilevamento automatico OPPURE tutti e tre gli endpoint personalizzati", + "chooseDiscoveryOrManual": "Scegli il rilevamento automatico (URL Provider) O gli endpoint manuali, non entrambi", + "loadFailed": "Caricamento dei provider fallito", + "addFailed": "Aggiunta del provider fallita", + "updateFailed": "Aggiornamento del provider fallito", + "deleteFailed": "Eliminazione del provider fallita", + "orderUpdateFailed": "Aggiornamento dell'ordine dei provider fallito" + }, + "info": { + "title": "Informazioni", + "officialProvidersRecommended": "Per una migliore funzionalità, considera l'uso di provider ufficiali. Se hai problemi con un provider personalizzato, considera di aprire una segnalazione su", + "github": "GitHub", + "officialProvider": "Provider Ufficiale", + "officialProviderDescription": "Questo provider è ottimizzato da Palmr. Possono essere modificate solo le credenziali e la configurazione.", + "manualConfigTitle": "Configurazione Manuale", + "manualConfigDescription": "Stai fornendo tutti gli endpoint manualmente. Assicurati che siano corretti per il tuo provider." + }, + "deleteModal": { + "title": "Elimina Provider di Autenticazione", + "confirmMessage": "Sei sicuro di voler eliminare il provider \"{displayName}\"? Questa azione non può essere annullata.", + "providerId": "ID Provider: {name}", + "cancel": "Annulla", + "delete": "Elimina Provider", + "deleting": "Eliminazione..." + } } } \ No newline at end of file diff --git a/apps/web/messages/ja-JP.json b/apps/web/messages/ja-JP.json index 7fb1568..158dd7d 100644 --- a/apps/web/messages/ja-JP.json +++ b/apps/web/messages/ja-JP.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "受信したファイル" + }, + "authProviders": { + "title": "認証プロバイダー", + "description": "SSOの外部認証プロバイダーを設定する", + "enabledCount": "{count}件有効", + "loadingProviders": "プロバイダーを読み込み中...", + "providersConfigured": "{count}件のプロバイダーが設定済み", + "enabledOfTotal": "全{total}件中{enabled}件が有効", + "hideDisabledProviders": "無効なプロバイダーを非表示", + "addProvider": "プロバイダーを追加", + "addProviderTitle": "プロバイダーを追加", + "editProvider": "プロバイダーを編集", + "deleteProvider": "プロバイダーを削除", + "enabled": "有効", + "disabled": "無効", + "officialProvider": "公式プロバイダー", + "dragToReorder": "ドラッグして並び替え", + "dragDisabledMessage": "プロバイダーをフィルタリング中はドラッグ&ドロップが無効です。並び替えるには全てのプロバイダーを表示してください。", + "dragEnabledMessage": "プロバイダーをドラッグして並び替えできます。この順序はログインページに反映されます。", + "noProvidersEnabled": "有効な認証プロバイダーがありません", + "noProvidersConfigured": "設定された認証プロバイダーがありません", + "form": { + "providerName": "プロバイダー名", + "providerNamePlaceholder": "例:mycompany", + "displayName": "表示名", + "displayNamePlaceholder": "例:My Company SSO", + "type": "タイプ", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "アイコン", + "iconPlaceholder": "アイコンを選択", + "clientId": "クライアントID", + "clientIdPlaceholder": "OAuthクライアントID", + "clientSecret": "クライアントシークレット", + "clientSecretPlaceholder": "OAuthクライアントシークレット", + "oauthScopes": "OAuthスコープ", + "scopesPlaceholder": "スコープを入力(例:openid, profile, email)", + "scopesHelpOidc": "プロバイダーURLに基づいて自動提案されるスコープ。一般的なOIDCスコープ:openid, profile, email, groups", + "scopesHelpOauth2": "プロバイダーURLに基づいて自動提案されるスコープ。一般的なOAuth2スコープはプロバイダーによって異なります", + "providerUrl": "プロバイダーURL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com(エンドポイントは自動的に検出されます)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "システムは認証、トークン、ユーザー情報のエンドポイントを自動的に検出します", + "manualConfigurationHelp": "プロバイダーのベースURL(エンドポイントはこれを基準とします)", + "authorizationEndpoint": "認証エンドポイント", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "トークンエンドポイント", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "ユーザー情報エンドポイント", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "設定方法", + "autoDiscovery": "自動検出(推奨)", + "autoDiscoveryDescription": "プロバイダーURLからエンドポイントを自動的に検出", + "manualEndpoints": "手動エンドポイント", + "manualEndpointsDescription": "認証、トークン、ユーザー情報エンドポイントを手動で設定", + "callbackUrl": "コールバックURL", + "callbackUrlDescription": "OAuthプロバイダー設定でこのURLを使用してください", + "copyCallbackUrl": "コールバックURLをコピー", + "callbackUrlCopied": "コールバックURLをクリップボードにコピーしました!", + "adminEmailDomains": "管理者メールドメイン", + "adminEmailDomainsPlaceholder": "ドメインを入力(例:admin.company.com)", + "adminEmailDomainsHelp": "これらのドメインのメールアドレスを持つユーザーに管理者権限が付与されます", + "autoRegister": "新規ユーザーを自動登録", + "officialProviderUrlPlaceholder": "プレースホルダーを{displayName}のURLに置き換えてください", + "officialProviderHelp": "これは公式プロバイダーです。エンドポイントは事前設定されています。このURLのみ編集可能です。", + "officialProviderIconHelp": "この公式プロバイダーのアイコンをカスタマイズできます。" + }, + "buttons": { + "cancel": "キャンセル", + "save": "保存", + "saving": "保存中...", + "adding": "追加中...", + "updating": "更新中...", + "saveProvider": "プロバイダーを保存", + "delete": "削除", + "deleting": "削除中...", + "edit": "編集", + "enable": "有効化", + "disable": "無効化" + }, + "messages": { + "providerAdded": "プロバイダーを追加しました", + "providerUpdated": "プロバイダーを更新しました", + "providerDeleted": "プロバイダーを削除しました", + "providerOrderUpdated": "プロバイダーの順序を更新しました", + "fillRequiredFields": "必須項目(名前、表示名、クライアントID、クライアントシークレット)を入力してください", + "provideUrlOrEndpoints": "自動検出用のプロバイダーURLか、3つのカスタムエンドポイントのいずれかを提供してください", + "chooseDiscoveryOrManual": "自動検出(プロバイダーURL)か手動エンドポイントのどちらかを選択してください。両方は選択できません", + "loadFailed": "プロバイダーの読み込みに失敗しました", + "addFailed": "プロバイダーの追加に失敗しました", + "updateFailed": "プロバイダーの更新に失敗しました", + "deleteFailed": "プロバイダーの削除に失敗しました", + "orderUpdateFailed": "プロバイダーの順序の更新に失敗しました" + }, + "info": { + "title": "情報", + "officialProvidersRecommended": "より良い機能のために、公式プロバイダーの使用を検討してください。カスタムプロバイダーで問題が発生した場合は、以下で問題を報告することを検討してください:", + "github": "GitHub", + "officialProvider": "公式プロバイダー", + "officialProviderDescription": "このプロバイダーはPalmrによって最適化されています。認証情報と設定のみ変更可能です。", + "manualConfigTitle": "手動設定", + "manualConfigDescription": "すべてのエンドポイントを手動で提供しています。プロバイダーに対して正しいことを確認してください。" + }, + "deleteModal": { + "title": "認証プロバイダーの削除", + "confirmMessage": "本当に\"{displayName}\"プロバイダーを削除しますか?この操作は取り消せません。", + "providerId": "プロバイダーID:{name}", + "cancel": "キャンセル", + "delete": "プロバイダーを削除", + "deleting": "削除中..." + } } } \ No newline at end of file diff --git a/apps/web/messages/ko-KR.json b/apps/web/messages/ko-KR.json index 3182d5e..102c868 100644 --- a/apps/web/messages/ko-KR.json +++ b/apps/web/messages/ko-KR.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "받은 파일" + }, + "authProviders": { + "title": "인증 제공자", + "description": "SSO를 위한 외부 인증 제공자 구성", + "enabledCount": "{count}개 활성화됨", + "loadingProviders": "제공자 로딩 중...", + "providersConfigured": "{count}개의 제공자가 구성됨", + "enabledOfTotal": "전체 {total}개 중 {enabled}개 활성화됨", + "hideDisabledProviders": "비활성화된 제공자 숨기기", + "addProvider": "제공자 추가", + "addProviderTitle": "제공자 추가", + "editProvider": "제공자 편집", + "deleteProvider": "제공자 삭제", + "enabled": "활성화됨", + "disabled": "비활성화됨", + "officialProvider": "공식 제공자", + "dragToReorder": "드래그하여 순서 변경", + "dragDisabledMessage": "제공자 필터링 시 드래그 앤 드롭이 비활성화됩니다. 순서를 변경하려면 모든 제공자를 표시하세요.", + "dragEnabledMessage": "제공자를 드래그하여 순서를 변경하세요. 이 순서는 로그인 페이지에 반영됩니다.", + "noProvidersEnabled": "활성화된 인증 제공자가 없습니다", + "noProvidersConfigured": "구성된 인증 제공자가 없습니다", + "form": { + "providerName": "제공자 이름", + "providerNamePlaceholder": "예: mycompany", + "displayName": "표시 이름", + "displayNamePlaceholder": "예: 내 회사 SSO", + "type": "유형", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "아이콘", + "iconPlaceholder": "아이콘 선택", + "clientId": "클라이언트 ID", + "clientIdPlaceholder": "OAuth 클라이언트 ID", + "clientSecret": "클라이언트 시크릿", + "clientSecretPlaceholder": "OAuth 클라이언트 시크릿", + "oauthScopes": "OAuth 스코프", + "scopesPlaceholder": "스코프 입력 (예: openid, profile, email)", + "scopesHelpOidc": "제공자 URL을 기반으로 자동 제안된 스코프. 일반적인 OIDC 스코프: openid, profile, email, groups", + "scopesHelpOauth2": "제공자 URL을 기반으로 자동 제안된 스코프. 일반적인 OAuth2 스코프는 제공자에 따라 다릅니다", + "providerUrl": "제공자 URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (엔드포인트가 자동으로 발견됩니다)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "시스템이 자동으로 인증, 토큰 및 사용자 정보 엔드포인트를 발견합니다", + "manualConfigurationHelp": "제공자의 기본 URL (엔드포인트는 이것을 기준으로 상대적)", + "authorizationEndpoint": "인증 엔드포인트", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "토큰 엔드포인트", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "사용자 정보 엔드포인트", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "구성 방법", + "autoDiscovery": "자동 발견 (권장)", + "autoDiscoveryDescription": "제공자 URL에서 엔드포인트 자동 발견", + "manualEndpoints": "수동 엔드포인트", + "manualEndpointsDescription": "인증, 토큰 및 사용자 정보 엔드포인트를 수동으로 구성", + "callbackUrl": "콜백 URL", + "callbackUrlDescription": "OAuth 제공자 구성에서 이 URL을 사용하세요", + "copyCallbackUrl": "콜백 URL 복사", + "callbackUrlCopied": "콜백 URL이 클립보드에 복사되었습니다!", + "adminEmailDomains": "관리자 이메일 도메인", + "adminEmailDomainsPlaceholder": "도메인 입력 (예: admin.company.com)", + "adminEmailDomainsHelp": "이러한 도메인의 이메일을 가진 사용자에게 관리자 권한이 부여됩니다", + "autoRegister": "새 사용자 자동 등록", + "officialProviderUrlPlaceholder": "{displayName} URL로 자리표시자 교체", + "officialProviderHelp": "이것은 공식 제공자입니다. 엔드포인트가 미리 구성되어 있습니다. 이 URL만 편집할 수 있습니다.", + "officialProviderIconHelp": "이 공식 제공자의 아이콘을 사용자 지정할 수 있습니다." + }, + "buttons": { + "cancel": "취소", + "save": "저장", + "saving": "저장 중...", + "adding": "추가 중...", + "updating": "업데이트 중...", + "saveProvider": "제공자 저장", + "delete": "삭제", + "deleting": "삭제 중...", + "edit": "편집", + "enable": "활성화", + "disable": "비활성화" + }, + "messages": { + "providerAdded": "제공자가 성공적으로 추가되었습니다", + "providerUpdated": "제공자가 성공적으로 업데이트되었습니다", + "providerDeleted": "제공자가 성공적으로 삭제되었습니다", + "providerOrderUpdated": "제공자 순서가 성공적으로 업데이트되었습니다", + "fillRequiredFields": "모든 필수 필드를 입력하세요 (이름, 표시 이름, 클라이언트 ID, 클라이언트 시크릿)", + "provideUrlOrEndpoints": "자동 발견을 위한 제공자 URL 또는 세 가지 사용자 지정 엔드포인트를 모두 제공하세요", + "chooseDiscoveryOrManual": "자동 발견(제공자 URL) 또는 수동 엔드포인트 중 하나를 선택하세요. 둘 다는 안 됩니다", + "loadFailed": "제공자 로드에 실패했습니다", + "addFailed": "제공자 추가에 실패했습니다", + "updateFailed": "제공자 업데이트에 실패했습니다", + "deleteFailed": "제공자 삭제에 실패했습니다", + "orderUpdateFailed": "제공자 순서 업데이트에 실패했습니다" + }, + "info": { + "title": "정보", + "officialProvidersRecommended": "더 나은 기능을 위해 공식 제공자 사용을 고려하세요. 사용자 지정 제공자에 문제가 있는 경우 다음에서 이슈를 열어주세요", + "github": "GitHub", + "officialProvider": "공식 제공자", + "officialProviderDescription": "이 제공자는 Palmr에 의해 최적화되었습니다. 자격 증명과 구성만 수정할 수 있습니다.", + "manualConfigTitle": "수동 구성", + "manualConfigDescription": "모든 엔드포인트를 수동으로 제공하고 있습니다. 제공자에 맞게 올바르게 설정되었는지 확인하세요." + }, + "deleteModal": { + "title": "인증 제공자 삭제", + "confirmMessage": "\"{displayName}\" 제공자를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "providerId": "제공자 ID: {name}", + "cancel": "취소", + "delete": "제공자 삭제", + "deleting": "삭제 중..." + } } } \ No newline at end of file diff --git a/apps/web/messages/nl-NL.json b/apps/web/messages/nl-NL.json index e4ef7d2..929d048 100644 --- a/apps/web/messages/nl-NL.json +++ b/apps/web/messages/nl-NL.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Ontvangen bestanden" + }, + "authProviders": { + "title": "Authenticatie Providers", + "description": "Configureer externe authenticatie providers voor SSO", + "enabledCount": "{count} ingeschakeld", + "loadingProviders": "Providers laden...", + "providersConfigured": "{count} providers geconfigureerd", + "enabledOfTotal": "{enabled} ingeschakeld van {total} providers", + "hideDisabledProviders": "Uitgeschakelde providers verbergen", + "addProvider": "Provider Toevoegen", + "addProviderTitle": "Provider Toevoegen", + "editProvider": "Provider Bewerken", + "deleteProvider": "Provider Verwijderen", + "enabled": "Ingeschakeld", + "disabled": "Uitgeschakeld", + "officialProvider": "Officiële Provider", + "dragToReorder": "Sleep om te herordenen", + "dragDisabledMessage": "Slepen en neerzetten is uitgeschakeld bij het filteren van providers. Toon alle providers om ze opnieuw te ordenen.", + "dragEnabledMessage": "Sleep providers om ze te herordenen. Deze volgorde wordt weergegeven op de inlogpagina.", + "noProvidersEnabled": "Geen authenticatie providers ingeschakeld", + "noProvidersConfigured": "Geen authenticatie providers geconfigureerd", + "form": { + "providerName": "Provider Naam", + "providerNamePlaceholder": "bijv. mijnbedrijf", + "displayName": "Weergavenaam", + "displayNamePlaceholder": "bijv. Mijn Bedrijf SSO", + "type": "Type", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Icoon", + "iconPlaceholder": "Selecteer een icoon", + "clientId": "Client ID", + "clientIdPlaceholder": "Uw OAuth client ID", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Uw OAuth client secret", + "oauthScopes": "OAuth Scopes", + "scopesPlaceholder": "Voer scopes in (bijv. openid, profile, email)", + "scopesHelpOidc": "Scopes automatisch voorgesteld op basis van Provider URL. Gebruikelijke OIDC scopes: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes automatisch voorgesteld op basis van Provider URL. Gebruikelijke OAuth2 scopes zijn afhankelijk van de provider", + "providerUrl": "Provider URL", + "providerUrlPlaceholder": "https://auth.voorbeeld.nl", + "providerUrlAutoPlaceholder": "https://uw-provider.nl (eindpunten worden automatisch ontdekt)", + "providerUrlManualPlaceholder": "https://uw-provider.nl", + "autoDiscoveryHelp": "Het systeem zal automatisch autorisatie-, token- en gebruikersinformatie-eindpunten ontdekken", + "manualConfigurationHelp": "Basis-URL van uw provider (eindpunten zijn relatief hieraan)", + "authorizationEndpoint": "Autorisatie Eindpunt", + "authorizationEndpointPlaceholder": "https://auth.voorbeeld.nl/auth", + "tokenEndpoint": "Token Eindpunt", + "tokenEndpointPlaceholder": "https://auth.voorbeeld.nl/token", + "userInfoEndpoint": "Gebruikersinformatie Eindpunt", + "userInfoEndpointPlaceholder": "https://auth.voorbeeld.nl/userinfo", + "configurationMethod": "Configuratiemethode", + "autoDiscovery": "Automatische Ontdekking (Aanbevolen)", + "autoDiscoveryDescription": "Automatisch eindpunten ontdekken van Provider URL", + "manualEndpoints": "Handmatige Eindpunten", + "manualEndpointsDescription": "Handmatig configureren van autorisatie-, token- en gebruikersinformatie-eindpunten", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "Gebruik deze URL in uw OAuth provider configuratie", + "copyCallbackUrl": "Kopieer Callback URL", + "callbackUrlCopied": "Callback URL gekopieerd naar klembord!", + "adminEmailDomains": "Admin E-mail Domeinen", + "adminEmailDomainsPlaceholder": "Voer domeinen in (bijv. admin.bedrijf.nl)", + "adminEmailDomainsHelp": "Gebruikers met e-mails van deze domeinen krijgen beheerdersrechten", + "autoRegister": "Automatisch nieuwe gebruikers registreren", + "officialProviderUrlPlaceholder": "Vervang placeholder met uw {displayName} URL", + "officialProviderHelp": "Dit is een officiële provider. Eindpunten zijn vooraf geconfigureerd. U kunt alleen deze URL bewerken.", + "officialProviderIconHelp": "U kunt het icoon voor deze officiële provider aanpassen." + }, + "buttons": { + "cancel": "Annuleren", + "save": "Opslaan", + "saving": "Opslaan...", + "adding": "Toevoegen...", + "updating": "Bijwerken...", + "saveProvider": "Provider Opslaan", + "delete": "Verwijderen", + "deleting": "Verwijderen...", + "edit": "Bewerken", + "enable": "Inschakelen", + "disable": "Uitschakelen" + }, + "messages": { + "providerAdded": "Provider succesvol toegevoegd", + "providerUpdated": "Provider succesvol bijgewerkt", + "providerDeleted": "Provider succesvol verwijderd", + "providerOrderUpdated": "Provider volgorde succesvol bijgewerkt", + "fillRequiredFields": "Vul alle verplichte velden in (naam, weergavenaam, client ID, client secret)", + "provideUrlOrEndpoints": "Geef een Provider URL op voor automatische ontdekking OF alle drie de aangepaste eindpunten", + "chooseDiscoveryOrManual": "Kies óf automatische ontdekking (Provider URL) óf handmatige eindpunten, niet beide", + "loadFailed": "Laden van providers mislukt", + "addFailed": "Toevoegen van provider mislukt", + "updateFailed": "Bijwerken van provider mislukt", + "deleteFailed": "Verwijderen van provider mislukt", + "orderUpdateFailed": "Bijwerken van provider volgorde mislukt" + }, + "info": { + "title": "Informatie", + "officialProvidersRecommended": "Voor betere functionaliteit raden we aan om officiële providers te gebruiken. Als u problemen heeft met een aangepaste provider, overweeg dan een issue te openen op", + "github": "GitHub", + "officialProvider": "Officiële Provider", + "officialProviderDescription": "Deze provider is geoptimaliseerd door Palmr. Alleen inloggegevens en configuratie kunnen worden aangepast.", + "manualConfigTitle": "Handmatige Configuratie", + "manualConfigDescription": "U voorziet alle eindpunten handmatig. Zorg ervoor dat ze correct zijn voor uw provider." + }, + "deleteModal": { + "title": "Authenticatie Provider Verwijderen", + "confirmMessage": "Weet u zeker dat u de \"{displayName}\" provider wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "providerId": "Provider ID: {name}", + "cancel": "Annuleren", + "delete": "Provider Verwijderen", + "deleting": "Verwijderen..." + } } } \ No newline at end of file diff --git a/apps/web/messages/pl-PL.json b/apps/web/messages/pl-PL.json index f609cff..abe2676 100644 --- a/apps/web/messages/pl-PL.json +++ b/apps/web/messages/pl-PL.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Otrzymane pliki" + }, + "authProviders": { + "title": "Dostawcy uwierzytelniania", + "description": "Skonfiguruj zewnętrznych dostawców uwierzytelniania dla SSO", + "enabledCount": "{count} włączonych", + "loadingProviders": "Ładowanie dostawców...", + "providersConfigured": "Skonfigurowano {count} dostawców", + "enabledOfTotal": "{enabled} włączonych z {total} dostawców", + "hideDisabledProviders": "Ukryj wyłączonych dostawców", + "addProvider": "Dodaj dostawcę", + "addProviderTitle": "Dodaj dostawcę", + "editProvider": "Edytuj dostawcę", + "deleteProvider": "Usuń dostawcę", + "enabled": "Włączony", + "disabled": "Wyłączony", + "officialProvider": "Oficjalny dostawca", + "dragToReorder": "Przeciągnij, aby zmienić kolejność", + "dragDisabledMessage": "Przeciąganie jest wyłączone podczas filtrowania dostawców. Pokaż wszystkich dostawców, aby zmienić ich kolejność.", + "dragEnabledMessage": "Przeciągnij dostawców, aby zmienić ich kolejność. Ta kolejność będzie widoczna na stronie logowania.", + "noProvidersEnabled": "Brak włączonych dostawców uwierzytelniania", + "noProvidersConfigured": "Nie skonfigurowano żadnych dostawców uwierzytelniania", + "form": { + "providerName": "Nazwa dostawcy", + "providerNamePlaceholder": "np. mojafirma", + "displayName": "Nazwa wyświetlana", + "displayNamePlaceholder": "np. SSO Mojej Firmy", + "type": "Typ", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Ikona", + "iconPlaceholder": "Wybierz ikonę", + "clientId": "ID klienta", + "clientIdPlaceholder": "Twój ID klienta OAuth", + "clientSecret": "Sekret klienta", + "clientSecretPlaceholder": "Twój sekret klienta OAuth", + "oauthScopes": "Zakresy OAuth", + "scopesPlaceholder": "Wprowadź zakresy (np. openid, profile, email)", + "scopesHelpOidc": "Zakresy sugerowane automatycznie na podstawie URL dostawcy. Typowe zakresy OIDC: openid, profile, email, groups", + "scopesHelpOauth2": "Zakresy sugerowane automatycznie na podstawie URL dostawcy. Typowe zakresy OAuth2 zależą od dostawcy", + "providerUrl": "URL dostawcy", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://twoj-dostawca.com (punkty końcowe zostaną wykryte automatycznie)", + "providerUrlManualPlaceholder": "https://twoj-dostawca.com", + "autoDiscoveryHelp": "System automatycznie wykryje punkty końcowe autoryzacji, tokena i informacji o użytkowniku", + "manualConfigurationHelp": "Podstawowy URL twojego dostawcy (punkty końcowe będą względne do tego URL)", + "authorizationEndpoint": "Punkt końcowy autoryzacji", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Punkt końcowy tokena", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Punkt końcowy informacji o użytkowniku", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Metoda konfiguracji", + "autoDiscovery": "Automatyczne wykrywanie (Zalecane)", + "autoDiscoveryDescription": "Automatycznie wykryj punkty końcowe z URL dostawcy", + "manualEndpoints": "Ręczne punkty końcowe", + "manualEndpointsDescription": "Ręcznie skonfiguruj punkty końcowe autoryzacji, tokena i informacji o użytkowniku", + "callbackUrl": "URL wywołania zwrotnego", + "callbackUrlDescription": "Użyj tego URL w konfiguracji twojego dostawcy OAuth", + "copyCallbackUrl": "Kopiuj URL wywołania zwrotnego", + "callbackUrlCopied": "URL wywołania zwrotnego skopiowany do schowka!", + "adminEmailDomains": "Domeny email administratorów", + "adminEmailDomainsPlaceholder": "Wprowadź domeny (np. admin.firma.com)", + "adminEmailDomainsHelp": "Użytkownicy z adresami email z tych domen otrzymają uprawnienia administratora", + "autoRegister": "Automatycznie rejestruj nowych użytkowników", + "officialProviderUrlPlaceholder": "Zastąp placeholder swoim URL {displayName}", + "officialProviderHelp": "To jest oficjalny dostawca. Punkty końcowe są wstępnie skonfigurowane. Możesz edytować tylko ten URL.", + "officialProviderIconHelp": "Możesz dostosować ikonę dla tego oficjalnego dostawcy." + }, + "buttons": { + "cancel": "Anuluj", + "save": "Zapisz", + "saving": "Zapisywanie...", + "adding": "Dodawanie...", + "updating": "Aktualizowanie...", + "saveProvider": "Zapisz dostawcę", + "delete": "Usuń", + "deleting": "Usuwanie...", + "edit": "Edytuj", + "enable": "Włącz", + "disable": "Wyłącz" + }, + "messages": { + "providerAdded": "Dostawca został pomyślnie dodany", + "providerUpdated": "Dostawca został pomyślnie zaktualizowany", + "providerDeleted": "Dostawca został pomyślnie usunięty", + "providerOrderUpdated": "Kolejność dostawców została pomyślnie zaktualizowana", + "fillRequiredFields": "Proszę wypełnić wszystkie wymagane pola (nazwa, nazwa wyświetlana, ID klienta, sekret klienta)", + "provideUrlOrEndpoints": "Podaj albo URL dostawcy do automatycznego wykrywania ALBO wszystkie trzy niestandardowe punkty końcowe", + "chooseDiscoveryOrManual": "Wybierz albo automatyczne wykrywanie (URL dostawcy) ALBO ręczne punkty końcowe, nie oba", + "loadFailed": "Nie udało się załadować dostawców", + "addFailed": "Nie udało się dodać dostawcy", + "updateFailed": "Nie udało się zaktualizować dostawcy", + "deleteFailed": "Nie udało się usunąć dostawcy", + "orderUpdateFailed": "Nie udało się zaktualizować kolejności dostawców" + }, + "info": { + "title": "Informacja", + "officialProvidersRecommended": "Dla lepszej funkcjonalności rozważ użycie oficjalnych dostawców. Jeśli masz problemy z niestandardowym dostawcą, rozważ zgłoszenie problemu na", + "github": "GitHub", + "officialProvider": "Oficjalny dostawca", + "officialProviderDescription": "Ten dostawca jest zoptymalizowany przez Palmr. Można modyfikować tylko poświadczenia i konfigurację.", + "manualConfigTitle": "Konfiguracja ręczna", + "manualConfigDescription": "Podajesz wszystkie punkty końcowe ręcznie. Upewnij się, że są poprawne dla twojego dostawcy." + }, + "deleteModal": { + "title": "Usuń dostawcę uwierzytelniania", + "confirmMessage": "Czy na pewno chcesz usunąć dostawcę \"{displayName}\"? Tej akcji nie można cofnąć.", + "providerId": "ID dostawcy: {name}", + "cancel": "Anuluj", + "delete": "Usuń dostawcę", + "deleting": "Usuwanie..." + } } } \ No newline at end of file diff --git a/apps/web/messages/pt-BR.json b/apps/web/messages/pt-BR.json index d47d6d7..b49fdb6 100644 --- a/apps/web/messages/pt-BR.json +++ b/apps/web/messages/pt-BR.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Arquivos recebidos" + }, + "authProviders": { + "title": "Provedores de autenticação", + "description": "Configure provedores de autenticação externos para SSO", + "enabledCount": "{count} ativados", + "loadingProviders": "Carregando provedores...", + "providersConfigured": "{count} provedores configurados", + "enabledOfTotal": "{enabled} ativados de {total} provedores", + "hideDisabledProviders": "Ocultar provedores desativados", + "addProvider": "Adicionar provedor", + "addProviderTitle": "Adicionar provedor", + "editProvider": "Editar provedor", + "deleteProvider": "Excluir provedor", + "enabled": "Ativado", + "disabled": "Desativado", + "officialProvider": "Provedor oficial", + "dragToReorder": "Arraste para reordenar", + "dragDisabledMessage": "Arrastar e soltar está desativado ao filtrar provedores. Mostre todos os provedores para reordená-los.", + "dragEnabledMessage": "Arraste os provedores para reordená-los. Esta ordem será refletida na página de login.", + "noProvidersEnabled": "Nenhum provedor de autenticação ativado", + "noProvidersConfigured": "Nenhum provedor de autenticação configurado", + "form": { + "providerName": "Nome do provedor", + "providerNamePlaceholder": "ex: minhaempresa", + "displayName": "Nome de exibição", + "displayNamePlaceholder": "ex: SSO da Minha Empresa", + "type": "Tipo", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Ícone", + "iconPlaceholder": "Selecione um ícone", + "clientId": "ID do cliente", + "clientIdPlaceholder": "Seu ID do cliente OAuth", + "clientSecret": "Segredo do cliente", + "clientSecretPlaceholder": "Seu segredo do cliente OAuth", + "oauthScopes": "Escopos OAuth", + "scopesPlaceholder": "Digite os escopos (ex: openid, profile, email)", + "scopesHelpOidc": "Escopos sugeridos automaticamente com base na URL do Provedor. Escopos comuns do OIDC: openid, profile, email, groups", + "scopesHelpOauth2": "Escopos sugeridos automaticamente com base na URL do Provedor. Escopos comuns do OAuth2 dependem do provedor", + "providerUrl": "URL do provedor", + "providerUrlPlaceholder": "https://auth.exemplo.com", + "providerUrlAutoPlaceholder": "https://seu-provedor.com (endpoints serão descobertos automaticamente)", + "providerUrlManualPlaceholder": "https://seu-provedor.com", + "autoDiscoveryHelp": "O sistema descobrirá automaticamente os endpoints de autorização, token e informações do usuário", + "manualConfigurationHelp": "URL base do seu provedor (endpoints serão relativos a esta)", + "authorizationEndpoint": "Endpoint de Autorização", + "authorizationEndpointPlaceholder": "https://auth.exemplo.com/auth", + "tokenEndpoint": "Endpoint de Token", + "tokenEndpointPlaceholder": "https://auth.exemplo.com/token", + "userInfoEndpoint": "Endpoint de informações do usuário", + "userInfoEndpointPlaceholder": "https://auth.exemplo.com/userinfo", + "configurationMethod": "Método de configuração", + "autoDiscovery": "Descoberta Automática (Recomendado)", + "autoDiscoveryDescription": "Descobrir endpoints automaticamente a partir da URL do Provedor", + "manualEndpoints": "Endpoints Manuais", + "manualEndpointsDescription": "Configurar manualmente os endpoints de autorização, token e informações do usuário", + "callbackUrl": "URL de Retorno", + "callbackUrlDescription": "Use esta URL na configuração do seu provedor OAuth", + "copyCallbackUrl": "Copiar URL de Retorno", + "callbackUrlCopied": "URL de retorno copiada para a área de transferência!", + "adminEmailDomains": "Domínios de Email de Administrador", + "adminEmailDomainsPlaceholder": "Digite os domínios (ex: admin.empresa.com)", + "adminEmailDomainsHelp": "Usuários com emails destes domínios receberão privilégios de administrador", + "autoRegister": "Registrar automaticamente novos usuários", + "officialProviderUrlPlaceholder": "Substitua o placeholder com sua URL do {displayName}", + "officialProviderHelp": "Este é um provedor oficial. Os endpoints estão pré-configurados. Você pode editar apenas esta URL.", + "officialProviderIconHelp": "Você pode personalizar o ícone para este provedor oficial." + }, + "buttons": { + "cancel": "Cancelar", + "save": "Salvar", + "saving": "Salvando...", + "adding": "Adicionando...", + "updating": "Atualizando...", + "saveProvider": "Salvar Provedor", + "delete": "Excluir", + "deleting": "Excluindo...", + "edit": "Editar", + "enable": "Ativar", + "disable": "Desativar" + }, + "messages": { + "providerAdded": "Provedor adicionado com sucesso", + "providerUpdated": "Provedor atualizado com sucesso", + "providerDeleted": "Provedor excluído com sucesso", + "providerOrderUpdated": "Ordem dos provedores atualizada com sucesso", + "fillRequiredFields": "Por favor, preencha todos os campos obrigatórios (nome, nome de exibição, ID do cliente, segredo do cliente)", + "provideUrlOrEndpoints": "Forneça uma URL do Provedor para descoberta automática OU todos os três endpoints personalizados", + "chooseDiscoveryOrManual": "Escolha descoberta automática (URL do Provedor) OU endpoints manuais, não ambos", + "loadFailed": "Falha ao carregar provedores", + "addFailed": "Falha ao adicionar provedor", + "updateFailed": "Falha ao atualizar provedor", + "deleteFailed": "Falha ao excluir provedor", + "orderUpdateFailed": "Falha ao atualizar ordem dos provedores" + }, + "info": { + "title": "Informações", + "officialProvidersRecommended": "Para melhor funcionalidade, considere usar provedores oficiais. Se você tiver problemas com um provedor personalizado, considere abrir uma issue no", + "github": "GitHub", + "officialProvider": "Provedor Oficial", + "officialProviderDescription": "Este provedor é otimizado pelo Palmr. Apenas credenciais e configuração podem ser modificadas.", + "manualConfigTitle": "Configuração Manual", + "manualConfigDescription": "Você está fornecendo todos os endpoints manualmente. Certifique-se de que estejam corretos para seu provedor." + }, + "deleteModal": { + "title": "Excluir Provedor de Autenticação", + "confirmMessage": "Tem certeza que deseja excluir o provedor \"{displayName}\"? Esta ação não pode ser desfeita.", + "providerId": "ID do Provedor: {name}", + "cancel": "Cancelar", + "delete": "Excluir Provedor", + "deleting": "Excluindo..." + } } } \ No newline at end of file diff --git a/apps/web/messages/ru-RU.json b/apps/web/messages/ru-RU.json index 86a0a51..7310a8f 100644 --- a/apps/web/messages/ru-RU.json +++ b/apps/web/messages/ru-RU.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Полученные файлы" + }, + "authProviders": { + "title": "Провайдеры аутентификации", + "description": "Настройка внешних провайдеров аутентификации для SSO", + "enabledCount": "{count} включено", + "loadingProviders": "Загрузка провайдеров...", + "providersConfigured": "Настроено {count} провайдеров", + "enabledOfTotal": "Включено {enabled} из {total} провайдеров", + "hideDisabledProviders": "Скрыть отключенные провайдеры", + "addProvider": "Добавить провайдера", + "addProviderTitle": "Добавить провайдера", + "editProvider": "Редактировать провайдера", + "deleteProvider": "Удалить провайдера", + "enabled": "Включен", + "disabled": "Отключен", + "officialProvider": "Официальный провайдер", + "dragToReorder": "Перетащите для изменения порядка", + "dragDisabledMessage": "Перетаскивание отключено при фильтрации провайдеров. Покажите всех провайдеров для изменения порядка.", + "dragEnabledMessage": "Перетащите провайдеров для изменения порядка. Этот порядок будет отображен на странице входа.", + "noProvidersEnabled": "Нет включенных провайдеров аутентификации", + "noProvidersConfigured": "Нет настроенных провайдеров аутентификации", + "form": { + "providerName": "Имя провайдера", + "providerNamePlaceholder": "например, mycompany", + "displayName": "Отображаемое имя", + "displayNamePlaceholder": "например, SSO моей компании", + "type": "Тип", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Иконка", + "iconPlaceholder": "Выберите иконку", + "clientId": "ID клиента", + "clientIdPlaceholder": "Ваш ID клиента OAuth", + "clientSecret": "Секрет клиента", + "clientSecretPlaceholder": "Ваш секрет клиента OAuth", + "oauthScopes": "Области OAuth", + "scopesPlaceholder": "Введите области (например, openid, profile, email)", + "scopesHelpOidc": "Области автоматически предлагаются на основе URL провайдера. Общие области OIDC: openid, profile, email, groups", + "scopesHelpOauth2": "Области автоматически предлагаются на основе URL провайдера. Общие области OAuth2 зависят от провайдера", + "providerUrl": "URL провайдера", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (конечные точки будут обнаружены автоматически)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Система автоматически обнаружит конечные точки авторизации, токена и информации о пользователе", + "manualConfigurationHelp": "Базовый URL вашего провайдера (конечные точки будут относительно этого URL)", + "authorizationEndpoint": "Конечная точка авторизации", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Конечная точка токена", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Конечная точка информации о пользователе", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Метод конфигурации", + "autoDiscovery": "Автоматическое обнаружение (Рекомендуется)", + "autoDiscoveryDescription": "Автоматически обнаруживать конечные точки из URL провайдера", + "manualEndpoints": "Ручные конечные точки", + "manualEndpointsDescription": "Вручную настроить конечные точки авторизации, токена и информации о пользователе", + "callbackUrl": "URL обратного вызова", + "callbackUrlDescription": "Используйте этот URL в конфигурации вашего OAuth провайдера", + "copyCallbackUrl": "Копировать URL обратного вызова", + "callbackUrlCopied": "URL обратного вызова скопирован в буфер обмена!", + "adminEmailDomains": "Домены электронной почты администраторов", + "adminEmailDomainsPlaceholder": "Введите домены (например, admin.company.com)", + "adminEmailDomainsHelp": "Пользователи с электронной почтой из этих доменов получат права администратора", + "autoRegister": "Автоматически регистрировать новых пользователей", + "officialProviderUrlPlaceholder": "Замените заполнитель вашим URL {displayName}", + "officialProviderHelp": "Это официальный провайдер. Конечные точки предварительно настроены. Вы можете редактировать только этот URL.", + "officialProviderIconHelp": "Вы можете настроить иконку для этого официального провайдера." + }, + "buttons": { + "cancel": "Отмена", + "save": "Сохранить", + "saving": "Сохранение...", + "adding": "Добавление...", + "updating": "Обновление...", + "saveProvider": "Сохранить провайдера", + "delete": "Удалить", + "deleting": "Удаление...", + "edit": "Редактировать", + "enable": "Включить", + "disable": "Отключить" + }, + "messages": { + "providerAdded": "Провайдер успешно добавлен", + "providerUpdated": "Провайдер успешно обновлен", + "providerDeleted": "Провайдер успешно удален", + "providerOrderUpdated": "Порядок провайдеров успешно обновлен", + "fillRequiredFields": "Пожалуйста, заполните все обязательные поля (имя, отображаемое имя, ID клиента, секрет клиента)", + "provideUrlOrEndpoints": "Укажите либо URL провайдера для автоматического обнаружения, ЛИБО все три пользовательские конечные точки", + "chooseDiscoveryOrManual": "Выберите либо автоматическое обнаружение (URL провайдера), ЛИБО ручные конечные точки, но не оба варианта", + "loadFailed": "Не удалось загрузить провайдеров", + "addFailed": "Не удалось добавить провайдера", + "updateFailed": "Не удалось обновить провайдера", + "deleteFailed": "Не удалось удалить провайдера", + "orderUpdateFailed": "Не удалось обновить порядок провайдеров" + }, + "info": { + "title": "Информация", + "officialProvidersRecommended": "Для лучшей функциональности рекомендуется использовать официальных провайдеров. Если у вас возникли проблемы с пользовательским провайдером, рассмотрите возможность создания issue на", + "github": "GitHub", + "officialProvider": "Официальный провайдер", + "officialProviderDescription": "Этот провайдер оптимизирован Palmr. Можно изменять только учетные данные и конфигурацию.", + "manualConfigTitle": "Ручная настройка", + "manualConfigDescription": "Вы предоставляете все конечные точки вручную. Убедитесь, что они правильны для вашего провайдера." + }, + "deleteModal": { + "title": "Удалить провайдера аутентификации", + "confirmMessage": "Вы уверены, что хотите удалить провайдера \"{displayName}\"? Это действие нельзя отменить.", + "providerId": "ID провайдера: {name}", + "cancel": "Отмена", + "delete": "Удалить провайдера", + "deleting": "Удаление..." + } } } \ No newline at end of file diff --git a/apps/web/messages/tr-TR.json b/apps/web/messages/tr-TR.json index 23f849b..a2e89af 100644 --- a/apps/web/messages/tr-TR.json +++ b/apps/web/messages/tr-TR.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "Alınan Dosyalar" + }, + "authProviders": { + "title": "Kimlik Doğrulama Sağlayıcıları", + "description": "SSO için harici kimlik doğrulama sağlayıcılarını yapılandırın", + "enabledCount": "{count} etkin", + "loadingProviders": "Sağlayıcılar yükleniyor...", + "providersConfigured": "{count} sağlayıcı yapılandırıldı", + "enabledOfTotal": "{total} sağlayıcıdan {enabled} tanesi etkin", + "hideDisabledProviders": "Devre dışı sağlayıcıları gizle", + "addProvider": "Sağlayıcı Ekle", + "addProviderTitle": "Sağlayıcı Ekle", + "editProvider": "Sağlayıcıyı Düzenle", + "deleteProvider": "Sağlayıcıyı Sil", + "enabled": "Etkin", + "disabled": "Devre Dışı", + "officialProvider": "Resmi Sağlayıcı", + "dragToReorder": "Yeniden sıralamak için sürükleyin", + "dragDisabledMessage": "Sağlayıcıları filtrelerken sürükle ve bırak devre dışıdır. Yeniden sıralamak için tüm sağlayıcıları gösterin.", + "dragEnabledMessage": "Sağlayıcıları yeniden sıralamak için sürükleyin. Bu sıralama giriş sayfasına yansıtılacaktır.", + "noProvidersEnabled": "Etkin kimlik doğrulama sağlayıcısı yok", + "noProvidersConfigured": "Yapılandırılmış kimlik doğrulama sağlayıcısı yok", + "form": { + "providerName": "Sağlayıcı Adı", + "providerNamePlaceholder": "örn., sirketim", + "displayName": "Görünen Ad", + "displayNamePlaceholder": "örn., Şirketim SSO", + "type": "Tür", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Simge", + "iconPlaceholder": "Bir simge seçin", + "clientId": "İstemci Kimliği", + "clientIdPlaceholder": "OAuth istemci kimliğiniz", + "clientSecret": "İstemci Sırrı", + "clientSecretPlaceholder": "OAuth istemci sırrınız", + "oauthScopes": "OAuth Kapsamları", + "scopesPlaceholder": "Kapsamları girin (örn., openid, profile, email)", + "scopesHelpOidc": "Sağlayıcı URL'sine göre otomatik önerilen kapsamlar. Yaygın OIDC kapsamları: openid, profile, email, groups", + "scopesHelpOauth2": "Sağlayıcı URL'sine göre otomatik önerilen kapsamlar. Yaygın OAuth2 kapsamları sağlayıcıya bağlıdır", + "providerUrl": "Sağlayıcı URL'si", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (uç noktalar otomatik keşfedilecek)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Sistem otomatik olarak yetkilendirme, token ve kullanıcı bilgisi uç noktalarını keşfedecek", + "manualConfigurationHelp": "Sağlayıcınızın temel URL'si (uç noktalar buna göre belirlenecek)", + "authorizationEndpoint": "Yetkilendirme Uç Noktası", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Token Uç Noktası", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Kullanıcı Bilgisi Uç Noktası", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Yapılandırma Yöntemi", + "autoDiscovery": "Otomatik Keşif (Önerilen)", + "autoDiscoveryDescription": "Sağlayıcı URL'sinden uç noktaları otomatik keşfet", + "manualEndpoints": "Manuel Uç Noktalar", + "manualEndpointsDescription": "Yetkilendirme, token ve kullanıcı bilgisi uç noktalarını manuel yapılandır", + "callbackUrl": "Geri Dönüş URL'si", + "callbackUrlDescription": "Bu URL'yi OAuth sağlayıcı yapılandırmanızda kullanın", + "copyCallbackUrl": "Geri Dönüş URL'sini Kopyala", + "callbackUrlCopied": "Geri dönüş URL'si panoya kopyalandı!", + "adminEmailDomains": "Yönetici E-posta Etki Alanları", + "adminEmailDomainsPlaceholder": "Etki alanlarını girin (örn., admin.sirket.com)", + "adminEmailDomainsHelp": "Bu etki alanlarından e-postalara sahip kullanıcılara yönetici yetkileri verilecek", + "autoRegister": "Yeni kullanıcıları otomatik kaydet", + "officialProviderUrlPlaceholder": "{displayName} URL'nizi yer tutucuyla değiştirin", + "officialProviderHelp": "Bu resmi bir sağlayıcıdır. Uç noktalar önceden yapılandırılmıştır. Sadece bu URL'yi düzenleyebilirsiniz.", + "officialProviderIconHelp": "Bu resmi sağlayıcının simgesini özelleştirebilirsiniz." + }, + "buttons": { + "cancel": "İptal", + "save": "Kaydet", + "saving": "Kaydediliyor...", + "adding": "Ekleniyor...", + "updating": "Güncelleniyor...", + "saveProvider": "Sağlayıcıyı Kaydet", + "delete": "Sil", + "deleting": "Siliniyor...", + "edit": "Düzenle", + "enable": "Etkinleştir", + "disable": "Devre Dışı Bırak" + }, + "messages": { + "providerAdded": "Sağlayıcı başarıyla eklendi", + "providerUpdated": "Sağlayıcı başarıyla güncellendi", + "providerDeleted": "Sağlayıcı başarıyla silindi", + "providerOrderUpdated": "Sağlayıcı sıralaması başarıyla güncellendi", + "fillRequiredFields": "Lütfen tüm gerekli alanları doldurun (ad, görünen ad, istemci kimliği, istemci sırrı)", + "provideUrlOrEndpoints": "Otomatik keşif için bir Sağlayıcı URL'si VEYA üç özel uç noktanın tümünü sağlayın", + "chooseDiscoveryOrManual": "Otomatik keşif (Sağlayıcı URL'si) VEYA manuel uç noktalardan birini seçin, ikisini birden değil", + "loadFailed": "Sağlayıcılar yüklenemedi", + "addFailed": "Sağlayıcı eklenemedi", + "updateFailed": "Sağlayıcı güncellenemedi", + "deleteFailed": "Sağlayıcı silinemedi", + "orderUpdateFailed": "Sağlayıcı sıralaması güncellenemedi" + }, + "info": { + "title": "Bilgi", + "officialProvidersRecommended": "Daha iyi işlevsellik için resmi sağlayıcıları kullanmayı düşünün. Özel bir sağlayıcıyla ilgili sorun yaşarsanız,", + "github": "GitHub", + "officialProvider": "Resmi Sağlayıcı", + "officialProviderDescription": "Bu sağlayıcı Palmr tarafından optimize edilmiştir. Sadece kimlik bilgileri ve yapılandırma değiştirilebilir.", + "manualConfigTitle": "Manuel Yapılandırma", + "manualConfigDescription": "Tüm uç noktaları manuel olarak sağlıyorsunuz. Sağlayıcınız için doğru olduklarından emin olun." + }, + "deleteModal": { + "title": "Kimlik Doğrulama Sağlayıcısını Sil", + "confirmMessage": "\"{displayName}\" sağlayıcısını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "providerId": "Sağlayıcı Kimliği: {name}", + "cancel": "İptal", + "delete": "Sağlayıcıyı Sil", + "deleting": "Siliniyor..." + } } } \ No newline at end of file diff --git a/apps/web/messages/translate_missing.py b/apps/web/messages/translate_missing.py deleted file mode 100644 index 46ed0e6..0000000 --- a/apps/web/messages/translate_missing.py +++ /dev/null @@ -1,342 +0,0 @@ -#!/usr/bin/env python3 -""" -Script para traduzir automaticamente strings marcadas com [TO_TRANSLATE] -usando Google Translate gratuito. -""" - -import json -import time -import re -from pathlib import Path -from typing import Dict, Any, List, Tuple, Optional -import argparse -import sys - -# Mapeamento de códigos de idioma dos arquivos para códigos do Google Translate -LANGUAGE_MAPPING = { - 'pt-BR.json': 'pt', # Português (Brasil) -> Português - 'es-ES.json': 'es', # Espanhol (Espanha) -> Espanhol - 'fr-FR.json': 'fr', # Francês (França) -> Francês - 'de-DE.json': 'de', # Alemão -> Alemão - 'it-IT.json': 'it', # Italiano -> Italiano - 'ru-RU.json': 'ru', # Russo -> Russo - 'ja-JP.json': 'ja', # Japonês -> Japonês - 'ko-KR.json': 'ko', # Coreano -> Coreano - 'zh-CN.json': 'zh-cn', # Chinês (Simplificado) -> Chinês Simplificado - 'ar-SA.json': 'ar', # Árabe -> Árabe - 'hi-IN.json': 'hi', # Hindi -> Hindi - 'nl-NL.json': 'nl', # Holandês -> Holandês - 'tr-TR.json': 'tr', # Turco -> Turco - 'pl-PL.json': 'pl', # Polonês -> Polonês -} - -# Prefixo para identificar strings não traduzidas -TO_TRANSLATE_PREFIX = '[TO_TRANSLATE] ' - - -def load_json_file(file_path: Path) -> Dict[str, Any]: - """Carrega um arquivo JSON.""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - print(f"❌ Erro ao carregar {file_path}: {e}") - return {} - - -def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool: - """Salva um arquivo JSON com formatação consistente.""" - try: - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': ')) - f.write('\n') # Adiciona nova linha no final - return True - except Exception as e: - print(f"❌ Erro ao salvar {file_path}: {e}") - return False - - -def get_nested_value(data: Dict[str, Any], key_path: str) -> Any: - """Obtém um valor aninhado usando uma chave com pontos como separador.""" - keys = key_path.split('.') - current = data - - for key in keys: - if isinstance(current, dict) and key in current: - current = current[key] - else: - return None - - return current - - -def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None: - """Define um valor aninhado usando uma chave com pontos como separador.""" - keys = key_path.split('.') - current = data - - # Navega até o penúltimo nível, criando dicionários conforme necessário - for key in keys[:-1]: - if key not in current: - current[key] = {} - elif not isinstance(current[key], dict): - current[key] = {} - current = current[key] - - # Define o valor no último nível - current[keys[-1]] = value - - -def find_untranslated_strings(data: Dict[str, Any], prefix: str = '') -> List[Tuple[str, str]]: - """Encontra todas as strings marcadas com [TO_TRANSLATE] recursivamente.""" - untranslated = [] - - for key, value in data.items(): - current_key = f"{prefix}.{key}" if prefix else key - - if isinstance(value, str) and value.startswith(TO_TRANSLATE_PREFIX): - # Remove o prefixo para obter o texto original - original_text = value[len(TO_TRANSLATE_PREFIX):].strip() - untranslated.append((current_key, original_text)) - elif isinstance(value, dict): - untranslated.extend(find_untranslated_strings(value, current_key)) - - return untranslated - - -def install_googletrans(): - """Instala a biblioteca googletrans se não estiver disponível.""" - try: - import googletrans - return True - except ImportError: - print("📦 Biblioteca 'googletrans' não encontrada. Tentando instalar...") - import subprocess - try: - subprocess.check_call([sys.executable, "-m", "pip", "install", "googletrans==4.0.0rc1"]) - print("✅ googletrans instalada com sucesso!") - return True - except subprocess.CalledProcessError: - print("❌ Falha ao instalar googletrans. Instale manualmente com:") - print("pip install googletrans==4.0.0rc1") - return False - - -def translate_text(text: str, target_language: str, max_retries: int = 3) -> Optional[str]: - """Traduz um texto usando Google Translate gratuito.""" - try: - from googletrans import Translator - - translator = Translator() - - for attempt in range(max_retries): - try: - # Traduz do inglês para o idioma alvo - result = translator.translate(text, src='en', dest=target_language) - - if result and result.text: - return result.text.strip() - - except Exception as e: - if attempt < max_retries - 1: - print(f" ⚠️ Tentativa {attempt + 1} falhou: {str(e)[:50]}... Reentando em 2s...") - time.sleep(2) - else: - print(f" ❌ Falha após {max_retries} tentativas: {str(e)[:50]}...") - - return None - - except ImportError: - print("❌ Biblioteca googletrans não disponível") - return None - - -def translate_file(file_path: Path, target_language: str, dry_run: bool = False, - delay_between_requests: float = 1.0) -> Tuple[int, int, int]: - """ - Traduz todas as strings [TO_TRANSLATE] em um arquivo. - Retorna: (total_found, successful_translations, failed_translations) - """ - print(f"🔍 Processando: {file_path.name}") - - # Carrega o arquivo - data = load_json_file(file_path) - if not data: - return 0, 0, 0 - - # Encontra strings não traduzidas - untranslated_strings = find_untranslated_strings(data) - - if not untranslated_strings: - print(f" ✅ Nenhuma string para traduzir") - return 0, 0, 0 - - print(f" 📝 Encontradas {len(untranslated_strings)} strings para traduzir") - - if dry_run: - print(f" 🔍 [DRY RUN] Strings que seriam traduzidas:") - for key, text in untranslated_strings[:3]: - print(f" - {key}: \"{text[:50]}{'...' if len(text) > 50 else ''}\"") - if len(untranslated_strings) > 3: - print(f" ... e mais {len(untranslated_strings) - 3}") - return len(untranslated_strings), 0, 0 - - # Traduz cada string - successful = 0 - failed = 0 - updated_data = data.copy() - - for i, (key_path, original_text) in enumerate(untranslated_strings, 1): - print(f" 📍 ({i}/{len(untranslated_strings)}) Traduzindo: {key_path}") - - # Traduz o texto - translated_text = translate_text(original_text, target_language) - - if translated_text and translated_text != original_text: - # Atualiza no dicionário - set_nested_value(updated_data, key_path, translated_text) - successful += 1 - print(f" ✅ \"{original_text[:30]}...\" → \"{translated_text[:30]}...\"") - else: - failed += 1 - print(f" ❌ Falha na tradução") - - # Delay entre requisições para evitar rate limiting - if i < len(untranslated_strings): # Não espera após a última - time.sleep(delay_between_requests) - - # Salva o arquivo atualizado - if successful > 0: - if save_json_file(file_path, updated_data): - print(f" 💾 Arquivo salvo com {successful} traduções") - else: - print(f" ❌ Erro ao salvar arquivo") - failed += successful # Conta como falha se não conseguiu salvar - successful = 0 - - return len(untranslated_strings), successful, failed - - -def translate_all_files(messages_dir: Path, delay_between_requests: float = 1.0, - dry_run: bool = False, skip_languages: List[str] = None) -> None: - """Traduz todos os arquivos de idioma que têm strings [TO_TRANSLATE].""" - - if not install_googletrans(): - return - - skip_languages = skip_languages or [] - - # Encontra arquivos JSON de idioma - language_files = [] - for file_name, lang_code in LANGUAGE_MAPPING.items(): - file_path = messages_dir / file_name - if file_path.exists() and file_name not in skip_languages: - language_files.append((file_path, lang_code)) - - if not language_files: - print("❌ Nenhum arquivo de idioma encontrado") - return - - print(f"🌍 Traduzindo {len(language_files)} idiomas...") - print(f"⏱️ Delay entre requisições: {delay_between_requests}s") - if dry_run: - print("🔍 MODO DRY RUN - Nenhuma alteração será feita") - print("-" * 60) - - total_found = 0 - total_successful = 0 - total_failed = 0 - - for i, (file_path, lang_code) in enumerate(language_files, 1): - print(f"\n[{i}/{len(language_files)}] 🌐 Idioma: {lang_code.upper()}") - - found, successful, failed = translate_file( - file_path, lang_code, dry_run, delay_between_requests - ) - - total_found += found - total_successful += successful - total_failed += failed - - # Pausa entre arquivos (exceto o último) - if i < len(language_files) and not dry_run: - print(f" ⏸️ Pausando {delay_between_requests * 2}s antes do próximo idioma...") - time.sleep(delay_between_requests * 2) - - # Sumário final - print("\n" + "=" * 60) - print("📊 SUMÁRIO FINAL") - print("=" * 60) - - if dry_run: - print(f"🔍 MODO DRY RUN:") - print(f" • {total_found} strings seriam traduzidas") - else: - print(f"✅ Traduções realizadas:") - print(f" • {total_successful} sucessos") - print(f" • {total_failed} falhas") - print(f" • {total_found} total processadas") - - if total_successful > 0: - success_rate = (total_successful / total_found) * 100 - print(f" • Taxa de sucesso: {success_rate:.1f}%") - - print("\n💡 DICAS:") - print("• Execute 'python3 check_translations.py' para verificar o resultado") - print("• Strings que falharam na tradução mantêm o prefixo [TO_TRANSLATE]") - print("• Considere revisar as traduções automáticas para garantir qualidade") - - -def main(): - parser = argparse.ArgumentParser( - description='Traduz automaticamente strings marcadas com [TO_TRANSLATE]' - ) - parser.add_argument( - '--messages-dir', - type=Path, - default=Path(__file__).parent, - help='Diretório contendo os arquivos de mensagem (padrão: diretório atual)' - ) - parser.add_argument( - '--dry-run', - action='store_true', - help='Apenas mostra o que seria traduzido, sem fazer alterações' - ) - parser.add_argument( - '--delay', - type=float, - default=1.0, - help='Delay em segundos entre requisições de tradução (padrão: 1.0)' - ) - parser.add_argument( - '--skip-languages', - nargs='*', - default=[], - help='Lista de idiomas para pular (ex: pt-BR.json fr-FR.json)' - ) - - args = parser.parse_args() - - if not args.messages_dir.exists(): - print(f"❌ Diretório não encontrado: {args.messages_dir}") - return 1 - - print(f"📁 Diretório: {args.messages_dir}") - print(f"🔍 Dry run: {args.dry_run}") - print(f"⏱️ Delay: {args.delay}s") - if args.skip_languages: - print(f"⏭️ Ignorando: {', '.join(args.skip_languages)}") - print("-" * 60) - - translate_all_files( - messages_dir=args.messages_dir, - delay_between_requests=args.delay, - dry_run=args.dry_run, - skip_languages=args.skip_languages - ) - - return 0 - - -if __name__ == '__main__': - exit(main()) \ No newline at end of file diff --git a/apps/web/messages/zh-CN.json b/apps/web/messages/zh-CN.json index 6d07772..c732545 100644 --- a/apps/web/messages/zh-CN.json +++ b/apps/web/messages/zh-CN.json @@ -1413,5 +1413,117 @@ } }, "defaultLinkName": "已接收文件" + }, + "authProviders": { + "title": "身份验证提供商", + "description": "配置外部身份验证提供商以实现单点登录", + "enabledCount": "{count} 个已启用", + "loadingProviders": "正在加载提供商...", + "providersConfigured": "已配置 {count} 个提供商", + "enabledOfTotal": "共 {total} 个提供商中已启用 {enabled} 个", + "hideDisabledProviders": "隐藏已禁用的提供商", + "addProvider": "添加提供商", + "addProviderTitle": "添加提供商", + "editProvider": "编辑提供商", + "deleteProvider": "删除提供商", + "enabled": "已启用", + "disabled": "已禁用", + "officialProvider": "官方提供商", + "dragToReorder": "拖动以重新排序", + "dragDisabledMessage": "筛选提供商时拖放功能已禁用。显示所有提供商以重新排序。", + "dragEnabledMessage": "拖动提供商以重新排序。此顺序将反映在登录页面上。", + "noProvidersEnabled": "没有启用的身份验证提供商", + "noProvidersConfigured": "未配置身份验证提供商", + "form": { + "providerName": "提供商名称", + "providerNamePlaceholder": "例如:mycompany", + "displayName": "显示名称", + "displayNamePlaceholder": "例如:我的公司 SSO", + "type": "类型", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "图标", + "iconPlaceholder": "选择图标", + "clientId": "客户端 ID", + "clientIdPlaceholder": "您的 OAuth 客户端 ID", + "clientSecret": "客户端密钥", + "clientSecretPlaceholder": "您的 OAuth 客户端密钥", + "oauthScopes": "OAuth 作用域", + "scopesPlaceholder": "输入作用域(例如:openid, profile, email)", + "scopesHelpOidc": "根据提供商 URL 自动建议作用域。常见 OIDC 作用域:openid, profile, email, groups", + "scopesHelpOauth2": "根据提供商 URL 自动建议作用域。常见 OAuth2 作用域取决于提供商", + "providerUrl": "提供商 URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com(将自动发现端点)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "系统将自动发现授权、令牌和用户信息端点", + "manualConfigurationHelp": "提供商的基础 URL(端点将相对于此 URL)", + "authorizationEndpoint": "授权端点", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "令牌端点", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "用户信息端点", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "配置方法", + "autoDiscovery": "自动发现(推荐)", + "autoDiscoveryDescription": "从提供商 URL 自动发现端点", + "manualEndpoints": "手动端点", + "manualEndpointsDescription": "手动配置授权、令牌和用户信息端点", + "callbackUrl": "回调 URL", + "callbackUrlDescription": "在您的 OAuth 提供商配置中使用此 URL", + "copyCallbackUrl": "复制回调 URL", + "callbackUrlCopied": "回调 URL 已复制到剪贴板!", + "adminEmailDomains": "管理员邮箱域名", + "adminEmailDomainsPlaceholder": "输入域名(例如:admin.company.com)", + "adminEmailDomainsHelp": "来自这些域名的用户将被授予管理员权限", + "autoRegister": "自动注册新用户", + "officialProviderUrlPlaceholder": "将占位符替换为您的 {displayName} URL", + "officialProviderHelp": "这是官方提供商。端点已预配置。您只能编辑此 URL。", + "officialProviderIconHelp": "您可以自定义此官方提供商的图标。" + }, + "buttons": { + "cancel": "取消", + "save": "保存", + "saving": "保存中...", + "adding": "添加中...", + "updating": "更新中...", + "saveProvider": "保存提供商", + "delete": "删除", + "deleting": "删除中...", + "edit": "编辑", + "enable": "启用", + "disable": "禁用" + }, + "messages": { + "providerAdded": "提供商添加成功", + "providerUpdated": "提供商更新成功", + "providerDeleted": "提供商删除成功", + "providerOrderUpdated": "提供商顺序更新成功", + "fillRequiredFields": "请填写所有必填字段(名称、显示名称、客户端 ID、客户端密钥)", + "provideUrlOrEndpoints": "请提供用于自动发现的提供商 URL 或所有三个自定义端点", + "chooseDiscoveryOrManual": "请选择自动发现(提供商 URL)或手动端点,不能同时选择两者", + "loadFailed": "加载提供商失败", + "addFailed": "添加提供商失败", + "updateFailed": "更新提供商失败", + "deleteFailed": "删除提供商失败", + "orderUpdateFailed": "更新提供商顺序失败" + }, + "info": { + "title": "信息", + "officialProvidersRecommended": "为获得更好的功能,建议使用官方提供商。如果您在使用自定义提供商时遇到问题,请考虑在以下位置提出问题", + "github": "GitHub", + "officialProvider": "官方提供商", + "officialProviderDescription": "此提供商由 Palmr 优化。只能修改凭据和配置。", + "manualConfigTitle": "手动配置", + "manualConfigDescription": "您正在手动提供所有端点。请确保它们对您的提供商是正确的。" + }, + "deleteModal": { + "title": "删除身份验证提供商", + "confirmMessage": "您确定要删除'{displayName}'提供商吗?此操作无法撤消。", + "providerId": "提供商 ID:{name}", + "cancel": "取消", + "delete": "删除提供商", + "deleting": "删除中..." + } } } \ No newline at end of file diff --git a/apps/web/src/app/settings/components/auth-provider-form/add-provider-form.tsx b/apps/web/src/app/settings/components/auth-provider-form/add-provider-form.tsx new file mode 100644 index 0000000..5e404bd --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/add-provider-form.tsx @@ -0,0 +1,289 @@ +"use client"; + +import React, { useState } from "react"; +import { IconInfoCircle, IconPlus } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { IconPicker } from "@/components/ui/icon-picker"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { TagsInput } from "@/components/ui/tags-input"; +import { CallbackUrlDisplay } from "./callback-url-display"; +import { ConfigurationMethodSelector } from "./configuration-method-selector"; +import type { NewProvider } from "./types"; + +interface AddProviderFormProps { + showAddForm: boolean; + onToggleForm: () => void; + onAddProvider: (provider: NewProvider) => Promise; + saving: boolean; +} + +export function AddProviderForm({ showAddForm, onToggleForm, onAddProvider, saving }: AddProviderFormProps) { + const t = useTranslations(); + const [newProvider, setNewProvider] = useState({ + name: "", + displayName: "", + type: "oidc", + icon: "", + clientId: "", + clientSecret: "", + issuerUrl: "", + scope: "openid profile email", + authorizationEndpoint: "", + tokenEndpoint: "", + userInfoEndpoint: "", + }); + + const detectProviderTypeAndSuggestScopes = (url: string): string[] => { + if (!url) return []; + + const urlLower = url.toLowerCase(); + + const providerPatterns = [ + { pattern: "frontegg.com", scopes: ["openid", "profile", "email"] }, + { pattern: "discord.com", scopes: ["identify", "email"] }, + { pattern: "github.com", scopes: ["read:user", "user:email"] }, + { pattern: "gitlab.com", scopes: ["read_user", "read_api"] }, + { pattern: "google.com", scopes: ["openid", "profile", "email"] }, + { pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, + { pattern: "facebook.com", scopes: ["public_profile", "email"] }, + { pattern: "twitter.com", scopes: ["tweet.read", "users.read"] }, + { pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] }, + { pattern: "authentik", scopes: ["openid", "profile", "email"] }, + { pattern: "keycloak", scopes: ["openid", "profile", "email"] }, + { pattern: "auth0.com", scopes: ["openid", "profile", "email"] }, + { pattern: "okta.com", scopes: ["openid", "profile", "email"] }, + { pattern: "onelogin.com", scopes: ["openid", "profile", "email"] }, + { pattern: "pingidentity.com", scopes: ["openid", "profile", "email"] }, + { pattern: "azure.com", scopes: ["openid", "profile", "email", "User.Read"] }, + { pattern: "aws.amazon.com", scopes: ["openid", "profile", "email"] }, + { pattern: "slack.com", scopes: ["identity.basic", "identity.email", "identity.avatar"] }, + { pattern: "bitbucket.org", scopes: ["account", "repository"] }, + { pattern: "atlassian.com", scopes: ["read:jira-user", "read:jira-work"] }, + { pattern: "salesforce.com", scopes: ["api", "refresh_token"] }, + { pattern: "zendesk.com", scopes: ["read"] }, + { pattern: "shopify.com", scopes: ["read_products", "read_customers"] }, + { pattern: "stripe.com", scopes: ["read"] }, + { pattern: "twilio.com", scopes: ["read"] }, + { pattern: "sendgrid.com", scopes: ["mail.send"] }, + { pattern: "mailchimp.com", scopes: ["read"] }, + { pattern: "hubspot.com", scopes: ["contacts", "crm.objects.contacts.read"] }, + { pattern: "zoom.us", scopes: ["user:read:admin"] }, + { pattern: "teams.microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, + { pattern: "notion.so", scopes: ["read"] }, + { pattern: "figma.com", scopes: ["files:read"] }, + { pattern: "dropbox.com", scopes: ["files.content.read"] }, + { pattern: "box.com", scopes: ["root_readwrite"] }, + { pattern: "trello.com", scopes: ["read"] }, + { pattern: "asana.com", scopes: ["default"] }, + { pattern: "monday.com", scopes: ["read"] }, + { pattern: "clickup.com", scopes: ["read"] }, + { pattern: "linear.app", scopes: ["read"] }, + { pattern: "kinde.com", scopes: ["openid", "profile", "email"] }, + { pattern: "zitadel.com", scopes: ["openid", "profile", "email"] }, + ]; + + for (const { pattern, scopes } of providerPatterns) { + if (urlLower.includes(pattern)) { + return scopes; + } + } + + if (newProvider.type === "oidc") { + return ["openid", "profile", "email"]; + } else { + return ["profile", "email"]; + } + }; + + const updateProviderUrl = (url: string) => { + if (!url.trim()) return; + + const suggestedScopes = detectProviderTypeAndSuggestScopes(url); + + setNewProvider((prev) => { + const shouldUpdateScopes = !prev.scope || prev.scope === "openid profile email" || prev.scope === "profile email"; + + return { + ...prev, + scope: shouldUpdateScopes ? suggestedScopes.join(" ") : prev.scope, + }; + }); + }; + + const handleSubmit = async () => { + if (!newProvider.name || !newProvider.displayName || !newProvider.clientId || !newProvider.clientSecret) { + toast.error(t("authProviders.messages.fillRequiredFields")); + return; + } + + const hasIssuerUrl = !!newProvider.issuerUrl; + const hasAllCustomEndpoints = !!( + newProvider.authorizationEndpoint && + newProvider.tokenEndpoint && + newProvider.userInfoEndpoint + ); + + if (!hasIssuerUrl && !hasAllCustomEndpoints) { + toast.error(t("authProviders.messages.provideUrlOrEndpoints")); + return; + } + + if (hasIssuerUrl && hasAllCustomEndpoints) { + toast.error(t("authProviders.messages.chooseDiscoveryOrManual")); + return; + } + + await onAddProvider(newProvider); + + setNewProvider({ + name: "", + displayName: "", + type: "oidc", + icon: "", + clientId: "", + clientSecret: "", + issuerUrl: "", + scope: "openid profile email", + authorizationEndpoint: "", + tokenEndpoint: "", + userInfoEndpoint: "", + }); + }; + + const updateProvider = (updates: Partial) => { + setNewProvider((prev) => ({ ...prev, ...updates })); + }; + + if (!showAddForm) { + return ( + + ); + } + + return ( +
+
+

{t("authProviders.addProviderTitle")}

+
+ +
+
+ + + + {t("authProviders.info.title")} +
+

+ {t("authProviders.info.officialProvidersRecommended")}{" "} + + {t("authProviders.info.github")}. + +

+
+ +
+
+ + updateProvider({ name: e.target.value })} + /> +
+
+ + updateProvider({ displayName: e.target.value })} + /> +
+
+ +
+
+ + +
+
+ + updateProvider({ icon })} + placeholder={t("authProviders.form.iconPlaceholder")} + /> +
+
+ + + +
+
+ + updateProvider({ clientId: e.target.value })} + /> +
+
+ + updateProvider({ clientSecret: e.target.value })} + /> +
+
+ +
+ + updateProvider({ scope: tags.join(" ") })} + placeholder={t("authProviders.form.scopesPlaceholder")} + /> +

+ {newProvider.type === "oidc" + ? t("authProviders.form.scopesHelpOidc") + : t("authProviders.form.scopesHelpOauth2")} +

+
+ + {newProvider.name && ( +
+ +
+ )} + +
+ + +
+
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-delete-modal.tsx b/apps/web/src/app/settings/components/auth-provider-form/auth-provider-delete-modal.tsx similarity index 79% rename from apps/web/src/app/settings/components/auth-provider-delete-modal.tsx rename to apps/web/src/app/settings/components/auth-provider-form/auth-provider-delete-modal.tsx index 9b4a670..b1789db 100644 --- a/apps/web/src/app/settings/components/auth-provider-delete-modal.tsx +++ b/apps/web/src/app/settings/components/auth-provider-form/auth-provider-delete-modal.tsx @@ -31,20 +31,22 @@ export function AuthProviderDeleteModal({ - Delete Authentication Provider + {t("authProviders.deleteModal.title")}

- Are you sure you want to delete the "{provider?.displayName}" provider? This action cannot be undone. + {t("authProviders.deleteModal.confirmMessage", { displayName: provider?.displayName || "" })}

{provider && (

{provider.displayName}

-

Provider ID: {provider.name}

+

+ {t("authProviders.deleteModal.providerId", { name: provider.name })} +

)} @@ -52,18 +54,18 @@ export function AuthProviderDeleteModal({ diff --git a/apps/web/src/app/settings/components/auth-provider-form/auth-providers-settings.tsx b/apps/web/src/app/settings/components/auth-provider-form/auth-providers-settings.tsx new file mode 100644 index 0000000..e79213a --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/auth-providers-settings.tsx @@ -0,0 +1,158 @@ +"use client"; + +import React, { useState } from "react"; +import { IconChevronDown, IconChevronUp, IconSettings } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; + +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { renderIconByName } from "@/components/ui/icon-picker"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import { useAuthProviders } from "../../hooks/use-auth-providers"; +import { AddProviderForm } from "./add-provider-form"; +import { AuthProviderDeleteModal } from "./auth-provider-delete-modal"; +import { ProviderList } from "./provider-list"; + +export function AuthProvidersSettings() { + const t = useTranslations(); + + const [isCollapsed, setIsCollapsed] = useState(true); + const [showAddForm, setShowAddForm] = useState(false); + + const { + providers, + loading, + saving, + editingProvider, + editingFormData, + hideDisabledProviders, + providerToDelete, + isDeleting, + enabledCount, + filteredProviders, + loadProviders, + updateProvider, + addProvider, + editProvider, + deleteProvider, + handleDragEnd, + handleHideDisabledProvidersChange, + handleEditProvider, + handleDeleteProvider, + handleCancelEdit, + setEditingFormData, + setProviderToDelete, + } = useAuthProviders(); + + const getProviderIcon = (provider: any) => { + const iconName = provider.icon || "FaCog"; + return renderIconByName(iconName, "w-5 h-5"); + }; + + const handleToggleAddForm = () => { + setShowAddForm(!showAddForm); + }; + + const handleConfirmDelete = async () => { + if (providerToDelete) { + await deleteProvider(providerToDelete.id); + } + }; + + return ( + + setIsCollapsed(!isCollapsed)} + > +
+ +
+

{t("authProviders.title")}

+

+ {t("authProviders.description")} + {enabledCount > 0 && ( + + {t("authProviders.enabledCount", { count: enabledCount })} + + )} +

+
+
+ {isCollapsed ? ( + + ) : ( + + )} +
+ + + + + {loading ? ( +
+ + {t("authProviders.loadingProviders")} +
+ ) : ( +
+
+
+ {hideDisabledProviders + ? t("authProviders.enabledOfTotal", { enabled: filteredProviders.length, total: providers.length }) + : t("authProviders.providersConfigured", { count: providers.length })} +
+ + +
+ + {providers.length > 0 && ( +
+ + +
+ )} + + +
+ )} + + setProviderToDelete(null)} + isDeleting={isDeleting} + /> +
+
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/callback-url-display.tsx b/apps/web/src/app/settings/components/auth-provider-form/callback-url-display.tsx new file mode 100644 index 0000000..a769069 --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/callback-url-display.tsx @@ -0,0 +1,55 @@ +"use client"; + +import React, { useState } from "react"; +import { IconCheck, IconCopy } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; + +interface CallbackUrlDisplayProps { + providerName: string; +} + +export function CallbackUrlDisplay({ providerName }: CallbackUrlDisplayProps) { + const t = useTranslations(); + const [copied, setCopied] = useState(false); + + const callbackUrl = + typeof window !== "undefined" + ? `${window.location.origin}/api/auth/providers/${providerName}/callback` + : `/api/auth/providers/${providerName}/callback`; + + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(callbackUrl); + setCopied(true); + toast.success(t("authProviders.form.callbackUrlCopied")); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy text: ", err); + } + }; + + return ( +
+
+ +
+
{callbackUrl}
+ +
+

{t("authProviders.form.callbackUrlDescription")}

+
+
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/configuration-method-selector.tsx b/apps/web/src/app/settings/components/auth-provider-form/configuration-method-selector.tsx new file mode 100644 index 0000000..813d31d --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/configuration-method-selector.tsx @@ -0,0 +1,134 @@ +"use client"; + +import React from "react"; +import { IconInfoCircle } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { NewProvider } from "./types"; + +interface ConfigurationMethodSelectorProps { + provider: NewProvider; + onUpdate: (updates: Partial) => void; + onUrlUpdate: (url: string) => void; +} + +export function ConfigurationMethodSelector({ provider, onUpdate, onUrlUpdate }: ConfigurationMethodSelectorProps) { + const t = useTranslations(); + const isManualMode = !!(provider.authorizationEndpoint || provider.tokenEndpoint || provider.userInfoEndpoint); + + return ( +
+
+

{t("authProviders.form.configurationMethod")}

+
+
+ + onUpdate({ + authorizationEndpoint: "", + tokenEndpoint: "", + userInfoEndpoint: "", + }) + } + className="w-4 h-4" + /> + +
+
+ { + if (!isManualMode) { + onUpdate({ + authorizationEndpoint: "/oauth/authorize", + tokenEndpoint: "/oauth/token", + userInfoEndpoint: "/oauth/userinfo", + issuerUrl: "", + }); + } + }} + className="w-4 h-4" + /> + +
+
+
+ + {!isManualMode && ( +
+ + onUpdate({ issuerUrl: e.target.value })} + onBlur={(e) => onUrlUpdate(e.target.value)} + /> +

{t("authProviders.form.autoDiscoveryHelp")}

+
+ )} + + {isManualMode && ( +
+
+ + onUpdate({ issuerUrl: e.target.value })} + onBlur={(e) => onUrlUpdate(e.target.value)} + /> +

{t("authProviders.form.manualConfigurationHelp")}

+
+
+ + onUpdate({ authorizationEndpoint: e.target.value })} + /> +
+
+ + onUpdate({ tokenEndpoint: e.target.value })} + /> +
+
+ + onUpdate({ userInfoEndpoint: e.target.value })} + /> +
+
+
+ +
+

{t("authProviders.info.manualConfigTitle")}

+

{t("authProviders.info.manualConfigDescription")}

+
+
+
+
+ )} +
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/edit-provider-form.tsx b/apps/web/src/app/settings/components/auth-provider-form/edit-provider-form.tsx new file mode 100644 index 0000000..d9b18ce --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/edit-provider-form.tsx @@ -0,0 +1,423 @@ +"use client"; + +import React, { useState } from "react"; +import { IconEye, IconEyeOff, IconInfoCircle } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; + +import { Button } from "@/components/ui/button"; +import { IconPicker } from "@/components/ui/icon-picker"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { TagsInput } from "@/components/ui/tags-input"; +import { CallbackUrlDisplay } from "./callback-url-display"; + +export interface AuthProvider { + id: string; + name: string; + displayName: string; + type: string; + icon?: string; + enabled: boolean; + issuerUrl?: string; + clientId?: string; + clientSecret?: string; + scope?: string; + autoRegister: boolean; + adminEmailDomains?: string; + sortOrder: number; + isOfficial?: boolean; + authorizationEndpoint?: string; + tokenEndpoint?: string; + userInfoEndpoint?: string; +} + +interface EditProviderFormProps { + provider: AuthProvider; + onSave: (data: Partial) => void; + onCancel: () => void; + saving: boolean; + editingFormData: Record; + setEditingFormData: (data: Record) => void; +} + +export function EditProviderForm({ + provider, + onSave, + onCancel, + saving, + editingFormData, + setEditingFormData, +}: EditProviderFormProps) { + const t = useTranslations(); + const savedData = editingFormData[provider.id] || {}; + const [formData, setFormData] = useState({ + name: savedData.name || provider.name || "", + displayName: savedData.displayName || provider.displayName || "", + type: (savedData.type || provider.type) as "oidc" | "oauth2", + icon: savedData.icon || provider.icon || "FaCog", + issuerUrl: savedData.issuerUrl || provider.issuerUrl || "", + clientId: savedData.clientId || provider.clientId || "", + clientSecret: savedData.clientSecret || provider.clientSecret || "", + scope: savedData.scope || provider.scope || "", + autoRegister: savedData.autoRegister !== undefined ? savedData.autoRegister : provider.autoRegister, + adminEmailDomains: savedData.adminEmailDomains || provider.adminEmailDomains || "", + authorizationEndpoint: savedData.authorizationEndpoint || provider.authorizationEndpoint || "", + tokenEndpoint: savedData.tokenEndpoint || provider.tokenEndpoint || "", + userInfoEndpoint: savedData.userInfoEndpoint || provider.userInfoEndpoint || "", + }); + + const [showClientSecret, setShowClientSecret] = useState(false); + const isOfficial = provider.isOfficial; + + const detectProviderTypeAndSuggestScopesEdit = (url: string, currentType: string): string[] => { + if (!url) return []; + + const urlLower = url.toLowerCase(); + + const providerPatterns = [ + { pattern: "frontegg.com", scopes: ["openid", "profile", "email"] }, + { pattern: "discord.com", scopes: ["identify", "email"] }, + { pattern: "github.com", scopes: ["read:user", "user:email"] }, + { pattern: "gitlab.com", scopes: ["read_user", "read_api"] }, + { pattern: "google.com", scopes: ["openid", "profile", "email"] }, + { pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, + { pattern: "facebook.com", scopes: ["public_profile", "email"] }, + { pattern: "twitter.com", scopes: ["tweet.read", "users.read"] }, + { pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] }, + { pattern: "auth0.com", scopes: ["openid", "profile", "email"] }, + { pattern: "okta.com", scopes: ["openid", "profile", "email"] }, + { pattern: "kinde.com", scopes: ["openid", "profile", "email"] }, + { pattern: "zitadel.com", scopes: ["openid", "profile", "email"] }, + ]; + + for (const { pattern, scopes } of providerPatterns) { + if (urlLower.includes(pattern)) { + return scopes; + } + } + + if (currentType === "oidc") { + return ["openid", "profile", "email"]; + } else { + return ["profile", "email"]; + } + }; + + const updateProviderUrlEdit = (url: string) => { + if (!url.trim()) return; + + if (isOfficial) { + return; + } + + const suggestedScopes = detectProviderTypeAndSuggestScopesEdit(url, formData.type); + const shouldUpdateScopes = + !formData.scope || formData.scope === "openid profile email" || formData.scope === "profile email"; + + if (shouldUpdateScopes) { + updateFormData({ + scope: suggestedScopes.join(" "), + }); + } + }; + + const updateFormData = (updates: Partial) => { + const newFormData = { ...formData, ...updates }; + setFormData(newFormData); + + setEditingFormData({ + ...editingFormData, + [provider.id]: newFormData, + }); + }; + + const handleSubmit = () => { + onSave(formData); + }; + + const isManualMode = !!(formData.authorizationEndpoint || formData.tokenEndpoint || formData.userInfoEndpoint); + + return ( +
+ {isOfficial && ( +
+
+ + + + {t("authProviders.info.officialProvider")} +
+

+ {t("authProviders.info.officialProviderDescription")} +

+
+ )} + + + + {!isOfficial && ( +
+
+ + updateFormData({ name: e.target.value })} + /> +
+
+ + updateFormData({ displayName: e.target.value })} + /> +
+
+ )} + + {!isOfficial && ( +
+
+ + +
+
+ + updateFormData({ icon })} + placeholder={t("authProviders.form.iconPlaceholder")} + /> +
+
+ )} + + {!isOfficial && ( +
+
+

{t("authProviders.form.configurationMethod")}

+
+
+ + updateFormData({ + authorizationEndpoint: "", + tokenEndpoint: "", + userInfoEndpoint: "", + }) + } + className="w-4 h-4" + /> + +
+
+ { + if (!isManualMode) { + updateFormData({ + authorizationEndpoint: "/oauth/authorize", + tokenEndpoint: "/oauth/token", + userInfoEndpoint: "/oauth/userinfo", + }); + } + }} + className="w-4 h-4" + /> + +
+
+
+ + {!isManualMode && ( +
+ + updateFormData({ issuerUrl: e.target.value })} + onBlur={(e) => updateProviderUrlEdit(e.target.value)} + /> +

{t("authProviders.form.autoDiscoveryHelp")}

+
+ )} + + {isManualMode && ( +
+
+ + updateFormData({ issuerUrl: e.target.value })} + onBlur={(e) => updateProviderUrlEdit(e.target.value)} + /> +

{t("authProviders.form.manualConfigurationHelp")}

+
+
+ + updateFormData({ authorizationEndpoint: e.target.value })} + /> +
+
+ + updateFormData({ tokenEndpoint: e.target.value })} + /> +
+
+ + updateFormData({ userInfoEndpoint: e.target.value })} + /> +
+
+
+ +
+

{t("authProviders.info.manualConfigTitle")}

+

{t("authProviders.info.manualConfigDescription")}

+
+
+
+
+ )} +
+ )} + + {isOfficial && ( +
+
+ + updateFormData({ issuerUrl: e.target.value })} + onBlur={(e) => updateProviderUrlEdit(e.target.value)} + /> +

{t("authProviders.form.officialProviderHelp")}

+
+
+ + updateFormData({ icon })} + placeholder={t("authProviders.form.iconPlaceholder")} + /> +

{t("authProviders.form.officialProviderIconHelp")}

+
+
+ )} + +
+
+ + updateFormData({ clientId: e.target.value })} + /> +
+
+ +
+ updateFormData({ clientSecret: e.target.value })} + className="pr-10" + /> + +
+
+
+ +
+ + updateFormData({ scope: tags.join(" ") })} + placeholder={t("authProviders.form.scopesPlaceholder")} + /> +

+ {formData.type === "oidc" ? t("authProviders.form.scopesHelpOidc") : t("authProviders.form.scopesHelpOauth2")} +

+
+ +
+ + updateFormData({ adminEmailDomains: tags.join(",") })} + placeholder={t("authProviders.form.adminEmailDomainsPlaceholder")} + /> +

{t("authProviders.form.adminEmailDomainsHelp")}

+
+ +
+ updateFormData({ autoRegister: checked })} + /> + +
+ +
+ + +
+
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/provider-list.tsx b/apps/web/src/app/settings/components/auth-provider-form/provider-list.tsx new file mode 100644 index 0000000..f589200 --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/provider-list.tsx @@ -0,0 +1,127 @@ +"use client"; + +import React from "react"; +import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd"; +import { IconSettings } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; + +import { AuthProvider } from "./edit-provider-form"; +import { ProviderRow } from "./provider-row"; + +interface ProviderListProps { + providers: AuthProvider[]; + filteredProviders: AuthProvider[]; + hideDisabledProviders: boolean; + onDragEnd: (result: DropResult) => void; + onUpdateProvider: (id: string, updates: Partial) => void; + onEditProvider: (provider: AuthProvider) => void; + onDeleteProvider: (provider: AuthProvider) => void; + saving: string | null; + getIcon: (provider: AuthProvider) => React.ReactNode; + editingProvider: AuthProvider | null; + editProvider: (data: Partial) => void; + onCancelEdit: () => void; + editingFormData: Record; + setEditingFormData: (data: Record) => void; +} + +export function ProviderList({ + filteredProviders, + hideDisabledProviders, + onDragEnd, + onUpdateProvider, + onEditProvider, + onDeleteProvider, + saving, + getIcon, + editingProvider, + editProvider, + onCancelEdit, + editingFormData, + setEditingFormData, +}: ProviderListProps) { + const t = useTranslations(); + + if (filteredProviders.length === 0) { + return ( +
+ +

+ {hideDisabledProviders ? t("authProviders.noProvidersEnabled") : t("authProviders.noProvidersConfigured")} +

+
+ ); + } + + return ( +
+
+

+ {hideDisabledProviders ? t("authProviders.dragDisabledMessage") : t("authProviders.dragEnabledMessage")} +

+
+ + {!hideDisabledProviders ? ( + + + {(provided) => ( +
+ {filteredProviders.map((provider, index) => ( + + {(provided, snapshot) => ( +
+ onUpdateProvider(provider.id, updates)} + onEdit={() => onEditProvider(provider)} + onDelete={() => onDeleteProvider(provider)} + saving={saving === provider.id} + getIcon={getIcon} + editingProvider={editingProvider} + editProvider={editProvider} + onCancelEdit={onCancelEdit} + editingFormData={editingFormData} + setEditingFormData={setEditingFormData} + dragHandleProps={provided.dragHandleProps} + isDragging={snapshot.isDragging} + isDragDisabled={false} + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ ) : ( +
+ {filteredProviders.map((provider) => ( + onUpdateProvider(provider.id, updates)} + onEdit={() => onEditProvider(provider)} + onDelete={() => onDeleteProvider(provider)} + saving={saving === provider.id} + getIcon={getIcon} + editingProvider={editingProvider} + editProvider={editProvider} + onCancelEdit={onCancelEdit} + editingFormData={editingFormData} + setEditingFormData={setEditingFormData} + dragHandleProps={null} + isDragging={false} + isDragDisabled={true} + /> + ))} +
+ )} +
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/provider-row.tsx b/apps/web/src/app/settings/components/auth-provider-form/provider-row.tsx new file mode 100644 index 0000000..4a011dc --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/provider-row.tsx @@ -0,0 +1,118 @@ +"use client"; + +import React from "react"; +import { IconEdit, IconGripVertical, IconTrash } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; + +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { AuthProvider, EditProviderForm } from "./edit-provider-form"; + +interface ProviderRowProps { + provider: AuthProvider; + onUpdate: (updates: Partial) => void; + onEdit: () => void; + onDelete: () => void; + saving: boolean; + getIcon: (provider: AuthProvider) => React.ReactNode; + editingProvider: AuthProvider | null; + editProvider: (data: Partial) => void; + onCancelEdit: () => void; + editingFormData: Record; + setEditingFormData: (data: Record) => void; + dragHandleProps: any; + isDragging: boolean; + isDragDisabled: boolean; +} + +export function ProviderRow({ + provider, + onUpdate, + onEdit, + onDelete, + saving, + getIcon, + editingProvider, + editProvider, + onCancelEdit, + editingFormData, + setEditingFormData, + dragHandleProps, + isDragging, + isDragDisabled, +}: ProviderRowProps) { + const t = useTranslations(); + const isEditing = editingProvider?.id === provider.id; + + return ( +
+
+
+ {!isDragDisabled ? ( +
+ +
+ ) : null} + + {getIcon(provider)} +
+
+
+ {provider.displayName} +
+
+ {provider.type.toUpperCase()} • {provider.name} + {provider.isOfficial && ( + • {t("authProviders.officialProvider")} + )} +
+
+
+
+ onUpdate({ enabled })} disabled={saving} /> + + {!provider.isOfficial && ( + + )} +
+
+ + {isEditing && ( +
+
+

+ Editar Provider: {provider.displayName} +

+
+ +
+ )} +
+ ); +} diff --git a/apps/web/src/app/settings/components/auth-provider-form/types.ts b/apps/web/src/app/settings/components/auth-provider-form/types.ts new file mode 100644 index 0000000..53067ed --- /dev/null +++ b/apps/web/src/app/settings/components/auth-provider-form/types.ts @@ -0,0 +1,33 @@ +export interface AuthProvider { + id: string; + name: string; + displayName: string; + type: string; + icon?: string; + enabled: boolean; + issuerUrl?: string; + clientId?: string; + clientSecret?: string; + scope?: string; + autoRegister: boolean; + adminEmailDomains?: string; + sortOrder: number; + isOfficial?: boolean; + authorizationEndpoint?: string; + tokenEndpoint?: string; + userInfoEndpoint?: string; +} + +export interface NewProvider { + name: string; + displayName: string; + type: "oidc" | "oauth2"; + icon: string; + clientId: string; + clientSecret: string; + issuerUrl: string; + scope: string; + authorizationEndpoint: string; + tokenEndpoint: string; + userInfoEndpoint: string; +} diff --git a/apps/web/src/app/settings/components/auth-providers-settings.tsx b/apps/web/src/app/settings/components/auth-providers-settings.tsx deleted file mode 100644 index 055a1eb..0000000 --- a/apps/web/src/app/settings/components/auth-providers-settings.tsx +++ /dev/null @@ -1,1412 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd"; -import { - IconCheck, - IconChevronDown, - IconChevronUp, - IconCopy, - IconEdit, - IconEye, - IconEyeOff, - IconGripVertical, - IconInfoCircle, - IconPlus, - IconSettings, - IconTrash, -} from "@tabler/icons-react"; -import { Globe } from "lucide-react"; -import { toast } from "sonner"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { IconPicker, renderIconByName } from "@/components/ui/icon-picker"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { Switch } from "@/components/ui/switch"; -import { TagsInput } from "@/components/ui/tags-input"; -import { AuthProviderDeleteModal } from "./auth-provider-delete-modal"; - -interface AuthProvider { - id: string; - name: string; - displayName: string; - type: string; - icon?: string; - enabled: boolean; - issuerUrl?: string; - clientId?: string; - clientSecret?: string; - scope?: string; - autoRegister: boolean; - adminEmailDomains?: string; - sortOrder: number; - isOfficial?: boolean; - authorizationEndpoint?: string; - tokenEndpoint?: string; - userInfoEndpoint?: string; -} - -interface NewProvider { - name: string; - displayName: string; - type: "oidc" | "oauth2"; - icon: string; - clientId: string; - clientSecret: string; - issuerUrl: string; - scope: string; - authorizationEndpoint: string; - tokenEndpoint: string; - userInfoEndpoint: string; -} - -export function AuthProvidersSettings() { - const [providers, setProviders] = useState([]); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(null); - const [isCollapsed, setIsCollapsed] = useState(true); - const [showAddForm, setShowAddForm] = useState(false); - const [editingProvider, setEditingProvider] = useState(null); - const [editingFormData, setEditingFormData] = useState>({}); - const [hideDisabledProviders, setHideDisabledProviders] = useState(false); - const [providerToDelete, setProviderToDelete] = useState<{ id: string; name: string; displayName: string } | null>( - null - ); - const [isDeleting, setIsDeleting] = useState(false); - - const [newProvider, setNewProvider] = useState({ - name: "", - displayName: "", - type: "oidc", - icon: "", - clientId: "", - clientSecret: "", - issuerUrl: "", - scope: "openid profile email", - authorizationEndpoint: "", - tokenEndpoint: "", - userInfoEndpoint: "", - }); - - const detectProviderTypeAndSuggestScopes = (url: string): string[] => { - if (!url) return []; - - const urlLower = url.toLowerCase(); - - const providerPatterns = [ - { pattern: "frontegg.com", scopes: ["openid", "profile", "email"] }, - { pattern: "discord.com", scopes: ["identify", "email"] }, - { pattern: "github.com", scopes: ["read:user", "user:email"] }, - { pattern: "gitlab.com", scopes: ["read_user", "read_api"] }, - { pattern: "google.com", scopes: ["openid", "profile", "email"] }, - { pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, - { pattern: "facebook.com", scopes: ["public_profile", "email"] }, - { pattern: "twitter.com", scopes: ["tweet.read", "users.read"] }, - { pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] }, - { pattern: "authentik", scopes: ["openid", "profile", "email"] }, - { pattern: "keycloak", scopes: ["openid", "profile", "email"] }, - { pattern: "auth0.com", scopes: ["openid", "profile", "email"] }, - { pattern: "okta.com", scopes: ["openid", "profile", "email"] }, - { pattern: "onelogin.com", scopes: ["openid", "profile", "email"] }, - { pattern: "pingidentity.com", scopes: ["openid", "profile", "email"] }, - { pattern: "azure.com", scopes: ["openid", "profile", "email", "User.Read"] }, - { pattern: "aws.amazon.com", scopes: ["openid", "profile", "email"] }, - { pattern: "slack.com", scopes: ["identity.basic", "identity.email", "identity.avatar"] }, - { pattern: "bitbucket.org", scopes: ["account", "repository"] }, - { pattern: "atlassian.com", scopes: ["read:jira-user", "read:jira-work"] }, - { pattern: "salesforce.com", scopes: ["api", "refresh_token"] }, - { pattern: "zendesk.com", scopes: ["read"] }, - { pattern: "shopify.com", scopes: ["read_products", "read_customers"] }, - { pattern: "stripe.com", scopes: ["read"] }, - { pattern: "twilio.com", scopes: ["read"] }, - { pattern: "sendgrid.com", scopes: ["mail.send"] }, - { pattern: "mailchimp.com", scopes: ["read"] }, - { pattern: "hubspot.com", scopes: ["contacts", "crm.objects.contacts.read"] }, - { pattern: "zoom.us", scopes: ["user:read:admin"] }, - { pattern: "teams.microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, - { pattern: "notion.so", scopes: ["read"] }, - { pattern: "figma.com", scopes: ["files:read"] }, - { pattern: "dropbox.com", scopes: ["files.content.read"] }, - { pattern: "box.com", scopes: ["root_readwrite"] }, - { pattern: "trello.com", scopes: ["read"] }, - { pattern: "asana.com", scopes: ["default"] }, - { pattern: "monday.com", scopes: ["read"] }, - { pattern: "clickup.com", scopes: ["read"] }, - { pattern: "linear.app", scopes: ["read"] }, - { pattern: "kinde.com", scopes: ["openid", "profile", "email"] }, - { pattern: "zitadel.com", scopes: ["openid", "profile", "email"] }, - ]; - - for (const { pattern, scopes } of providerPatterns) { - if (urlLower.includes(pattern)) { - return scopes; - } - } - - if (newProvider.type === "oidc") { - return ["openid", "profile", "email"]; - } else { - return ["profile", "email"]; - } - }; - - const updateProviderUrl = (url: string) => { - if (!url.trim()) return; - - const suggestedScopes = detectProviderTypeAndSuggestScopes(url); - - setNewProvider((prev) => { - const shouldUpdateScopes = !prev.scope || prev.scope === "openid profile email" || prev.scope === "profile email"; - - return { - ...prev, - scope: shouldUpdateScopes ? suggestedScopes.join(" ") : prev.scope, - }; - }); - }; - - useEffect(() => { - const savedState = localStorage.getItem("hideDisabledProviders"); - if (savedState !== null) { - setHideDisabledProviders(JSON.parse(savedState)); - } - }, []); - - useEffect(() => { - loadProviders(); - }, []); - - const loadProviders = async () => { - try { - setLoading(true); - const response = await fetch("/api/auth/providers/all"); - const data = await response.json(); - - if (data.success) { - setProviders(data.data.sort((a: AuthProvider, b: AuthProvider) => a.sortOrder - b.sortOrder)); - } else { - toast.error("Failed to load providers"); - } - } catch (error) { - console.error("Error loading providers:", error); - toast.error("Failed to load providers"); - } finally { - setLoading(false); - } - }; - - const updateProvider = async (id: string, updates: Partial) => { - try { - setSaving(id); - const response = await fetch(`/api/auth/providers/manage/${id}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(updates), - }); - - const data = await response.json(); - - if (data.success) { - setProviders((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p))); - toast.success("Provider updated"); - } else { - toast.error("Failed to update provider"); - } - } catch (error) { - console.error("Error updating provider:", error); - toast.error("Failed to update provider"); - } finally { - setSaving(null); - } - }; - - const deleteProvider = async (id: string, name: string) => { - try { - setIsDeleting(true); - const response = await fetch(`/api/auth/providers/manage/${id}`, { - method: "DELETE", - }); - - const data = await response.json(); - - if (data.success) { - setProviders((prev) => prev.filter((p) => p.id !== id)); - toast.success("Provider deleted"); - setProviderToDelete(null); - } else { - toast.error("Failed to delete provider"); - } - } catch (error) { - console.error("Error deleting provider:", error); - toast.error("Failed to delete provider"); - } finally { - setIsDeleting(false); - } - }; - - const openDeleteModal = (provider: AuthProvider) => { - setProviderToDelete({ - id: provider.id, - name: provider.name, - displayName: provider.displayName, - }); - }; - - const addProvider = async () => { - if (!newProvider.name || !newProvider.displayName || !newProvider.clientId || !newProvider.clientSecret) { - toast.error("Please fill in all required fields (name, display name, client ID, client secret)"); - return; - } - - const hasIssuerUrl = !!newProvider.issuerUrl; - const hasAllCustomEndpoints = !!( - newProvider.authorizationEndpoint && - newProvider.tokenEndpoint && - newProvider.userInfoEndpoint - ); - - if (!hasIssuerUrl && !hasAllCustomEndpoints) { - toast.error("Either provide a Provider URL for automatic discovery OR all three custom endpoints"); - return; - } - - if (hasIssuerUrl && hasAllCustomEndpoints) { - toast.error("Choose either automatic discovery (Provider URL) OR manual endpoints, not both"); - return; - } - - try { - setSaving("new"); - const response = await fetch("/api/auth/providers", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: newProvider.name.toLowerCase().replace(/\s+/g, "-"), - displayName: newProvider.displayName, - type: newProvider.type, - icon: newProvider.icon, - clientId: newProvider.clientId, - clientSecret: newProvider.clientSecret, - enabled: false, - autoRegister: true, - scope: newProvider.scope || (newProvider.type === "oidc" ? "openid profile email" : "user:email"), - sortOrder: providers.length + 1, - ...(newProvider.issuerUrl ? { issuerUrl: newProvider.issuerUrl } : {}), - ...(newProvider.authorizationEndpoint ? { authorizationEndpoint: newProvider.authorizationEndpoint } : {}), - ...(newProvider.tokenEndpoint ? { tokenEndpoint: newProvider.tokenEndpoint } : {}), - ...(newProvider.userInfoEndpoint ? { userInfoEndpoint: newProvider.userInfoEndpoint } : {}), - }), - }); - - const data = await response.json(); - - if (data.success) { - await loadProviders(); - setNewProvider({ - name: "", - displayName: "", - type: "oidc", - icon: "", - clientId: "", - clientSecret: "", - issuerUrl: "", - scope: "openid profile email", - authorizationEndpoint: "", - tokenEndpoint: "", - userInfoEndpoint: "", - }); - setShowAddForm(false); - toast.success("Provider added"); - } else { - toast.error("Failed to add provider"); - } - } catch (error) { - console.error("Error adding provider:", error); - toast.error("Failed to add provider"); - } finally { - setSaving(null); - } - }; - - const editProvider = async (providerData: Partial) => { - if (!editingProvider || !providerData.name || !providerData.displayName) { - toast.error("Please fill in all required fields"); - return; - } - - try { - setSaving(editingProvider.id); - const response = await fetch(`/api/auth/providers/manage/${editingProvider.id}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - ...providerData, - name: providerData.name?.toLowerCase().replace(/\s+/g, "-"), - }), - }); - - const data = await response.json(); - - if (data.success) { - await loadProviders(); - setEditingProvider(null); - toast.success("Provider updated"); - } else { - toast.error("Failed to update provider"); - } - } catch (error) { - console.error("Error updating provider:", error); - toast.error("Failed to update provider"); - } finally { - setSaving(null); - } - }; - - const handleDragEnd = async (result: DropResult) => { - if (!result.destination) return; - - const items = Array.from(providers); - const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); - - const updatedItems = items.map((provider, index) => ({ - ...provider, - sortOrder: index + 1, - })); - setProviders(updatedItems); - - const updatedProviders = updatedItems.map((provider) => ({ - id: provider.id, - sortOrder: provider.sortOrder, - })); - - try { - const response = await fetch("/api/auth/providers/order", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ providers: updatedProviders }), - }); - - const data = await response.json(); - - if (data.success) { - toast.success("Provider order updated"); - } else { - toast.error("Failed to update provider order"); - await loadProviders(); - } - } catch (error) { - console.error("Error updating provider order:", error); - toast.error("Failed to update provider order"); - await loadProviders(); - } - }; - - const getProviderIcon = (provider: AuthProvider) => { - const iconName = provider.icon || "FaCog"; - return renderIconByName(iconName, "w-5 h-5"); - }; - - const handleHideDisabledProvidersChange = (checked: boolean) => { - setHideDisabledProviders(checked); - localStorage.setItem("hideDisabledProviders", JSON.stringify(checked)); - }; - - const enabledCount = providers.filter((p) => p.enabled).length; - const filteredProviders = hideDisabledProviders ? providers.filter((p) => p.enabled) : providers; - - return ( - - setIsCollapsed(!isCollapsed)} - > -
- -
-

Authentication Providers

-

- Configure external authentication providers for SSO - {enabledCount > 0 && ( - - {enabledCount} enabled - - )} -

-
-
- {isCollapsed ? ( - - ) : ( - - )} -
- - - - - {loading ? ( -
- - Loading providers... -
- ) : ( -
-
-
- {hideDisabledProviders - ? `${filteredProviders.length} enabled of ${providers.length} providers` - : `${providers.length} providers configured`} -
- -
- - {showAddForm && ( -
-
-

Add Provider

-
-
-
- - - - Informação -
-

- Para melhor funcionamento, considere usar os providers oficiais. Caso tenha problemas com um - provider customizado, considere abrir uma issue no{" "} - - GitHub. - -

-
-
-
- - setNewProvider((prev) => ({ ...prev, name: e.target.value }))} - /> -
-
- - setNewProvider((prev) => ({ ...prev, displayName: e.target.value }))} - /> -
-
-
-
- - -
-
- - setNewProvider((prev) => ({ ...prev, icon }))} - placeholder="Select an icon" - /> -
-
- -
-
-

Configuration Method

-
-
- - setNewProvider((prev) => ({ - ...prev, - authorizationEndpoint: "", - tokenEndpoint: "", - userInfoEndpoint: "", - })) - } - className="w-4 h-4" - /> - -
-
- { - if ( - !newProvider.authorizationEndpoint && - !newProvider.tokenEndpoint && - !newProvider.userInfoEndpoint - ) { - setNewProvider((prev) => ({ - ...prev, - authorizationEndpoint: "/oauth/authorize", - tokenEndpoint: "/oauth/token", - userInfoEndpoint: "/oauth/userinfo", - issuerUrl: "", - })); - } - }} - className="w-4 h-4" - /> - -
-
-
- - {!newProvider.authorizationEndpoint && - !newProvider.tokenEndpoint && - !newProvider.userInfoEndpoint && ( -
- - setNewProvider((prev) => ({ ...prev, issuerUrl: e.target.value }))} - onBlur={(e) => updateProviderUrl(e.target.value)} - /> -

- The system will automatically discover authorization, token, and userinfo endpoints -

-
- )} - - {(newProvider.authorizationEndpoint || newProvider.tokenEndpoint || newProvider.userInfoEndpoint) && ( -
-
- - setNewProvider((prev) => ({ ...prev, issuerUrl: e.target.value }))} - onBlur={(e) => updateProviderUrl(e.target.value)} - /> -

- Base URL of your provider (endpoints will be relative to this) -

-
-
- - - setNewProvider((prev) => ({ ...prev, authorizationEndpoint: e.target.value })) - } - /> -
-
- - setNewProvider((prev) => ({ ...prev, tokenEndpoint: e.target.value }))} - /> -
-
- - setNewProvider((prev) => ({ ...prev, userInfoEndpoint: e.target.value }))} - /> -
-
-
- -
-

Manual Configuration

-

- You're providing all endpoints manually. Make sure they're correct for your provider. -

-
-
-
-
- )} -
- -
-
- - setNewProvider((prev) => ({ ...prev, clientId: e.target.value }))} - /> -
-
- - setNewProvider((prev) => ({ ...prev, clientSecret: e.target.value }))} - /> -
-
- -
- - setNewProvider((prev) => ({ ...prev, scope: tags.join(" ") }))} - placeholder="Enter scopes (e.g., openid, profile, email)" - /> -

- {newProvider.type === "oidc" - ? "Scopes auto-suggested based on Provider URL. Common OIDC scopes: openid, profile, email, groups" - : "Scopes auto-suggested based on Provider URL. Common OAuth2 scopes depend on the provider"} -

-
- - {newProvider.name && ( -
- -
- )} -
- - -
-
- )} - - {providers.length > 0 && ( -
- - -
- )} - -
-
-

- {hideDisabledProviders - ? "Drag and drop is disabled when filtering providers. Show all providers to reorder them." - : "Drag providers to reorder them. This order will be reflected on the login page."} -

-
- - {!hideDisabledProviders ? ( - - - {(provided) => ( -
- {filteredProviders.map((provider, index) => ( - - {(provided, snapshot) => ( -
- updateProvider(provider.id, updates)} - onEdit={() => { - if (showAddForm) { - setShowAddForm(false); - } - if (editingProvider?.id === provider.id) { - setEditingProvider(null); - } else { - setEditingProvider(provider); - } - }} - onDelete={() => openDeleteModal(provider)} - saving={saving === provider.id} - getIcon={getProviderIcon} - editingProvider={editingProvider} - editProvider={editProvider} - onCancelEdit={() => { - setEditingProvider(null); - setEditingFormData({}); - }} - editingFormData={editingFormData} - setEditingFormData={setEditingFormData} - dragHandleProps={provided.dragHandleProps} - isDragging={snapshot.isDragging} - isDragDisabled={false} - /> -
- )} -
- ))} - {provided.placeholder} -
- )} -
-
- ) : ( -
- {filteredProviders.map((provider) => ( - updateProvider(provider.id, updates)} - onEdit={() => { - if (showAddForm) { - setShowAddForm(false); - } - if (editingProvider?.id === provider.id) { - setEditingProvider(null); - } else { - setEditingProvider(provider); - } - }} - onDelete={() => openDeleteModal(provider)} - saving={saving === provider.id} - getIcon={getProviderIcon} - editingProvider={editingProvider} - editProvider={editProvider} - onCancelEdit={() => { - setEditingProvider(null); - setEditingFormData({}); - }} - editingFormData={editingFormData} - setEditingFormData={setEditingFormData} - dragHandleProps={null} - isDragging={false} - isDragDisabled={true} - /> - ))} -
- )} -
- - {filteredProviders.length === 0 && !showAddForm && ( -
- -

- {hideDisabledProviders - ? "No enabled authentication providers" - : "No authentication providers configured"} -

-
- )} -
- )} -
- - setProviderToDelete(null)} - provider={providerToDelete} - onConfirm={async () => { - if (providerToDelete) { - await deleteProvider(providerToDelete.id, providerToDelete.name); - } - }} - isDeleting={isDeleting} - /> -
- ); -} - -interface ProviderRowProps { - provider: AuthProvider; - onUpdate: (updates: Partial) => void; - onEdit: () => void; - onDelete: () => void; - saving: boolean; - getIcon: (provider: AuthProvider) => React.ReactNode; - editingProvider: AuthProvider | null; - editProvider: (data: Partial) => void; - onCancelEdit: () => void; - editingFormData: Record; - setEditingFormData: (data: Record) => void; - dragHandleProps: any; - isDragging: boolean; - isDragDisabled: boolean; -} - -function ProviderRow({ - provider, - onUpdate, - onEdit, - onDelete, - saving, - getIcon, - editingProvider, - editProvider, - onCancelEdit, - editingFormData, - setEditingFormData, - dragHandleProps, - isDragging, - isDragDisabled, -}: ProviderRowProps) { - const isEditing = editingProvider?.id === provider.id; - - return ( -
-
-
- {!isDragDisabled ? ( -
- -
- ) : null} - - {getIcon(provider)} -
-
-
- {provider.displayName} -
-
- {provider.type.toUpperCase()} • {provider.name} - {provider.isOfficial && • Official Provider} -
-
-
-
- onUpdate({ enabled })} disabled={saving} /> - - {!provider.isOfficial && ( - - )} -
-
- - {isEditing && ( -
-
-

- Editar Provider: {provider.displayName} -

-
- -
- )} -
- ); -} - -interface EditProviderFormProps { - provider: AuthProvider; - onSave: (data: Partial) => void; - onCancel: () => void; - saving: boolean; - editingFormData: Record; - setEditingFormData: (data: Record) => void; -} - -function EditProviderForm({ - provider, - onSave, - onCancel, - saving, - editingFormData, - setEditingFormData, -}: EditProviderFormProps) { - const savedData = editingFormData[provider.id] || {}; - const [formData, setFormData] = useState({ - name: savedData.name || provider.name || "", - displayName: savedData.displayName || provider.displayName || "", - type: (savedData.type || provider.type) as "oidc" | "oauth2", - icon: savedData.icon || provider.icon || "FaCog", - issuerUrl: savedData.issuerUrl || provider.issuerUrl || "", - clientId: savedData.clientId || provider.clientId || "", - clientSecret: savedData.clientSecret || provider.clientSecret || "", - scope: savedData.scope || provider.scope || "", - autoRegister: savedData.autoRegister !== undefined ? savedData.autoRegister : provider.autoRegister, - adminEmailDomains: savedData.adminEmailDomains || provider.adminEmailDomains || "", - authorizationEndpoint: savedData.authorizationEndpoint || provider.authorizationEndpoint || "", - tokenEndpoint: savedData.tokenEndpoint || provider.tokenEndpoint || "", - userInfoEndpoint: savedData.userInfoEndpoint || provider.userInfoEndpoint || "", - }); - - const [showClientSecret, setShowClientSecret] = useState(false); - const isOfficial = provider.isOfficial; - - const detectProviderTypeAndSuggestScopesEdit = (url: string, currentType: string): string[] => { - if (!url) return []; - - const urlLower = url.toLowerCase(); - - const providerPatterns = [ - { pattern: "frontegg.com", scopes: ["openid", "profile", "email"] }, - { pattern: "discord.com", scopes: ["identify", "email"] }, - { pattern: "github.com", scopes: ["read:user", "user:email"] }, - { pattern: "gitlab.com", scopes: ["read_user", "read_api"] }, - { pattern: "google.com", scopes: ["openid", "profile", "email"] }, - { pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] }, - { pattern: "facebook.com", scopes: ["public_profile", "email"] }, - { pattern: "twitter.com", scopes: ["tweet.read", "users.read"] }, - { pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] }, - { pattern: "auth0.com", scopes: ["openid", "profile", "email"] }, - { pattern: "okta.com", scopes: ["openid", "profile", "email"] }, - { pattern: "kinde.com", scopes: ["openid", "profile", "email"] }, - { pattern: "zitadel.com", scopes: ["openid", "profile", "email"] }, - ]; - - for (const { pattern, scopes } of providerPatterns) { - if (urlLower.includes(pattern)) { - return scopes; - } - } - - if (currentType === "oidc") { - return ["openid", "profile", "email"]; - } else { - return ["profile", "email"]; - } - }; - - const updateProviderUrlEdit = (url: string) => { - if (!url.trim()) return; - - if (isOfficial) { - return; - } - - const suggestedScopes = detectProviderTypeAndSuggestScopesEdit(url, formData.type); - const shouldUpdateScopes = - !formData.scope || formData.scope === "openid profile email" || formData.scope === "profile email"; - - if (shouldUpdateScopes) { - updateFormData({ - scope: suggestedScopes.join(" "), - }); - } - }; - - const updateFormData = (updates: Partial) => { - const newFormData = { ...formData, ...updates }; - setFormData(newFormData); - - setEditingFormData({ - ...editingFormData, - [provider.id]: newFormData, - }); - }; - - const handleSubmit = () => { - onSave(formData); - }; - - return ( -
- {isOfficial && ( -
-
- - - - Official Provider -
-

- This provider is optimized by Palmr. Only credentials and configuration can be modified. -

-
- )} - - - {!isOfficial && ( -
-
- - updateFormData({ name: e.target.value })} - /> -
-
- - updateFormData({ displayName: e.target.value })} - /> -
-
- )} - - {!isOfficial && ( -
-
- - -
-
- - updateFormData({ icon })} - placeholder="Select an icon" - /> -
-
- )} - - {!isOfficial && ( -
-
-

Configuration Method

-
-
- - updateFormData({ - authorizationEndpoint: "", - tokenEndpoint: "", - userInfoEndpoint: "", - }) - } - className="w-4 h-4" - /> - -
-
- { - if (!formData.authorizationEndpoint && !formData.tokenEndpoint && !formData.userInfoEndpoint) { - updateFormData({ - authorizationEndpoint: "/oauth/authorize", - tokenEndpoint: "/oauth/token", - userInfoEndpoint: "/oauth/userinfo", - }); - } - }} - className="w-4 h-4" - /> - -
-
-
- - {!formData.authorizationEndpoint && !formData.tokenEndpoint && !formData.userInfoEndpoint && ( -
- - updateFormData({ issuerUrl: e.target.value })} - onBlur={(e) => updateProviderUrlEdit(e.target.value)} - /> -

- The system will automatically discover authorization, token, and userinfo endpoints -

-
- )} - - {(formData.authorizationEndpoint || formData.tokenEndpoint || formData.userInfoEndpoint) && ( -
-
- - updateFormData({ issuerUrl: e.target.value })} - onBlur={(e) => updateProviderUrlEdit(e.target.value)} - /> -

- Base URL of your provider (endpoints will be relative to this) -

-
-
- - updateFormData({ authorizationEndpoint: e.target.value })} - /> -
-
- - updateFormData({ tokenEndpoint: e.target.value })} - /> -
-
- - updateFormData({ userInfoEndpoint: e.target.value })} - /> -
-
-
- -
-

Manual Configuration

-

- You're providing all endpoints manually. Make sure they're correct for your provider. -

-
-
-
-
- )} -
- )} - - {isOfficial && ( -
-
- - updateFormData({ issuerUrl: e.target.value })} - onBlur={(e) => updateProviderUrlEdit(e.target.value)} - /> -

- This is an official provider. Endpoints are pre-configured. You can edit just this URL. -

-
-
- - updateFormData({ icon })} - placeholder="Select an icon" - /> -

You can customize the icon for this official provider.

-
-
- )} - -
-
- - updateFormData({ clientId: e.target.value })} - /> -
-
- -
- updateFormData({ clientSecret: e.target.value })} - className="pr-10" - /> - -
-
-
- -
- - updateFormData({ scope: tags.join(" ") })} - placeholder="Enter scopes (e.g., openid, profile, email)" - /> -

- {formData.type === "oidc" - ? "Scopes auto-suggested based on Provider URL. Common OIDC scopes: openid, profile, email, groups" - : "Scopes auto-suggested based on Provider URL. Common OAuth2 scopes depend on the provider"} -

-
- -
- - updateFormData({ adminEmailDomains: tags.join(",") })} - placeholder="Enter domains (e.g., admin.company.com)" - /> -

- Users with emails from these domains will be granted admin privileges -

-
- -
- updateFormData({ autoRegister: checked })} - /> - -
- -
- - -
-
- ); -} - -interface CallbackUrlDisplayProps { - providerName: string; -} - -function CallbackUrlDisplay({ providerName }: CallbackUrlDisplayProps) { - const [copied, setCopied] = useState(false); - - const callbackUrl = - typeof window !== "undefined" - ? `${window.location.origin}/api/auth/providers/${providerName}/callback` - : `/api/auth/providers/${providerName}/callback`; - - const copyToClipboard = async () => { - try { - await navigator.clipboard.writeText(callbackUrl); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch (err) { - console.error("Failed to copy text: ", err); - } - }; - - return ( -
-
- -
-
{callbackUrl}
- -
-

Use esta URL na configuração do seu provedor OAuth2/OIDC

-
-
- ); -} diff --git a/apps/web/src/app/settings/components/settings-form.tsx b/apps/web/src/app/settings/components/settings-form.tsx index 53abec4..5aefe3b 100644 --- a/apps/web/src/app/settings/components/settings-form.tsx +++ b/apps/web/src/app/settings/components/settings-form.tsx @@ -1,5 +1,5 @@ import { SettingsFormProps, ValidGroup } from "../types"; -import { AuthProvidersSettings } from "./auth-providers-settings"; +import { AuthProvidersSettings } from "./auth-provider-form/auth-providers-settings"; import { SettingsGroup } from "./settings-group"; const GROUP_ORDER: string[] = ["general", "email", "auth-providers", "security", "storage"]; diff --git a/apps/web/src/app/settings/hooks/use-auth-providers.ts b/apps/web/src/app/settings/hooks/use-auth-providers.ts new file mode 100644 index 0000000..56e6d78 --- /dev/null +++ b/apps/web/src/app/settings/hooks/use-auth-providers.ts @@ -0,0 +1,281 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { DropResult } from "@hello-pangea/dnd"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import type { AuthProvider, NewProvider } from "../components/auth-provider-form/types"; + +export function useAuthProviders() { + const t = useTranslations(); + const [providers, setProviders] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(null); + const [editingProvider, setEditingProvider] = useState(null); + const [editingFormData, setEditingFormData] = useState>({}); + const [hideDisabledProviders, setHideDisabledProviders] = useState(false); + const [providerToDelete, setProviderToDelete] = useState<{ + id: string; + name: string; + displayName: string; + } | null>(null); + const [isDeleting, setIsDeleting] = useState(false); + + // Load initial state from localStorage + useEffect(() => { + const savedState = localStorage.getItem("hideDisabledProviders"); + if (savedState !== null) { + setHideDisabledProviders(JSON.parse(savedState)); + } + }, []); + + // Load providers on mount + useEffect(() => { + loadProviders(); + }, []); + + const loadProviders = async () => { + try { + setLoading(true); + const response = await fetch("/api/auth/providers/all"); + const data = await response.json(); + + if (data.success) { + setProviders(data.data.sort((a: AuthProvider, b: AuthProvider) => a.sortOrder - b.sortOrder)); + } else { + toast.error(t("authProviders.messages.loadFailed")); + } + } catch (error) { + console.error("Error loading providers:", error); + toast.error(t("authProviders.messages.loadFailed")); + } finally { + setLoading(false); + } + }; + + const updateProvider = async (id: string, updates: Partial) => { + try { + setSaving(id); + const response = await fetch(`/api/auth/providers/manage/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(updates), + }); + + const data = await response.json(); + + if (data.success) { + setProviders((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p))); + toast.success(t("authProviders.messages.providerUpdated")); + } else { + toast.error(t("authProviders.messages.updateFailed")); + } + } catch (error) { + console.error("Error updating provider:", error); + toast.error(t("authProviders.messages.updateFailed")); + } finally { + setSaving(null); + } + }; + + const addProvider = async (newProvider: NewProvider) => { + try { + setSaving("new"); + const response = await fetch("/api/auth/providers", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: newProvider.name.toLowerCase().replace(/\s+/g, "-"), + displayName: newProvider.displayName, + type: newProvider.type, + icon: newProvider.icon, + clientId: newProvider.clientId, + clientSecret: newProvider.clientSecret, + enabled: false, + autoRegister: true, + scope: newProvider.scope || (newProvider.type === "oidc" ? "openid profile email" : "user:email"), + sortOrder: providers.length + 1, + ...(newProvider.issuerUrl ? { issuerUrl: newProvider.issuerUrl } : {}), + ...(newProvider.authorizationEndpoint ? { authorizationEndpoint: newProvider.authorizationEndpoint } : {}), + ...(newProvider.tokenEndpoint ? { tokenEndpoint: newProvider.tokenEndpoint } : {}), + ...(newProvider.userInfoEndpoint ? { userInfoEndpoint: newProvider.userInfoEndpoint } : {}), + }), + }); + + const data = await response.json(); + + if (data.success) { + await loadProviders(); + toast.success(t("authProviders.messages.providerAdded")); + } else { + toast.error(t("authProviders.messages.addFailed")); + } + } catch (error) { + console.error("Error adding provider:", error); + toast.error(t("authProviders.messages.addFailed")); + } finally { + setSaving(null); + } + }; + + const editProvider = async (providerData: Partial) => { + if (!editingProvider || !providerData.name || !providerData.displayName) { + toast.error(t("authProviders.messages.fillRequiredFields")); + return; + } + + try { + setSaving(editingProvider.id); + const response = await fetch(`/api/auth/providers/manage/${editingProvider.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ...providerData, + name: providerData.name?.toLowerCase().replace(/\s+/g, "-"), + }), + }); + + const data = await response.json(); + + if (data.success) { + await loadProviders(); + setEditingProvider(null); + toast.success(t("authProviders.messages.providerUpdated")); + } else { + toast.error(t("authProviders.messages.updateFailed")); + } + } catch (error) { + console.error("Error updating provider:", error); + toast.error(t("authProviders.messages.updateFailed")); + } finally { + setSaving(null); + } + }; + + const deleteProvider = async (id: string) => { + try { + setIsDeleting(true); + const response = await fetch(`/api/auth/providers/manage/${id}`, { + method: "DELETE", + }); + + const data = await response.json(); + + if (data.success) { + setProviders((prev) => prev.filter((p) => p.id !== id)); + toast.success(t("authProviders.messages.providerDeleted")); + setProviderToDelete(null); + } else { + toast.error(t("authProviders.messages.deleteFailed")); + } + } catch (error) { + console.error("Error deleting provider:", error); + toast.error(t("authProviders.messages.deleteFailed")); + } finally { + setIsDeleting(false); + } + }; + + const handleDragEnd = async (result: DropResult) => { + if (!result.destination) return; + + const items = Array.from(providers); + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(result.destination.index, 0, reorderedItem); + + const updatedItems = items.map((provider, index) => ({ + ...provider, + sortOrder: index + 1, + })); + setProviders(updatedItems); + + const updatedProviders = updatedItems.map((provider) => ({ + id: provider.id, + sortOrder: provider.sortOrder, + })); + + try { + const response = await fetch("/api/auth/providers/order", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ providers: updatedProviders }), + }); + + const data = await response.json(); + + if (data.success) { + toast.success(t("authProviders.messages.providerOrderUpdated")); + } else { + toast.error(t("authProviders.messages.orderUpdateFailed")); + await loadProviders(); + } + } catch (error) { + console.error("Error updating provider order:", error); + toast.error(t("authProviders.messages.orderUpdateFailed")); + await loadProviders(); + } + }; + + const handleHideDisabledProvidersChange = (checked: boolean) => { + setHideDisabledProviders(checked); + localStorage.setItem("hideDisabledProviders", JSON.stringify(checked)); + }; + + const handleEditProvider = (provider: AuthProvider) => { + if (editingProvider?.id === provider.id) { + setEditingProvider(null); + } else { + setEditingProvider(provider); + } + }; + + const handleDeleteProvider = (provider: AuthProvider) => { + setProviderToDelete({ + id: provider.id, + name: provider.name, + displayName: provider.displayName, + }); + }; + + const handleCancelEdit = () => { + setEditingProvider(null); + setEditingFormData({}); + }; + + // Computed values + const enabledCount = providers.filter((p) => p.enabled).length; + const filteredProviders = hideDisabledProviders ? providers.filter((p) => p.enabled) : providers; + + return { + // State + providers, + loading, + saving, + editingProvider, + editingFormData, + hideDisabledProviders, + providerToDelete, + isDeleting, + + // Computed + enabledCount, + filteredProviders, + + // Actions + loadProviders, + updateProvider, + addProvider, + editProvider, + deleteProvider, + handleDragEnd, + handleHideDisabledProvidersChange, + handleEditProvider, + handleDeleteProvider, + handleCancelEdit, + + // Setters + setEditingFormData, + setProviderToDelete, + }; +} diff --git a/apps/web/src/app/settings/hooks/use-settings.ts b/apps/web/src/app/settings/hooks/use-settings.ts index 788ccac..872cf97 100644 --- a/apps/web/src/app/settings/hooks/use-settings.ts +++ b/apps/web/src/app/settings/hooks/use-settings.ts @@ -112,7 +112,6 @@ export function useSettings() { setGroupedConfigs(grouped); Object.entries(grouped).forEach(([groupName, groupConfigs]) => { - // Skip auth-providers group as it has its own custom component if (groupName === "auth-providers") { return; }