diff --git a/esbuild.mjs b/esbuild.mjs index b0227099..d76c0753 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -63,7 +63,7 @@ esbuild packagePath: getPackagePaths(), }), ], - sourcemap: true, + sourcemap: "external", target: "node22", }) .then(() => { diff --git a/install/config/traefik/traefik_config.yml b/install/config/traefik/traefik_config.yml index dd0ba1b2..8bb5aa6c 100644 --- a/install/config/traefik/traefik_config.yml +++ b/install/config/traefik/traefik_config.yml @@ -42,6 +42,10 @@ entryPoints: address: ":80" websecure: address: ":443" +{{if .HybridMode}} proxyProtocol: + trustedIPs: + - 0.0.0.0/0 + - ::1/128{{end}} transport: respondingTimeouts: readTimeout: "30m" diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 7d9a5262..3717855e 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Settings", "alwaysAllow": "Always Allow", "alwaysDeny": "Always Deny", + "passToAuth": "Pass to Auth", "orgSettingsDescription": "Configure your organization's general settings", "orgGeneralSettings": "Organization Settings", "orgGeneralSettingsDescription": "Manage your organization details and configuration", @@ -545,6 +546,7 @@ "rulesActions": "Actions", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", + "rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted", "rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 3a6737fa..fc03108a 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -149,44 +149,44 @@ "resourceDescription": "Vytvořte bezpečné proxy služby pro přístup k privátním aplikacím", "resourcesSearch": "Prohledat zdroje...", "resourceAdd": "Přidat zdroj", - "resourceErrorDelte": "Error deleting resource", - "authentication": "Authentication", - "protected": "Protected", - "notProtected": "Not Protected", - "resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.", - "resourceMessageConfirm": "To confirm, please type the name of the resource below.", - "resourceQuestionRemove": "Are you sure you want to remove the resource {selectedResource} from the organization?", - "resourceHTTP": "HTTPS Resource", + "resourceErrorDelte": "Chyba při odstraňování zdroje", + "authentication": "Autentifikace", + "protected": "Chráněno", + "notProtected": "Nechráněno", + "resourceMessageRemove": "Jakmile zdroj odstraníte, nebude dostupný. Všechny související služby a cíle budou také odstraněny.", + "resourceMessageConfirm": "Pro potvrzení zadejte prosím název zdroje.", + "resourceQuestionRemove": "Opravdu chcete odstranit zdroj {selectedResource} z organizace?", + "resourceHTTP": "Zdroj HTTPS", "resourceHTTPDescription": "Proxy requests to your app over HTTPS using a subdomain or base domain.", "resourceRaw": "Raw TCP/UDP Resource", "resourceRawDescription": "Proxy requests to your app over TCP/UDP using a port number.", - "resourceCreate": "Create Resource", - "resourceCreateDescription": "Follow the steps below to create a new resource", - "resourceSeeAll": "See All Resources", - "resourceInfo": "Resource Information", - "resourceNameDescription": "This is the display name for the resource.", - "siteSelect": "Select site", - "siteSearch": "Search site", - "siteNotFound": "No site found.", - "siteSelectionDescription": "This site will provide connectivity to the target.", - "resourceType": "Resource Type", - "resourceTypeDescription": "Determine how you want to access your resource", - "resourceHTTPSSettings": "HTTPS Settings", - "resourceHTTPSSettingsDescription": "Configure how your resource will be accessed over HTTPS", - "domainType": "Domain Type", - "subdomain": "Subdomain", - "baseDomain": "Base Domain", - "subdomnainDescription": "The subdomain where your resource will be accessible.", - "resourceRawSettings": "TCP/UDP Settings", - "resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP", - "protocol": "Protocol", - "protocolSelect": "Select a protocol", - "resourcePortNumber": "Port Number", - "resourcePortNumberDescription": "The external port number to proxy requests.", - "cancel": "Cancel", - "resourceConfig": "Configuration Snippets", - "resourceConfigDescription": "Copy and paste these configuration snippets to set up your TCP/UDP resource", - "resourceAddEntrypoints": "Traefik: Add Entrypoints", + "resourceCreate": "Vytvořit zdroj", + "resourceCreateDescription": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili nový zdroj", + "resourceSeeAll": "Zobrazit všechny zdroje", + "resourceInfo": "Informace o zdroji", + "resourceNameDescription": "Toto je zobrazovaný název zdroje.", + "siteSelect": "Vybrat lokalitu", + "siteSearch": "Hledat lokalitu", + "siteNotFound": "Nebyla nalezena žádná lokalita.", + "siteSelectionDescription": "Tato lokalita poskytne připojení k cíli.", + "resourceType": "Typ zdroje", + "resourceTypeDescription": "Určete, jak chcete přistupovat ke svému zdroji", + "resourceHTTPSSettings": "Nastavení HTTPS", + "resourceHTTPSSettingsDescription": "Nakonfigurujte, jak bude váš zdroj přístupný přes HTTPS", + "domainType": "Typ domény", + "subdomain": "Subdoména", + "baseDomain": "Základní doména", + "subdomnainDescription": "Subdoména, kde bude váš zdroj přístupný.", + "resourceRawSettings": "Nastavení TCP/UDP", + "resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP", + "protocol": "Protokol", + "protocolSelect": "Vybrat protokol", + "resourcePortNumber": "Číslo portu", + "resourcePortNumberDescription": "Externí port k požadavkům proxy serveru.", + "cancel": "Zrušit", + "resourceConfig": "Konfigurační snippety", + "resourceConfigDescription": "Zkopírujte a vložte tyto konfigurační snippety pro nastavení TCP/UDP zdroje", + "resourceAddEntrypoints": "Traefik: Přidat vstupní body", "resourceExposePorts": "Gerbil: Expose Ports in Docker Compose", "resourceLearnRaw": "Learn how to configure TCP/UDP resources", "resourceBack": "Back to Resources", @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Settings", "alwaysAllow": "Always Allow", "alwaysDeny": "Always Deny", + "passToAuth": "Pass to Auth", "orgSettingsDescription": "Configure your organization's general settings", "orgGeneralSettings": "Organization Settings", "orgGeneralSettingsDescription": "Manage your organization details and configuration", @@ -545,6 +546,7 @@ "rulesActions": "Actions", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", + "rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted", "rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", diff --git a/messages/de-DE.json b/messages/de-DE.json index 01b712e1..062b61af 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Einstellungen", "alwaysAllow": "Immer erlauben", "alwaysDeny": "Immer ablehnen", + "passToAuth": "Weiterleiten zur Authentifizierung", "orgSettingsDescription": "Konfiguriere die allgemeinen Einstellungen deiner Organisation", "orgGeneralSettings": "Organisations-Einstellungen", "orgGeneralSettingsDescription": "Organisationsdetails und Konfiguration verwalten", @@ -545,6 +546,7 @@ "rulesActions": "Aktionen", "rulesActionAlwaysAllow": "Immer erlauben: Alle Authentifizierungsmethoden umgehen", "rulesActionAlwaysDeny": "Immer verweigern: Alle Anfragen blockieren; keine Authentifizierung möglich", + "rulesActionPassToAuth": "Weiterleiten zur Authentifizierung: Erlaubt das Versuchen von Authentifizierungsmethoden", "rulesMatchCriteria": "Übereinstimmungskriterien", "rulesMatchCriteriaIpAddress": "Mit einer bestimmten IP-Adresse übereinstimmen", "rulesMatchCriteriaIpAddressRange": "Mit einem IP-Adressbereich in CIDR-Notation übereinstimmen", diff --git a/messages/en-US.json b/messages/en-US.json index 29b7f9f1..0b4a7534 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Settings", "alwaysAllow": "Always Allow", "alwaysDeny": "Always Deny", + "passToAuth": "Pass to Auth", "orgSettingsDescription": "Configure your organization's general settings", "orgGeneralSettings": "Organization Settings", "orgGeneralSettingsDescription": "Manage your organization details and configuration", @@ -545,6 +546,7 @@ "rulesActions": "Actions", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", + "rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted", "rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", diff --git a/messages/es-ES.json b/messages/es-ES.json index 70076d07..2b49d9bb 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -205,6 +205,7 @@ "resourceSetting": "Ajustes {resourceName}", "alwaysAllow": "Permitir siempre", "alwaysDeny": "Denegar siempre", + "passToAuth": "Pasar a Autenticación", "orgSettingsDescription": "Configurar la configuración general de su organización", "orgGeneralSettings": "Configuración de la organización", "orgGeneralSettingsDescription": "Administra los detalles y la configuración de tu organización", @@ -545,6 +546,7 @@ "rulesActions": "Acciones", "rulesActionAlwaysAllow": "Permitir siempre: pasar todos los métodos de autenticación", "rulesActionAlwaysDeny": "Denegar siempre: Bloquear todas las peticiones; no se puede intentar autenticación", + "rulesActionPassToAuth": "Pasar a Autenticación: Permitir que se intenten los métodos de autenticación", "rulesMatchCriteria": "Criterios coincidentes", "rulesMatchCriteriaIpAddress": "Coincidir con una dirección IP específica", "rulesMatchCriteriaIpAddressRange": "Coincide con un rango de direcciones IP en notación CIDR", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 8d1890c8..ee08d77b 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -23,7 +23,7 @@ "inviteLoginUser": "Assurez-vous que vous êtes bien connecté en tant qu'utilisateur correct.", "inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour un utilisateur qui existe.", "inviteCreateUser": "Veuillez d'abord créer un compte.", - "goHome": "Aller à l’accueil", + "goHome": "Retour à la maison", "inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent", "createAnAccount": "Créer un compte", "inviteNotAccepted": "Invitation non acceptée", @@ -34,16 +34,16 @@ "confirmPassword": "Confirmer le mot de passe", "createAccount": "Créer un compte", "viewSettings": "Afficher les paramètres", - "delete": "Supprimer", + "delete": "Supprimez", "name": "Nom", "online": "En ligne", "offline": "Hors ligne", "site": "Site", - "dataIn": "Données entrantes", - "dataOut": "Données sortantes", + "dataIn": "Données dans", + "dataOut": "Données épuisées", "connectionType": "Type de connexion", "tunnelType": "Type de tunnel", - "local": "Local", + "local": "Locale", "edit": "Editer", "siteConfirmDelete": "Confirmer la suppression du site", "siteDelete": "Supprimer le site", @@ -68,7 +68,7 @@ "toggle": "Activer/désactiver", "dockerCompose": "Composition Docker", "dockerRun": "Exécution Docker", - "siteLearnLocal": "Les sites locaux ne tunnel plus, en savoir plus", + "siteLearnLocal": "Les sites locaux ne tunnel, en savoir plus", "siteConfirmCopy": "J'ai copié la configuration", "searchSitesProgress": "Rechercher des sites...", "siteAdd": "Ajouter un site", @@ -94,9 +94,9 @@ "siteNewtTunnelDescription": "La façon la plus simple de créer un point d'entrée dans votre réseau. Pas de configuration supplémentaire.", "siteWg": "WireGuard basique", "siteWgDescription": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise.", - "siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. NE FONCTIONNE QUE SUR LES NŒUDS AUTO-HÉBERGÉS", + "siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES", "siteLocalDescription": "Ressources locales seulement. Pas de tunneling.", - "siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. NE FONCTIONNE QUE SUR LES NŒUDS AUTO-HÉBERGÉS", + "siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES", "siteSeeAll": "Voir tous les sites", "siteTunnelDescription": "Déterminez comment vous voulez vous connecter à votre site", "siteNewtCredentials": "Identifiants Newt", @@ -132,7 +132,7 @@ "expireIn": "Expire dans", "neverExpire": "N'expire jamais", "shareExpireDescription": "Le temps d'expiration est combien de temps le lien sera utilisable et fournira un accès à la ressource. Après cette période, le lien ne fonctionnera plus et les utilisateurs qui ont utilisé ce lien perdront l'accès à la ressource.", - "shareSeeOnce": "Vous ne pourrez voir ce lien qu’une seule fois. Assurez-vous de le copier.", + "shareSeeOnce": "Vous ne pourrez voir ce lien. Assurez-vous de le copier.", "shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.", "shareTokenUsage": "Voir Utilisation du jeton d'accès", "createLink": "Créer un lien", @@ -140,7 +140,7 @@ "resourceSearch": "Rechercher des ressources", "openMenu": "Ouvrir le menu", "resource": "Ressource", - "title": "Titre", + "title": "Titre de la page", "created": "Créé", "expires": "Expire", "never": "Jamais", @@ -196,7 +196,7 @@ "visibility": "Visibilité", "enabled": "Activé", "disabled": "Désactivé", - "general": "Général", + "general": "Généraux", "generalSettings": "Paramètres généraux", "proxy": "Proxy", "internal": "Interne", @@ -205,6 +205,7 @@ "resourceSetting": "Réglages {resourceName}", "alwaysAllow": "Toujours autoriser", "alwaysDeny": "Toujours refuser", + "passToAuth": "Paser à l'authentification", "orgSettingsDescription": "Configurer les paramètres généraux de votre organisation", "orgGeneralSettings": "Paramètres de l'organisation", "orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation", @@ -545,6 +546,7 @@ "rulesActions": "Actions", "rulesActionAlwaysAllow": "Toujours autoriser : Contourner toutes les méthodes d'authentification", "rulesActionAlwaysDeny": "Toujours refuser : Bloquer toutes les requêtes ; aucune authentification ne peut être tentée", + "rulesActionPassToAuth": "Passer à l'authentification : Autoriser les méthodes d'authentification à être tentées", "rulesMatchCriteria": "Critères de correspondance", "rulesMatchCriteriaIpAddress": "Correspondre à une adresse IP spécifique", "rulesMatchCriteriaIpAddressRange": "Correspondre à une plage d'adresses IP en notation CIDR", @@ -593,7 +595,7 @@ "newtId": "ID Newt", "newtSecretKey": "Clé secrète Newt", "architecture": "Architecture", - "sites": "Sites", + "sites": "Espaces", "siteWgAnyClients": "Utilisez n'importe quel client WireGuard pour vous connecter. Vous devrez adresser vos ressources internes en utilisant l'IP du pair.", "siteWgCompatibleAllClients": "Compatible avec tous les clients WireGuard", "siteWgManualConfigurationRequired": "Configuration manuelle requise", @@ -959,7 +961,7 @@ "supportKetOptionFull": "Support complet", "forWholeServer": "Pour tout le serveur", "lifetimePurchase": "Achat à vie", - "supporterStatus": "Statut de supporteur", + "supporterStatus": "Statut de supporter", "buy": "Acheter", "supportKeyOptionLimited": "Support limité", "forFiveUsers": "Pour 5 utilisateurs ou moins", @@ -1103,7 +1105,7 @@ "allowAll": "Tout autoriser", "permissionsAllowAll": "Autoriser toutes les autorisations", "githubUsernameRequired": "Le nom d'utilisateur GitHub est requis", - "supportKeyRequired": "La clé de supporteur est requise", + "supportKeyRequired": "La clé de supporter est requise", "passwordRequirementsChars": "Le mot de passe doit comporter au moins 8 caractères", "language": "Langue", "verificationCodeRequired": "Le code est requis", @@ -1115,14 +1117,14 @@ "orgErrorNoProvided": "Aucune organisation fournie", "apiKeysErrorNoUpdate": "Pas de clé API à mettre à jour", "sidebarOverview": "Aperçu", - "sidebarHome": "Accueil", - "sidebarSites": "Sites", - "sidebarResources": "Ressources", + "sidebarHome": "Domicile", + "sidebarSites": "Espaces", + "sidebarResources": "Ressource", "sidebarAccessControl": "Contrôle d'accès", "sidebarUsers": "Utilisateurs", "sidebarInvitations": "Invitations", "sidebarRoles": "Rôles", - "sidebarShareableLinks": "Liens partageables", + "sidebarShareableLinks": "Liens partagables", "sidebarApiKeys": "Clés API", "sidebarSettings": "Réglages", "sidebarAllUsers": "Tous les utilisateurs", diff --git a/messages/it-IT.json b/messages/it-IT.json index c6b09859..83708597 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -205,6 +205,7 @@ "resourceSetting": "Impostazioni {resourceName}", "alwaysAllow": "Consenti Sempre", "alwaysDeny": "Nega Sempre", + "passToAuth": "Passa all'autenticazione", "orgSettingsDescription": "Configura le impostazioni generali della tua organizzazione", "orgGeneralSettings": "Impostazioni Organizzazione", "orgGeneralSettingsDescription": "Gestisci i dettagli dell'organizzazione e la configurazione", @@ -545,6 +546,7 @@ "rulesActions": "Azioni", "rulesActionAlwaysAllow": "Consenti Sempre: Ignora tutti i metodi di autenticazione", "rulesActionAlwaysDeny": "Nega Sempre: Blocca tutte le richieste; nessuna autenticazione può essere tentata", + "rulesActionPassToAuth": "Passa all'autenticazione: Consenti di tentare i metodi di autenticazione", "rulesMatchCriteria": "Criteri di Corrispondenza", "rulesMatchCriteriaIpAddress": "Corrisponde a un indirizzo IP specifico", "rulesMatchCriteriaIpAddressRange": "Corrisponde a un intervallo di indirizzi IP in notazione CIDR", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index e97c214f..b13dd19d 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} 설정", "alwaysAllow": "항상 허용", "alwaysDeny": "항상 거부", + "passToAuth": "인증으로 전달", "orgSettingsDescription": "조직의 일반 설정을 구성하세요", "orgGeneralSettings": "조직 설정", "orgGeneralSettingsDescription": "조직 세부정보 및 구성을 관리하세요.", @@ -545,6 +546,7 @@ "rulesActions": "작업", "rulesActionAlwaysAllow": "항상 허용: 모든 인증 방법 우회", "rulesActionAlwaysDeny": "항상 거부: 모든 요청을 차단합니다. 인증을 시도할 수 없습니다.", + "rulesActionPassToAuth": "인증으로 전달: 인증 방법 시도를 허용합니다", "rulesMatchCriteria": "일치 기준", "rulesMatchCriteriaIpAddress": "특정 IP 주소와 일치", "rulesMatchCriteriaIpAddressRange": "CIDR 표기법으로 IP 주소 범위를 일치시킵니다", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index be7453e2..f88bfbcf 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Innstillinger", "alwaysAllow": "Alltid tillat", "alwaysDeny": "Alltid avslå", + "passToAuth": "Pass til Autentisering", "orgSettingsDescription": "Konfigurer organisasjonens generelle innstillinger", "orgGeneralSettings": "Organisasjonsinnstillinger", "orgGeneralSettingsDescription": "Administrer dine organisasjonsdetaljer og konfigurasjon", @@ -545,6 +546,7 @@ "rulesActions": "Handlinger", "rulesActionAlwaysAllow": "Alltid Tillat: Omgå alle autentiserings metoder", "rulesActionAlwaysDeny": "Alltid Nekt: Blokker alle forespørsler; ingen autentisering kan forsøkes", + "rulesActionPassToAuth": "Pass til Autentisering: Tillat at autentiseringsmetoder forsøkes", "rulesMatchCriteria": "Samsvarende kriterier", "rulesMatchCriteriaIpAddress": "Samsvar med en spesifikk IP-adresse", "rulesMatchCriteriaIpAddressRange": "Samsvar et IP-adresseområde i CIDR-notasjon", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index ef2978bc..76c28cb5 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} instellingen", "alwaysAllow": "Altijd toestaan", "alwaysDeny": "Altijd weigeren", + "passToAuth": "Passeren naar Auth", "orgSettingsDescription": "Configureer de algemene instellingen van je organisatie", "orgGeneralSettings": "Organisatie Instellingen", "orgGeneralSettingsDescription": "Beheer de details en configuratie van uw organisatie", @@ -545,6 +546,7 @@ "rulesActions": "acties", "rulesActionAlwaysAllow": "Altijd toegestaan: Omzeil alle authenticatiemethoden", "rulesActionAlwaysDeny": "Altijd weigeren: Blokkeer alle aanvragen, er kan geen verificatie worden geprobeerd", + "rulesActionPassToAuth": "Doorgeven aan Auth: Toestaan dat authenticatiemethoden worden geprobeerd", "rulesMatchCriteria": "Overeenkomende criteria", "rulesMatchCriteriaIpAddress": "Overeenkomen met een specifiek IP-adres", "rulesMatchCriteriaIpAddressRange": "Overeenkomen met een bereik van IP-adressen in de CIDR-notatie", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 25928ab1..be33709c 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -205,6 +205,7 @@ "resourceSetting": "Ustawienia {resourceName}", "alwaysAllow": "Zawsze zezwalaj", "alwaysDeny": "Zawsze odmawiaj", + "passToAuth": "Przekaż do Autoryzacji", "orgSettingsDescription": "Skonfiguruj ustawienia ogólne swojej organizacji", "orgGeneralSettings": "Ustawienia organizacji", "orgGeneralSettingsDescription": "Zarządzaj szczegółami swojej organizacji i konfiguracją", @@ -545,6 +546,7 @@ "rulesActions": "Akcje", "rulesActionAlwaysAllow": "Zawsze zezwalaj: Pomiń wszystkie metody uwierzytelniania", "rulesActionAlwaysDeny": "Zawsze odmawiaj: Blokuj wszystkie żądania; nie można próbować uwierzytelniania", + "rulesActionPassToAuth": "Przekaż do Autoryzacji: Zezwól na próby metod uwierzytelniania", "rulesMatchCriteria": "Kryteria dopasowania", "rulesMatchCriteriaIpAddress": "Dopasuj konkretny adres IP", "rulesMatchCriteriaIpAddressRange": "Dopasuj zakres adresów IP w notacji CIDR", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 80b44bac..4efd0237 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -205,6 +205,7 @@ "resourceSetting": "Configurações do {resourceName}", "alwaysAllow": "Sempre permitir", "alwaysDeny": "Sempre negar", + "passToAuth": "Passar para Autenticação", "orgSettingsDescription": "Configurar as configurações gerais da sua organização", "orgGeneralSettings": "Configurações da organização", "orgGeneralSettingsDescription": "Gerencie os detalhes e a configuração da sua organização", @@ -545,6 +546,7 @@ "rulesActions": "Ações", "rulesActionAlwaysAllow": "Sempre Permitir: Ignorar todos os métodos de autenticação", "rulesActionAlwaysDeny": "Sempre Negar: Bloquear todas as requisições; nenhuma autenticação pode ser tentada", + "rulesActionPassToAuth": "Passar para Autenticação: Permitir que métodos de autenticação sejam tentados", "rulesMatchCriteria": "Critérios de Correspondência", "rulesMatchCriteriaIpAddress": "Corresponder a um endereço IP específico", "rulesMatchCriteriaIpAddressRange": "Corresponder a uma faixa de endereços IP em notação CIDR", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 12174c81..ed44702d 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -205,6 +205,7 @@ "resourceSetting": "Настройки {resourceName}", "alwaysAllow": "Всегда разрешать", "alwaysDeny": "Всегда запрещать", + "passToAuth": "Переход к аутентификации", "orgSettingsDescription": "Настройте общие параметры вашей организации", "orgGeneralSettings": "Настройки организации", "orgGeneralSettingsDescription": "Управляйте данными и конфигурацией вашей организации", @@ -545,6 +546,7 @@ "rulesActions": "Действия", "rulesActionAlwaysAllow": "Всегда разрешать: Обойти все методы аутентификации", "rulesActionAlwaysDeny": "Всегда запрещать: Блокировать все запросы; аутентификация не может быть выполнена", + "rulesActionPassToAuth": "Переход к аутентификации: Разрешить попытки методов аутентификации", "rulesMatchCriteria": "Критерии совпадения", "rulesMatchCriteriaIpAddress": "Совпадение с конкретным IP адресом", "rulesMatchCriteriaIpAddressRange": "Совпадение с диапазоном IP адресов в нотации CIDR", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index a1bc4dc3..89de6876 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} Ayarları", "alwaysAllow": "Her Zaman İzin Ver", "alwaysDeny": "Her Zaman Reddet", + "passToAuth": "Kimlik Doğrulamasına Geç", "orgSettingsDescription": "Organizasyonunuzun genel ayarlarını yapılandırın", "orgGeneralSettings": "Organizasyon Ayarları", "orgGeneralSettingsDescription": "Organizasyon detaylarınızı ve yapılandırmanızı yönetin", @@ -545,6 +546,7 @@ "rulesActions": "Aksiyonlar", "rulesActionAlwaysAllow": "Her Zaman İzin Ver: Tüm kimlik doğrulama yöntemlerini atlayın", "rulesActionAlwaysDeny": "Her Zaman Reddedin: Tüm istekleri engelleyin; kimlik doğrulaması yapılamaz", + "rulesActionPassToAuth": "Kimlik Doğrulamasına Geç: Kimlik doğrulama yöntemlerinin denenmesine izin ver", "rulesMatchCriteria": "Eşleşme Kriterleri", "rulesMatchCriteriaIpAddress": "Belirli bir IP adresi ile eşleşme", "rulesMatchCriteriaIpAddressRange": "CIDR gösteriminde bir IP adresi aralığı ile eşleşme", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index e87e455b..06cd8549 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -205,6 +205,7 @@ "resourceSetting": "{resourceName} 设置", "alwaysAllow": "一律允许", "alwaysDeny": "一律拒绝", + "passToAuth": "传递至认证", "orgSettingsDescription": "配置您组织的一般设置", "orgGeneralSettings": "组织设置", "orgGeneralSettingsDescription": "管理您的机构详细信息和配置", @@ -545,6 +546,7 @@ "rulesActions": "行动", "rulesActionAlwaysAllow": "总是允许:绕过所有身份验证方法", "rulesActionAlwaysDeny": "总是拒绝:阻止所有请求;无法尝试验证", + "rulesActionPassToAuth": "传递至认证:允许尝试身份验证方法", "rulesMatchCriteria": "匹配条件", "rulesMatchCriteriaIpAddress": "匹配一个指定的 IP 地址", "rulesMatchCriteriaIpAddressRange": "在 CIDR 符号中匹配一系列IP地址", diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index fcbf2621..8e725ab1 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -430,7 +430,7 @@ export const resourceRules = pgTable("resourceRules", { .references(() => resources.resourceId, { onDelete: "cascade" }), enabled: boolean("enabled").notNull().default(true), priority: integer("priority").notNull(), - action: varchar("action").notNull(), // ACCEPT, DROP + action: varchar("action").notNull(), // ACCEPT, DROP, PASS match: varchar("match").notNull(), // CIDR, PATH, IP value: varchar("value").notNull() }); diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index 5db6bfdd..c3e79291 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -570,7 +570,7 @@ export const resourceRules = sqliteTable("resourceRules", { .references(() => resources.resourceId, { onDelete: "cascade" }), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), priority: integer("priority").notNull(), - action: text("action").notNull(), // ACCEPT, DROP + action: text("action").notNull(), // ACCEPT, DROP, PASS match: text("match").notNull(), // CIDR, PATH, IP value: text("value").notNull() }); diff --git a/server/lib/geoip.ts b/server/lib/geoip.ts new file mode 100644 index 00000000..042e53c9 --- /dev/null +++ b/server/lib/geoip.ts @@ -0,0 +1,32 @@ +import axios from "axios"; +import config from "./config"; +import { tokenManager } from "./tokenManager"; +import logger from "@server/logger"; + +export async function getCountryCodeForIp( + ip: string +): Promise { + try { + const response = await axios.get( + `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/geoip/${ip}`, + await tokenManager.getAuthHeader() + ); + + return response.data.data.countryCode; + } catch (error) { + if (axios.isAxiosError(error)) { + logger.error("Error fetching config in verify session:", { + message: error.message, + code: error.code, + status: error.response?.status, + statusText: error.response?.statusText, + url: error.config?.url, + method: error.config?.method + }); + } else { + logger.error("Error fetching config in verify session:", error); + } + } + + return; +} diff --git a/server/lib/index.ts b/server/lib/index.ts index 7705e0af..db1a73da 100644 --- a/server/lib/index.ts +++ b/server/lib/index.ts @@ -1,2 +1,3 @@ export * from "./response"; export { tokenManager, TokenManager } from "./tokenManager"; +export * from "./geoip"; diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 905f748d..4ab47ed6 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -5,7 +5,6 @@ import { validateResourceSessionToken } from "@server/auth/sessions/resource"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; -import { db } from "@server/db"; import { getResourceByDomain, getUserSessionWithUser, @@ -33,6 +32,7 @@ import createHttpError from "http-errors"; import NodeCache from "node-cache"; import { z } from "zod"; import { fromError } from "zod-validation-error"; +import { getCountryCodeForIp } from "@server/lib"; // We'll see if this speeds anything up const cache = new NodeCache({ @@ -123,8 +123,8 @@ export async function verifyResourceSession( let cleanHost = host; // if the host ends with :port, strip it if (cleanHost.match(/:[0-9]{1,5}$/)) { - const matched = ''+cleanHost.match(/:[0-9]{1,5}$/); - cleanHost = cleanHost.slice(0, -1*matched.length); + const matched = "" + cleanHost.match(/:[0-9]{1,5}$/); + cleanHost = cleanHost.slice(0, -1 * matched.length); } const resourceCacheKey = `resource:${cleanHost}`; @@ -176,6 +176,11 @@ export async function verifyResourceSession( } else if (action == "DROP") { logger.debug("Resource denied by rule"); return notAllowed(res); + } else if (action == "PASS") { + logger.debug( + "Resource passed by rule, continuing to auth checks" + ); + // Continue to authentication checks below } // otherwise its undefined and we pass @@ -193,7 +198,10 @@ export async function verifyResourceSession( let endpoint: string; if (config.isManagedMode()) { - endpoint = config.getRawConfig().managed?.redirect_endpoint || config.getRawConfig().managed?.endpoint || ""; + endpoint = + config.getRawConfig().managed?.redirect_endpoint || + config.getRawConfig().managed?.endpoint || + ""; } else { endpoint = config.getRawConfig().app.dashboard_url!; } @@ -576,7 +584,7 @@ async function checkRules( resourceId: number, clientIp: string | undefined, path: string | undefined -): Promise<"ACCEPT" | "DROP" | undefined> { +): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> { const ruleCacheKey = `rules:${resourceId}`; let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey); @@ -613,6 +621,12 @@ async function checkRules( isPathAllowed(rule.value, path) ) { return rule.action as any; + } else if ( + clientIp && + rule.match == "GEOIP" && + (await isIpInGeoIP(clientIp, rule.value)) + ) { + return rule.action as any; } } @@ -737,3 +751,23 @@ export function isPathAllowed(pattern: string, path: string): boolean { logger.debug(`Final result: ${result}`); return result; } + +async function isIpInGeoIP(ip: string, countryCode: string): Promise { + if (countryCode == "ALL") { + return true; + } + + const geoIpCacheKey = `geoip:${ip}`; + + let cachedCountryCode: string | undefined = cache.get(geoIpCacheKey); + + if (!cachedCountryCode) { + cachedCountryCode = await getCountryCodeForIp(ip); + // Cache for longer since IP geolocation doesn't change frequently + cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes + } + + logger.debug(`IP ${ip} is in country: ${cachedCountryCode}`); + + return cachedCountryCode?.toUpperCase() === countryCode.toUpperCase(); +} diff --git a/server/routers/gerbil/createExitNode.ts b/server/routers/gerbil/createExitNode.ts new file mode 100644 index 00000000..d4e6d43a --- /dev/null +++ b/server/routers/gerbil/createExitNode.ts @@ -0,0 +1,58 @@ +import { db, ExitNode, exitNodes } from "@server/db"; +import { getUniqueExitNodeEndpointName } from "@server/db/names"; +import config from "@server/lib/config"; +import { getNextAvailableSubnet } from "@server/lib/exitNodes"; +import logger from "@server/logger"; +import { eq } from "drizzle-orm"; + +export async function createExitNode(publicKey: string, reachableAt: string | undefined) { + // Fetch exit node + const [exitNodeQuery] = await db.select().from(exitNodes).limit(1); + let exitNode: ExitNode; + if (!exitNodeQuery) { + const address = await getNextAvailableSubnet(); + // TODO: eventually we will want to get the next available port so that we can multiple exit nodes + // const listenPort = await getNextAvailablePort(); + const listenPort = config.getRawConfig().gerbil.start_port; + let subEndpoint = ""; + if (config.getRawConfig().gerbil.use_subdomain) { + subEndpoint = await getUniqueExitNodeEndpointName(); + } + + const exitNodeName = + config.getRawConfig().gerbil.exit_node_name || + `Exit Node ${publicKey.slice(0, 8)}`; + + // create a new exit node + [exitNode] = await db + .insert(exitNodes) + .values({ + publicKey, + endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`, + address, + listenPort, + reachableAt, + name: exitNodeName + }) + .returning() + .execute(); + + logger.info( + `Created new exit node ${exitNode.name} with address ${exitNode.address} and port ${exitNode.listenPort}` + ); + } else { + // update the existing exit node + [exitNode] = await db + .update(exitNodes) + .set({ + reachableAt, + publicKey + }) + .where(eq(exitNodes.publicKey, publicKey)) + .returning(); + + logger.info(`Updated exit node`); + } + + return exitNode; +} diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index 77f7d2e0..71d1a45e 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -13,6 +13,8 @@ import { fromError } from "zod-validation-error"; import { getAllowedIps } from "../target/helpers"; import { proxyToRemote } from "@server/lib/remoteProxy"; import { getNextAvailableSubnet } from "@server/lib/exitNodes"; +import { createExitNode } from "./createExitNode"; + // Define Zod schema for request validation const getConfigSchema = z.object({ publicKey: z.string(), @@ -53,46 +55,7 @@ export async function getConfig( ); } - // Fetch exit node - const exitNodeQuery = await db - .select() - .from(exitNodes) - .where(eq(exitNodes.publicKey, publicKey)); - let exitNode; - if (exitNodeQuery.length === 0) { - const address = await getNextAvailableSubnet(); - // TODO: eventually we will want to get the next available port so that we can multiple exit nodes - // const listenPort = await getNextAvailablePort(); - const listenPort = config.getRawConfig().gerbil.start_port; - let subEndpoint = ""; - if (config.getRawConfig().gerbil.use_subdomain) { - subEndpoint = await getUniqueExitNodeEndpointName(); - } - - const exitNodeName = - config.getRawConfig().gerbil.exit_node_name || - `Exit Node ${publicKey.slice(0, 8)}`; - - // create a new exit node - exitNode = await db - .insert(exitNodes) - .values({ - publicKey, - endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`, - address, - listenPort, - reachableAt, - name: exitNodeName - }) - .returning() - .execute(); - - logger.info( - `Created new exit node ${exitNode[0].name} with address ${exitNode[0].address} and port ${exitNode[0].listenPort}` - ); - } else { - exitNode = exitNodeQuery; - } + const exitNode = await createExitNode(publicKey, reachableAt); if (!exitNode) { return next( @@ -107,13 +70,13 @@ export async function getConfig( if (config.isManagedMode()) { req.body = { ...req.body, - endpoint: exitNode[0].endpoint, - listenPort: exitNode[0].listenPort + endpoint: exitNode.endpoint, + listenPort: exitNode.listenPort }; return proxyToRemote(req, res, next, "hybrid/gerbil/get-config"); } - const configResponse = await generateGerbilConfig(exitNode[0]); + const configResponse = await generateGerbilConfig(exitNode); logger.debug("Sending config: ", configResponse); diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index 6651eee2..affd7625 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -17,7 +17,7 @@ import { OpenAPITags, registry } from "@server/openApi"; const createResourceRuleSchema = z .object({ - action: z.enum(["ACCEPT", "DROP"]), + action: z.enum(["ACCEPT", "DROP", "PASS"]), match: z.enum(["CIDR", "IP", "PATH"]), value: z.string().min(1), priority: z.number().int(), diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index 449a92ef..c2b6a47a 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -29,7 +29,7 @@ const updateResourceRuleParamsSchema = z // Define Zod schema for request body validation const updateResourceRuleSchema = z .object({ - action: z.enum(["ACCEPT", "DROP"]).optional(), + action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(), match: z.enum(["CIDR", "IP", "PATH"]).optional(), value: z.string().min(1).optional(), priority: z.number().int(), diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index 21c74311..424d7973 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -76,7 +76,7 @@ import { useTranslations } from "next-intl"; // Schema for rule validation const addRuleSchema = z.object({ - action: z.string(), + action: z.enum(["ACCEPT", "DROP", "PASS"]), match: z.string(), value: z.string(), priority: z.coerce.number().int().optional() @@ -104,7 +104,8 @@ export default function ResourceRules(props: { const RuleAction = { ACCEPT: t('alwaysAllow'), - DROP: t('alwaysDeny') + DROP: t('alwaysDeny'), + PASS: t('passToAuth') } as const; const RuleMatch = { @@ -113,7 +114,7 @@ export default function ResourceRules(props: { CIDR: t('ipAddressRange') } as const; - const addRuleForm = useForm({ + const addRuleForm = useForm>({ resolver: zodResolver(addRuleSchema), defaultValues: { action: "ACCEPT", @@ -437,7 +438,7 @@ export default function ResourceRules(props: { cell: ({ row }) => ( ) @@ -629,6 +631,9 @@ export default function ResourceRules(props: { {RuleAction.DROP} + + {RuleAction.PASS} +