Merge pull request #1366 from fosrl/dev

1.9.2
This commit is contained in:
Owen Schwartz
2025-08-27 12:02:21 -07:00
committed by GitHub
27 changed files with 237 additions and 110 deletions

View File

@@ -63,7 +63,7 @@ esbuild
packagePath: getPackagePaths(), packagePath: getPackagePaths(),
}), }),
], ],
sourcemap: true, sourcemap: "external",
target: "node22", target: "node22",
}) })
.then(() => { .then(() => {

View File

@@ -42,6 +42,10 @@ entryPoints:
address: ":80" address: ":80"
websecure: websecure:
address: ":443" address: ":443"
{{if .HybridMode}} proxyProtocol:
trustedIPs:
- 0.0.0.0/0
- ::1/128{{end}}
transport: transport:
respondingTimeouts: respondingTimeouts:
readTimeout: "30m" readTimeout: "30m"

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Settings", "resourceSetting": "{resourceName} Settings",
"alwaysAllow": "Always Allow", "alwaysAllow": "Always Allow",
"alwaysDeny": "Always Deny", "alwaysDeny": "Always Deny",
"passToAuth": "Pass to Auth",
"orgSettingsDescription": "Configure your organization's general settings", "orgSettingsDescription": "Configure your organization's general settings",
"orgGeneralSettings": "Organization Settings", "orgGeneralSettings": "Organization Settings",
"orgGeneralSettingsDescription": "Manage your organization details and configuration", "orgGeneralSettingsDescription": "Manage your organization details and configuration",
@@ -545,6 +546,7 @@
"rulesActions": "Actions", "rulesActions": "Actions",
"rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods",
"rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted",
"rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted",
"rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteria": "Matching Criteria",
"rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddress": "Match a specific IP address",
"rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation",

View File

@@ -149,44 +149,44 @@
"resourceDescription": "Vytvořte bezpečné proxy služby pro přístup k privátním aplikacím", "resourceDescription": "Vytvořte bezpečné proxy služby pro přístup k privátním aplikacím",
"resourcesSearch": "Prohledat zdroje...", "resourcesSearch": "Prohledat zdroje...",
"resourceAdd": "Přidat zdroj", "resourceAdd": "Přidat zdroj",
"resourceErrorDelte": "Error deleting resource", "resourceErrorDelte": "Chyba při odstraňování zdroje",
"authentication": "Authentication", "authentication": "Autentifikace",
"protected": "Protected", "protected": "Chráněno",
"notProtected": "Not Protected", "notProtected": "Nechráněno",
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.", "resourceMessageRemove": "Jakmile zdroj odstraníte, nebude dostupný. Všechny související služby a cíle budou také odstraněny.",
"resourceMessageConfirm": "To confirm, please type the name of the resource below.", "resourceMessageConfirm": "Pro potvrzení zadejte prosím název zdroje.",
"resourceQuestionRemove": "Are you sure you want to remove the resource {selectedResource} from the organization?", "resourceQuestionRemove": "Opravdu chcete odstranit zdroj {selectedResource} z organizace?",
"resourceHTTP": "HTTPS Resource", "resourceHTTP": "Zdroj HTTPS",
"resourceHTTPDescription": "Proxy requests to your app over HTTPS using a subdomain or base domain.", "resourceHTTPDescription": "Proxy requests to your app over HTTPS using a subdomain or base domain.",
"resourceRaw": "Raw TCP/UDP Resource", "resourceRaw": "Raw TCP/UDP Resource",
"resourceRawDescription": "Proxy requests to your app over TCP/UDP using a port number.", "resourceRawDescription": "Proxy requests to your app over TCP/UDP using a port number.",
"resourceCreate": "Create Resource", "resourceCreate": "Vytvořit zdroj",
"resourceCreateDescription": "Follow the steps below to create a new resource", "resourceCreateDescription": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili nový zdroj",
"resourceSeeAll": "See All Resources", "resourceSeeAll": "Zobrazit všechny zdroje",
"resourceInfo": "Resource Information", "resourceInfo": "Informace o zdroji",
"resourceNameDescription": "This is the display name for the resource.", "resourceNameDescription": "Toto je zobrazovaný název zdroje.",
"siteSelect": "Select site", "siteSelect": "Vybrat lokalitu",
"siteSearch": "Search site", "siteSearch": "Hledat lokalitu",
"siteNotFound": "No site found.", "siteNotFound": "Nebyla nalezena žádná lokalita.",
"siteSelectionDescription": "This site will provide connectivity to the target.", "siteSelectionDescription": "Tato lokalita poskytne připojení k cíli.",
"resourceType": "Resource Type", "resourceType": "Typ zdroje",
"resourceTypeDescription": "Determine how you want to access your resource", "resourceTypeDescription": "Určete, jak chcete přistupovat ke svému zdroji",
"resourceHTTPSSettings": "HTTPS Settings", "resourceHTTPSSettings": "Nastavení HTTPS",
"resourceHTTPSSettingsDescription": "Configure how your resource will be accessed over HTTPS", "resourceHTTPSSettingsDescription": "Nakonfigurujte, jak bude váš zdroj přístupný přes HTTPS",
"domainType": "Domain Type", "domainType": "Typ domény",
"subdomain": "Subdomain", "subdomain": "Subdoména",
"baseDomain": "Base Domain", "baseDomain": "Základní doména",
"subdomnainDescription": "The subdomain where your resource will be accessible.", "subdomnainDescription": "Subdoména, kde bude váš zdroj přístupný.",
"resourceRawSettings": "TCP/UDP Settings", "resourceRawSettings": "Nastavení TCP/UDP",
"resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP", "resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP",
"protocol": "Protocol", "protocol": "Protokol",
"protocolSelect": "Select a protocol", "protocolSelect": "Vybrat protokol",
"resourcePortNumber": "Port Number", "resourcePortNumber": "Číslo portu",
"resourcePortNumberDescription": "The external port number to proxy requests.", "resourcePortNumberDescription": "Externí port k požadavkům proxy serveru.",
"cancel": "Cancel", "cancel": "Zrušit",
"resourceConfig": "Configuration Snippets", "resourceConfig": "Konfigurační snippety",
"resourceConfigDescription": "Copy and paste these configuration snippets to set up your TCP/UDP resource", "resourceConfigDescription": "Zkopírujte a vložte tyto konfigurační snippety pro nastavení TCP/UDP zdroje",
"resourceAddEntrypoints": "Traefik: Add Entrypoints", "resourceAddEntrypoints": "Traefik: Přidat vstupní body",
"resourceExposePorts": "Gerbil: Expose Ports in Docker Compose", "resourceExposePorts": "Gerbil: Expose Ports in Docker Compose",
"resourceLearnRaw": "Learn how to configure TCP/UDP resources", "resourceLearnRaw": "Learn how to configure TCP/UDP resources",
"resourceBack": "Back to Resources", "resourceBack": "Back to Resources",
@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Settings", "resourceSetting": "{resourceName} Settings",
"alwaysAllow": "Always Allow", "alwaysAllow": "Always Allow",
"alwaysDeny": "Always Deny", "alwaysDeny": "Always Deny",
"passToAuth": "Pass to Auth",
"orgSettingsDescription": "Configure your organization's general settings", "orgSettingsDescription": "Configure your organization's general settings",
"orgGeneralSettings": "Organization Settings", "orgGeneralSettings": "Organization Settings",
"orgGeneralSettingsDescription": "Manage your organization details and configuration", "orgGeneralSettingsDescription": "Manage your organization details and configuration",
@@ -545,6 +546,7 @@
"rulesActions": "Actions", "rulesActions": "Actions",
"rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods",
"rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted",
"rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted",
"rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteria": "Matching Criteria",
"rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddress": "Match a specific IP address",
"rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Einstellungen", "resourceSetting": "{resourceName} Einstellungen",
"alwaysAllow": "Immer erlauben", "alwaysAllow": "Immer erlauben",
"alwaysDeny": "Immer ablehnen", "alwaysDeny": "Immer ablehnen",
"passToAuth": "Weiterleiten zur Authentifizierung",
"orgSettingsDescription": "Konfiguriere die allgemeinen Einstellungen deiner Organisation", "orgSettingsDescription": "Konfiguriere die allgemeinen Einstellungen deiner Organisation",
"orgGeneralSettings": "Organisations-Einstellungen", "orgGeneralSettings": "Organisations-Einstellungen",
"orgGeneralSettingsDescription": "Organisationsdetails und Konfiguration verwalten", "orgGeneralSettingsDescription": "Organisationsdetails und Konfiguration verwalten",
@@ -545,6 +546,7 @@
"rulesActions": "Aktionen", "rulesActions": "Aktionen",
"rulesActionAlwaysAllow": "Immer erlauben: Alle Authentifizierungsmethoden umgehen", "rulesActionAlwaysAllow": "Immer erlauben: Alle Authentifizierungsmethoden umgehen",
"rulesActionAlwaysDeny": "Immer verweigern: Alle Anfragen blockieren; keine Authentifizierung möglich", "rulesActionAlwaysDeny": "Immer verweigern: Alle Anfragen blockieren; keine Authentifizierung möglich",
"rulesActionPassToAuth": "Weiterleiten zur Authentifizierung: Erlaubt das Versuchen von Authentifizierungsmethoden",
"rulesMatchCriteria": "Übereinstimmungskriterien", "rulesMatchCriteria": "Übereinstimmungskriterien",
"rulesMatchCriteriaIpAddress": "Mit einer bestimmten IP-Adresse übereinstimmen", "rulesMatchCriteriaIpAddress": "Mit einer bestimmten IP-Adresse übereinstimmen",
"rulesMatchCriteriaIpAddressRange": "Mit einem IP-Adressbereich in CIDR-Notation übereinstimmen", "rulesMatchCriteriaIpAddressRange": "Mit einem IP-Adressbereich in CIDR-Notation übereinstimmen",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Settings", "resourceSetting": "{resourceName} Settings",
"alwaysAllow": "Always Allow", "alwaysAllow": "Always Allow",
"alwaysDeny": "Always Deny", "alwaysDeny": "Always Deny",
"passToAuth": "Pass to Auth",
"orgSettingsDescription": "Configure your organization's general settings", "orgSettingsDescription": "Configure your organization's general settings",
"orgGeneralSettings": "Organization Settings", "orgGeneralSettings": "Organization Settings",
"orgGeneralSettingsDescription": "Manage your organization details and configuration", "orgGeneralSettingsDescription": "Manage your organization details and configuration",
@@ -545,6 +546,7 @@
"rulesActions": "Actions", "rulesActions": "Actions",
"rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods", "rulesActionAlwaysAllow": "Always Allow: Bypass all authentication methods",
"rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted", "rulesActionAlwaysDeny": "Always Deny: Block all requests; no authentication can be attempted",
"rulesActionPassToAuth": "Pass to Auth: Allow authentication methods to be attempted",
"rulesMatchCriteria": "Matching Criteria", "rulesMatchCriteria": "Matching Criteria",
"rulesMatchCriteriaIpAddress": "Match a specific IP address", "rulesMatchCriteriaIpAddress": "Match a specific IP address",
"rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation", "rulesMatchCriteriaIpAddressRange": "Match a range of IP addresses in CIDR notation",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "Ajustes {resourceName}", "resourceSetting": "Ajustes {resourceName}",
"alwaysAllow": "Permitir siempre", "alwaysAllow": "Permitir siempre",
"alwaysDeny": "Denegar siempre", "alwaysDeny": "Denegar siempre",
"passToAuth": "Pasar a Autenticación",
"orgSettingsDescription": "Configurar la configuración general de su organización", "orgSettingsDescription": "Configurar la configuración general de su organización",
"orgGeneralSettings": "Configuración de la organización", "orgGeneralSettings": "Configuración de la organización",
"orgGeneralSettingsDescription": "Administra los detalles y la configuración de tu organización", "orgGeneralSettingsDescription": "Administra los detalles y la configuración de tu organización",
@@ -545,6 +546,7 @@
"rulesActions": "Acciones", "rulesActions": "Acciones",
"rulesActionAlwaysAllow": "Permitir siempre: pasar todos los métodos de autenticación", "rulesActionAlwaysAllow": "Permitir siempre: pasar todos los métodos de autenticación",
"rulesActionAlwaysDeny": "Denegar siempre: Bloquear todas las peticiones; no se puede intentar 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", "rulesMatchCriteria": "Criterios coincidentes",
"rulesMatchCriteriaIpAddress": "Coincidir con una dirección IP específica", "rulesMatchCriteriaIpAddress": "Coincidir con una dirección IP específica",
"rulesMatchCriteriaIpAddressRange": "Coincide con un rango de direcciones IP en notación CIDR", "rulesMatchCriteriaIpAddressRange": "Coincide con un rango de direcciones IP en notación CIDR",

View File

@@ -23,7 +23,7 @@
"inviteLoginUser": "Assurez-vous que vous êtes bien connecté en tant qu'utilisateur correct.", "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.", "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.", "inviteCreateUser": "Veuillez d'abord créer un compte.",
"goHome": "Aller à laccueil", "goHome": "Retour à la maison",
"inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent", "inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent",
"createAnAccount": "Créer un compte", "createAnAccount": "Créer un compte",
"inviteNotAccepted": "Invitation non acceptée", "inviteNotAccepted": "Invitation non acceptée",
@@ -34,16 +34,16 @@
"confirmPassword": "Confirmer le mot de passe", "confirmPassword": "Confirmer le mot de passe",
"createAccount": "Créer un compte", "createAccount": "Créer un compte",
"viewSettings": "Afficher les paramètres", "viewSettings": "Afficher les paramètres",
"delete": "Supprimer", "delete": "Supprimez",
"name": "Nom", "name": "Nom",
"online": "En ligne", "online": "En ligne",
"offline": "Hors ligne", "offline": "Hors ligne",
"site": "Site", "site": "Site",
"dataIn": "Données entrantes", "dataIn": "Données dans",
"dataOut": "Données sortantes", "dataOut": "Données épuisées",
"connectionType": "Type de connexion", "connectionType": "Type de connexion",
"tunnelType": "Type de tunnel", "tunnelType": "Type de tunnel",
"local": "Local", "local": "Locale",
"edit": "Editer", "edit": "Editer",
"siteConfirmDelete": "Confirmer la suppression du site", "siteConfirmDelete": "Confirmer la suppression du site",
"siteDelete": "Supprimer le site", "siteDelete": "Supprimer le site",
@@ -68,7 +68,7 @@
"toggle": "Activer/désactiver", "toggle": "Activer/désactiver",
"dockerCompose": "Composition Docker", "dockerCompose": "Composition Docker",
"dockerRun": "Exécution 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", "siteConfirmCopy": "J'ai copié la configuration",
"searchSitesProgress": "Rechercher des sites...", "searchSitesProgress": "Rechercher des sites...",
"siteAdd": "Ajouter un site", "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.", "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", "siteWg": "WireGuard basique",
"siteWgDescription": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise.", "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.", "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", "siteSeeAll": "Voir tous les sites",
"siteTunnelDescription": "Déterminez comment vous voulez vous connecter à votre site", "siteTunnelDescription": "Déterminez comment vous voulez vous connecter à votre site",
"siteNewtCredentials": "Identifiants Newt", "siteNewtCredentials": "Identifiants Newt",
@@ -132,7 +132,7 @@
"expireIn": "Expire dans", "expireIn": "Expire dans",
"neverExpire": "N'expire jamais", "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.", "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 quune 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.", "shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.",
"shareTokenUsage": "Voir Utilisation du jeton d'accès", "shareTokenUsage": "Voir Utilisation du jeton d'accès",
"createLink": "Créer un lien", "createLink": "Créer un lien",
@@ -140,7 +140,7 @@
"resourceSearch": "Rechercher des ressources", "resourceSearch": "Rechercher des ressources",
"openMenu": "Ouvrir le menu", "openMenu": "Ouvrir le menu",
"resource": "Ressource", "resource": "Ressource",
"title": "Titre", "title": "Titre de la page",
"created": "Créé", "created": "Créé",
"expires": "Expire", "expires": "Expire",
"never": "Jamais", "never": "Jamais",
@@ -196,7 +196,7 @@
"visibility": "Visibilité", "visibility": "Visibilité",
"enabled": "Activé", "enabled": "Activé",
"disabled": "Désactivé", "disabled": "Désactivé",
"general": "Général", "general": "Généraux",
"generalSettings": "Paramètres généraux", "generalSettings": "Paramètres généraux",
"proxy": "Proxy", "proxy": "Proxy",
"internal": "Interne", "internal": "Interne",
@@ -205,6 +205,7 @@
"resourceSetting": "Réglages {resourceName}", "resourceSetting": "Réglages {resourceName}",
"alwaysAllow": "Toujours autoriser", "alwaysAllow": "Toujours autoriser",
"alwaysDeny": "Toujours refuser", "alwaysDeny": "Toujours refuser",
"passToAuth": "Paser à l'authentification",
"orgSettingsDescription": "Configurer les paramètres généraux de votre organisation", "orgSettingsDescription": "Configurer les paramètres généraux de votre organisation",
"orgGeneralSettings": "Paramètres de l'organisation", "orgGeneralSettings": "Paramètres de l'organisation",
"orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation", "orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation",
@@ -545,6 +546,7 @@
"rulesActions": "Actions", "rulesActions": "Actions",
"rulesActionAlwaysAllow": "Toujours autoriser : Contourner toutes les méthodes d'authentification", "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", "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", "rulesMatchCriteria": "Critères de correspondance",
"rulesMatchCriteriaIpAddress": "Correspondre à une adresse IP spécifique", "rulesMatchCriteriaIpAddress": "Correspondre à une adresse IP spécifique",
"rulesMatchCriteriaIpAddressRange": "Correspondre à une plage d'adresses IP en notation CIDR", "rulesMatchCriteriaIpAddressRange": "Correspondre à une plage d'adresses IP en notation CIDR",
@@ -593,7 +595,7 @@
"newtId": "ID Newt", "newtId": "ID Newt",
"newtSecretKey": "Clé secrète Newt", "newtSecretKey": "Clé secrète Newt",
"architecture": "Architecture", "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.", "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", "siteWgCompatibleAllClients": "Compatible avec tous les clients WireGuard",
"siteWgManualConfigurationRequired": "Configuration manuelle requise", "siteWgManualConfigurationRequired": "Configuration manuelle requise",
@@ -959,7 +961,7 @@
"supportKetOptionFull": "Support complet", "supportKetOptionFull": "Support complet",
"forWholeServer": "Pour tout le serveur", "forWholeServer": "Pour tout le serveur",
"lifetimePurchase": "Achat à vie", "lifetimePurchase": "Achat à vie",
"supporterStatus": "Statut de supporteur", "supporterStatus": "Statut de supporter",
"buy": "Acheter", "buy": "Acheter",
"supportKeyOptionLimited": "Support limité", "supportKeyOptionLimited": "Support limité",
"forFiveUsers": "Pour 5 utilisateurs ou moins", "forFiveUsers": "Pour 5 utilisateurs ou moins",
@@ -1103,7 +1105,7 @@
"allowAll": "Tout autoriser", "allowAll": "Tout autoriser",
"permissionsAllowAll": "Autoriser toutes les autorisations", "permissionsAllowAll": "Autoriser toutes les autorisations",
"githubUsernameRequired": "Le nom d'utilisateur GitHub est requis", "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", "passwordRequirementsChars": "Le mot de passe doit comporter au moins 8 caractères",
"language": "Langue", "language": "Langue",
"verificationCodeRequired": "Le code est requis", "verificationCodeRequired": "Le code est requis",
@@ -1115,14 +1117,14 @@
"orgErrorNoProvided": "Aucune organisation fournie", "orgErrorNoProvided": "Aucune organisation fournie",
"apiKeysErrorNoUpdate": "Pas de clé API à mettre à jour", "apiKeysErrorNoUpdate": "Pas de clé API à mettre à jour",
"sidebarOverview": "Aperçu", "sidebarOverview": "Aperçu",
"sidebarHome": "Accueil", "sidebarHome": "Domicile",
"sidebarSites": "Sites", "sidebarSites": "Espaces",
"sidebarResources": "Ressources", "sidebarResources": "Ressource",
"sidebarAccessControl": "Contrôle d'accès", "sidebarAccessControl": "Contrôle d'accès",
"sidebarUsers": "Utilisateurs", "sidebarUsers": "Utilisateurs",
"sidebarInvitations": "Invitations", "sidebarInvitations": "Invitations",
"sidebarRoles": "Rôles", "sidebarRoles": "Rôles",
"sidebarShareableLinks": "Liens partageables", "sidebarShareableLinks": "Liens partagables",
"sidebarApiKeys": "Clés API", "sidebarApiKeys": "Clés API",
"sidebarSettings": "Réglages", "sidebarSettings": "Réglages",
"sidebarAllUsers": "Tous les utilisateurs", "sidebarAllUsers": "Tous les utilisateurs",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "Impostazioni {resourceName}", "resourceSetting": "Impostazioni {resourceName}",
"alwaysAllow": "Consenti Sempre", "alwaysAllow": "Consenti Sempre",
"alwaysDeny": "Nega Sempre", "alwaysDeny": "Nega Sempre",
"passToAuth": "Passa all'autenticazione",
"orgSettingsDescription": "Configura le impostazioni generali della tua organizzazione", "orgSettingsDescription": "Configura le impostazioni generali della tua organizzazione",
"orgGeneralSettings": "Impostazioni Organizzazione", "orgGeneralSettings": "Impostazioni Organizzazione",
"orgGeneralSettingsDescription": "Gestisci i dettagli dell'organizzazione e la configurazione", "orgGeneralSettingsDescription": "Gestisci i dettagli dell'organizzazione e la configurazione",
@@ -545,6 +546,7 @@
"rulesActions": "Azioni", "rulesActions": "Azioni",
"rulesActionAlwaysAllow": "Consenti Sempre: Ignora tutti i metodi di autenticazione", "rulesActionAlwaysAllow": "Consenti Sempre: Ignora tutti i metodi di autenticazione",
"rulesActionAlwaysDeny": "Nega Sempre: Blocca tutte le richieste; nessuna autenticazione può essere tentata", "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", "rulesMatchCriteria": "Criteri di Corrispondenza",
"rulesMatchCriteriaIpAddress": "Corrisponde a un indirizzo IP specifico", "rulesMatchCriteriaIpAddress": "Corrisponde a un indirizzo IP specifico",
"rulesMatchCriteriaIpAddressRange": "Corrisponde a un intervallo di indirizzi IP in notazione CIDR", "rulesMatchCriteriaIpAddressRange": "Corrisponde a un intervallo di indirizzi IP in notazione CIDR",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} 설정", "resourceSetting": "{resourceName} 설정",
"alwaysAllow": "항상 허용", "alwaysAllow": "항상 허용",
"alwaysDeny": "항상 거부", "alwaysDeny": "항상 거부",
"passToAuth": "인증으로 전달",
"orgSettingsDescription": "조직의 일반 설정을 구성하세요", "orgSettingsDescription": "조직의 일반 설정을 구성하세요",
"orgGeneralSettings": "조직 설정", "orgGeneralSettings": "조직 설정",
"orgGeneralSettingsDescription": "조직 세부정보 및 구성을 관리하세요.", "orgGeneralSettingsDescription": "조직 세부정보 및 구성을 관리하세요.",
@@ -545,6 +546,7 @@
"rulesActions": "작업", "rulesActions": "작업",
"rulesActionAlwaysAllow": "항상 허용: 모든 인증 방법 우회", "rulesActionAlwaysAllow": "항상 허용: 모든 인증 방법 우회",
"rulesActionAlwaysDeny": "항상 거부: 모든 요청을 차단합니다. 인증을 시도할 수 없습니다.", "rulesActionAlwaysDeny": "항상 거부: 모든 요청을 차단합니다. 인증을 시도할 수 없습니다.",
"rulesActionPassToAuth": "인증으로 전달: 인증 방법 시도를 허용합니다",
"rulesMatchCriteria": "일치 기준", "rulesMatchCriteria": "일치 기준",
"rulesMatchCriteriaIpAddress": "특정 IP 주소와 일치", "rulesMatchCriteriaIpAddress": "특정 IP 주소와 일치",
"rulesMatchCriteriaIpAddressRange": "CIDR 표기법으로 IP 주소 범위를 일치시킵니다", "rulesMatchCriteriaIpAddressRange": "CIDR 표기법으로 IP 주소 범위를 일치시킵니다",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Innstillinger", "resourceSetting": "{resourceName} Innstillinger",
"alwaysAllow": "Alltid tillat", "alwaysAllow": "Alltid tillat",
"alwaysDeny": "Alltid avslå", "alwaysDeny": "Alltid avslå",
"passToAuth": "Pass til Autentisering",
"orgSettingsDescription": "Konfigurer organisasjonens generelle innstillinger", "orgSettingsDescription": "Konfigurer organisasjonens generelle innstillinger",
"orgGeneralSettings": "Organisasjonsinnstillinger", "orgGeneralSettings": "Organisasjonsinnstillinger",
"orgGeneralSettingsDescription": "Administrer dine organisasjonsdetaljer og konfigurasjon", "orgGeneralSettingsDescription": "Administrer dine organisasjonsdetaljer og konfigurasjon",
@@ -545,6 +546,7 @@
"rulesActions": "Handlinger", "rulesActions": "Handlinger",
"rulesActionAlwaysAllow": "Alltid Tillat: Omgå alle autentiserings metoder", "rulesActionAlwaysAllow": "Alltid Tillat: Omgå alle autentiserings metoder",
"rulesActionAlwaysDeny": "Alltid Nekt: Blokker alle forespørsler; ingen autentisering kan forsøkes", "rulesActionAlwaysDeny": "Alltid Nekt: Blokker alle forespørsler; ingen autentisering kan forsøkes",
"rulesActionPassToAuth": "Pass til Autentisering: Tillat at autentiseringsmetoder forsøkes",
"rulesMatchCriteria": "Samsvarende kriterier", "rulesMatchCriteria": "Samsvarende kriterier",
"rulesMatchCriteriaIpAddress": "Samsvar med en spesifikk IP-adresse", "rulesMatchCriteriaIpAddress": "Samsvar med en spesifikk IP-adresse",
"rulesMatchCriteriaIpAddressRange": "Samsvar et IP-adresseområde i CIDR-notasjon", "rulesMatchCriteriaIpAddressRange": "Samsvar et IP-adresseområde i CIDR-notasjon",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} instellingen", "resourceSetting": "{resourceName} instellingen",
"alwaysAllow": "Altijd toestaan", "alwaysAllow": "Altijd toestaan",
"alwaysDeny": "Altijd weigeren", "alwaysDeny": "Altijd weigeren",
"passToAuth": "Passeren naar Auth",
"orgSettingsDescription": "Configureer de algemene instellingen van je organisatie", "orgSettingsDescription": "Configureer de algemene instellingen van je organisatie",
"orgGeneralSettings": "Organisatie Instellingen", "orgGeneralSettings": "Organisatie Instellingen",
"orgGeneralSettingsDescription": "Beheer de details en configuratie van uw organisatie", "orgGeneralSettingsDescription": "Beheer de details en configuratie van uw organisatie",
@@ -545,6 +546,7 @@
"rulesActions": "acties", "rulesActions": "acties",
"rulesActionAlwaysAllow": "Altijd toegestaan: Omzeil alle authenticatiemethoden", "rulesActionAlwaysAllow": "Altijd toegestaan: Omzeil alle authenticatiemethoden",
"rulesActionAlwaysDeny": "Altijd weigeren: Blokkeer alle aanvragen, er kan geen verificatie worden geprobeerd", "rulesActionAlwaysDeny": "Altijd weigeren: Blokkeer alle aanvragen, er kan geen verificatie worden geprobeerd",
"rulesActionPassToAuth": "Doorgeven aan Auth: Toestaan dat authenticatiemethoden worden geprobeerd",
"rulesMatchCriteria": "Overeenkomende criteria", "rulesMatchCriteria": "Overeenkomende criteria",
"rulesMatchCriteriaIpAddress": "Overeenkomen met een specifiek IP-adres", "rulesMatchCriteriaIpAddress": "Overeenkomen met een specifiek IP-adres",
"rulesMatchCriteriaIpAddressRange": "Overeenkomen met een bereik van IP-adressen in de CIDR-notatie", "rulesMatchCriteriaIpAddressRange": "Overeenkomen met een bereik van IP-adressen in de CIDR-notatie",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "Ustawienia {resourceName}", "resourceSetting": "Ustawienia {resourceName}",
"alwaysAllow": "Zawsze zezwalaj", "alwaysAllow": "Zawsze zezwalaj",
"alwaysDeny": "Zawsze odmawiaj", "alwaysDeny": "Zawsze odmawiaj",
"passToAuth": "Przekaż do Autoryzacji",
"orgSettingsDescription": "Skonfiguruj ustawienia ogólne swojej organizacji", "orgSettingsDescription": "Skonfiguruj ustawienia ogólne swojej organizacji",
"orgGeneralSettings": "Ustawienia organizacji", "orgGeneralSettings": "Ustawienia organizacji",
"orgGeneralSettingsDescription": "Zarządzaj szczegółami swojej organizacji i konfiguracją", "orgGeneralSettingsDescription": "Zarządzaj szczegółami swojej organizacji i konfiguracją",
@@ -545,6 +546,7 @@
"rulesActions": "Akcje", "rulesActions": "Akcje",
"rulesActionAlwaysAllow": "Zawsze zezwalaj: Pomiń wszystkie metody uwierzytelniania", "rulesActionAlwaysAllow": "Zawsze zezwalaj: Pomiń wszystkie metody uwierzytelniania",
"rulesActionAlwaysDeny": "Zawsze odmawiaj: Blokuj wszystkie żądania; nie można próbować 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", "rulesMatchCriteria": "Kryteria dopasowania",
"rulesMatchCriteriaIpAddress": "Dopasuj konkretny adres IP", "rulesMatchCriteriaIpAddress": "Dopasuj konkretny adres IP",
"rulesMatchCriteriaIpAddressRange": "Dopasuj zakres adresów IP w notacji CIDR", "rulesMatchCriteriaIpAddressRange": "Dopasuj zakres adresów IP w notacji CIDR",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "Configurações do {resourceName}", "resourceSetting": "Configurações do {resourceName}",
"alwaysAllow": "Sempre permitir", "alwaysAllow": "Sempre permitir",
"alwaysDeny": "Sempre negar", "alwaysDeny": "Sempre negar",
"passToAuth": "Passar para Autenticação",
"orgSettingsDescription": "Configurar as configurações gerais da sua organização", "orgSettingsDescription": "Configurar as configurações gerais da sua organização",
"orgGeneralSettings": "Configurações da organização", "orgGeneralSettings": "Configurações da organização",
"orgGeneralSettingsDescription": "Gerencie os detalhes e a configuração da sua organização", "orgGeneralSettingsDescription": "Gerencie os detalhes e a configuração da sua organização",
@@ -545,6 +546,7 @@
"rulesActions": "Ações", "rulesActions": "Ações",
"rulesActionAlwaysAllow": "Sempre Permitir: Ignorar todos os métodos de autenticação", "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", "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", "rulesMatchCriteria": "Critérios de Correspondência",
"rulesMatchCriteriaIpAddress": "Corresponder a um endereço IP específico", "rulesMatchCriteriaIpAddress": "Corresponder a um endereço IP específico",
"rulesMatchCriteriaIpAddressRange": "Corresponder a uma faixa de endereços IP em notação CIDR", "rulesMatchCriteriaIpAddressRange": "Corresponder a uma faixa de endereços IP em notação CIDR",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "Настройки {resourceName}", "resourceSetting": "Настройки {resourceName}",
"alwaysAllow": "Всегда разрешать", "alwaysAllow": "Всегда разрешать",
"alwaysDeny": "Всегда запрещать", "alwaysDeny": "Всегда запрещать",
"passToAuth": "Переход к аутентификации",
"orgSettingsDescription": "Настройте общие параметры вашей организации", "orgSettingsDescription": "Настройте общие параметры вашей организации",
"orgGeneralSettings": "Настройки организации", "orgGeneralSettings": "Настройки организации",
"orgGeneralSettingsDescription": "Управляйте данными и конфигурацией вашей организации", "orgGeneralSettingsDescription": "Управляйте данными и конфигурацией вашей организации",
@@ -545,6 +546,7 @@
"rulesActions": "Действия", "rulesActions": "Действия",
"rulesActionAlwaysAllow": "Всегда разрешать: Обойти все методы аутентификации", "rulesActionAlwaysAllow": "Всегда разрешать: Обойти все методы аутентификации",
"rulesActionAlwaysDeny": "Всегда запрещать: Блокировать все запросы; аутентификация не может быть выполнена", "rulesActionAlwaysDeny": "Всегда запрещать: Блокировать все запросы; аутентификация не может быть выполнена",
"rulesActionPassToAuth": "Переход к аутентификации: Разрешить попытки методов аутентификации",
"rulesMatchCriteria": "Критерии совпадения", "rulesMatchCriteria": "Критерии совпадения",
"rulesMatchCriteriaIpAddress": "Совпадение с конкретным IP адресом", "rulesMatchCriteriaIpAddress": "Совпадение с конкретным IP адресом",
"rulesMatchCriteriaIpAddressRange": "Совпадение с диапазоном IP адресов в нотации CIDR", "rulesMatchCriteriaIpAddressRange": "Совпадение с диапазоном IP адресов в нотации CIDR",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} Ayarları", "resourceSetting": "{resourceName} Ayarları",
"alwaysAllow": "Her Zaman İzin Ver", "alwaysAllow": "Her Zaman İzin Ver",
"alwaysDeny": "Her Zaman Reddet", "alwaysDeny": "Her Zaman Reddet",
"passToAuth": "Kimlik Doğrulamasına Geç",
"orgSettingsDescription": "Organizasyonunuzun genel ayarlarını yapılandırın", "orgSettingsDescription": "Organizasyonunuzun genel ayarlarını yapılandırın",
"orgGeneralSettings": "Organizasyon Ayarları", "orgGeneralSettings": "Organizasyon Ayarları",
"orgGeneralSettingsDescription": "Organizasyon detaylarınızı ve yapılandırmanızı yönetin", "orgGeneralSettingsDescription": "Organizasyon detaylarınızı ve yapılandırmanızı yönetin",
@@ -545,6 +546,7 @@
"rulesActions": "Aksiyonlar", "rulesActions": "Aksiyonlar",
"rulesActionAlwaysAllow": "Her Zaman İzin Ver: Tüm kimlik doğrulama yöntemlerini atlayın", "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", "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", "rulesMatchCriteria": "Eşleşme Kriterleri",
"rulesMatchCriteriaIpAddress": "Belirli bir IP adresi ile eşleşme", "rulesMatchCriteriaIpAddress": "Belirli bir IP adresi ile eşleşme",
"rulesMatchCriteriaIpAddressRange": "CIDR gösteriminde bir IP adresi aralığı ile eşleşme", "rulesMatchCriteriaIpAddressRange": "CIDR gösteriminde bir IP adresi aralığı ile eşleşme",

View File

@@ -205,6 +205,7 @@
"resourceSetting": "{resourceName} 设置", "resourceSetting": "{resourceName} 设置",
"alwaysAllow": "一律允许", "alwaysAllow": "一律允许",
"alwaysDeny": "一律拒绝", "alwaysDeny": "一律拒绝",
"passToAuth": "传递至认证",
"orgSettingsDescription": "配置您组织的一般设置", "orgSettingsDescription": "配置您组织的一般设置",
"orgGeneralSettings": "组织设置", "orgGeneralSettings": "组织设置",
"orgGeneralSettingsDescription": "管理您的机构详细信息和配置", "orgGeneralSettingsDescription": "管理您的机构详细信息和配置",
@@ -545,6 +546,7 @@
"rulesActions": "行动", "rulesActions": "行动",
"rulesActionAlwaysAllow": "总是允许:绕过所有身份验证方法", "rulesActionAlwaysAllow": "总是允许:绕过所有身份验证方法",
"rulesActionAlwaysDeny": "总是拒绝:阻止所有请求;无法尝试验证", "rulesActionAlwaysDeny": "总是拒绝:阻止所有请求;无法尝试验证",
"rulesActionPassToAuth": "传递至认证:允许尝试身份验证方法",
"rulesMatchCriteria": "匹配条件", "rulesMatchCriteria": "匹配条件",
"rulesMatchCriteriaIpAddress": "匹配一个指定的 IP 地址", "rulesMatchCriteriaIpAddress": "匹配一个指定的 IP 地址",
"rulesMatchCriteriaIpAddressRange": "在 CIDR 符号中匹配一系列IP地址", "rulesMatchCriteriaIpAddressRange": "在 CIDR 符号中匹配一系列IP地址",

View File

@@ -430,7 +430,7 @@ export const resourceRules = pgTable("resourceRules", {
.references(() => resources.resourceId, { onDelete: "cascade" }), .references(() => resources.resourceId, { onDelete: "cascade" }),
enabled: boolean("enabled").notNull().default(true), enabled: boolean("enabled").notNull().default(true),
priority: integer("priority").notNull(), priority: integer("priority").notNull(),
action: varchar("action").notNull(), // ACCEPT, DROP action: varchar("action").notNull(), // ACCEPT, DROP, PASS
match: varchar("match").notNull(), // CIDR, PATH, IP match: varchar("match").notNull(), // CIDR, PATH, IP
value: varchar("value").notNull() value: varchar("value").notNull()
}); });

View File

@@ -570,7 +570,7 @@ export const resourceRules = sqliteTable("resourceRules", {
.references(() => resources.resourceId, { onDelete: "cascade" }), .references(() => resources.resourceId, { onDelete: "cascade" }),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
priority: integer("priority").notNull(), priority: integer("priority").notNull(),
action: text("action").notNull(), // ACCEPT, DROP action: text("action").notNull(), // ACCEPT, DROP, PASS
match: text("match").notNull(), // CIDR, PATH, IP match: text("match").notNull(), // CIDR, PATH, IP
value: text("value").notNull() value: text("value").notNull()
}); });

32
server/lib/geoip.ts Normal file
View File

@@ -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<string | undefined> {
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;
}

View File

@@ -1,2 +1,3 @@
export * from "./response"; export * from "./response";
export { tokenManager, TokenManager } from "./tokenManager"; export { tokenManager, TokenManager } from "./tokenManager";
export * from "./geoip";

View File

@@ -5,7 +5,6 @@ import {
validateResourceSessionToken validateResourceSessionToken
} from "@server/auth/sessions/resource"; } from "@server/auth/sessions/resource";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
import { db } from "@server/db";
import { import {
getResourceByDomain, getResourceByDomain,
getUserSessionWithUser, getUserSessionWithUser,
@@ -33,6 +32,7 @@ import createHttpError from "http-errors";
import NodeCache from "node-cache"; import NodeCache from "node-cache";
import { z } from "zod"; import { z } from "zod";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { getCountryCodeForIp } from "@server/lib";
// We'll see if this speeds anything up // We'll see if this speeds anything up
const cache = new NodeCache({ const cache = new NodeCache({
@@ -123,7 +123,7 @@ export async function verifyResourceSession(
let cleanHost = host; let cleanHost = host;
// if the host ends with :port, strip it // if the host ends with :port, strip it
if (cleanHost.match(/:[0-9]{1,5}$/)) { if (cleanHost.match(/:[0-9]{1,5}$/)) {
const matched = ''+cleanHost.match(/:[0-9]{1,5}$/); const matched = "" + cleanHost.match(/:[0-9]{1,5}$/);
cleanHost = cleanHost.slice(0, -1 * matched.length); cleanHost = cleanHost.slice(0, -1 * matched.length);
} }
@@ -176,6 +176,11 @@ export async function verifyResourceSession(
} else if (action == "DROP") { } else if (action == "DROP") {
logger.debug("Resource denied by rule"); logger.debug("Resource denied by rule");
return notAllowed(res); 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 // otherwise its undefined and we pass
@@ -193,7 +198,10 @@ export async function verifyResourceSession(
let endpoint: string; let endpoint: string;
if (config.isManagedMode()) { if (config.isManagedMode()) {
endpoint = config.getRawConfig().managed?.redirect_endpoint || config.getRawConfig().managed?.endpoint || ""; endpoint =
config.getRawConfig().managed?.redirect_endpoint ||
config.getRawConfig().managed?.endpoint ||
"";
} else { } else {
endpoint = config.getRawConfig().app.dashboard_url!; endpoint = config.getRawConfig().app.dashboard_url!;
} }
@@ -576,7 +584,7 @@ async function checkRules(
resourceId: number, resourceId: number,
clientIp: string | undefined, clientIp: string | undefined,
path: string | undefined path: string | undefined
): Promise<"ACCEPT" | "DROP" | undefined> { ): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> {
const ruleCacheKey = `rules:${resourceId}`; const ruleCacheKey = `rules:${resourceId}`;
let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey); let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey);
@@ -613,6 +621,12 @@ async function checkRules(
isPathAllowed(rule.value, path) isPathAllowed(rule.value, path)
) { ) {
return rule.action as any; 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}`); logger.debug(`Final result: ${result}`);
return result; return result;
} }
async function isIpInGeoIP(ip: string, countryCode: string): Promise<boolean> {
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();
}

View File

@@ -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;
}

View File

@@ -13,6 +13,8 @@ import { fromError } from "zod-validation-error";
import { getAllowedIps } from "../target/helpers"; import { getAllowedIps } from "../target/helpers";
import { proxyToRemote } from "@server/lib/remoteProxy"; import { proxyToRemote } from "@server/lib/remoteProxy";
import { getNextAvailableSubnet } from "@server/lib/exitNodes"; import { getNextAvailableSubnet } from "@server/lib/exitNodes";
import { createExitNode } from "./createExitNode";
// Define Zod schema for request validation // Define Zod schema for request validation
const getConfigSchema = z.object({ const getConfigSchema = z.object({
publicKey: z.string(), publicKey: z.string(),
@@ -53,46 +55,7 @@ export async function getConfig(
); );
} }
// Fetch exit node const exitNode = await createExitNode(publicKey, reachableAt);
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;
}
if (!exitNode) { if (!exitNode) {
return next( return next(
@@ -107,13 +70,13 @@ export async function getConfig(
if (config.isManagedMode()) { if (config.isManagedMode()) {
req.body = { req.body = {
...req.body, ...req.body,
endpoint: exitNode[0].endpoint, endpoint: exitNode.endpoint,
listenPort: exitNode[0].listenPort listenPort: exitNode.listenPort
}; };
return proxyToRemote(req, res, next, "hybrid/gerbil/get-config"); 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); logger.debug("Sending config: ", configResponse);

View File

@@ -17,7 +17,7 @@ import { OpenAPITags, registry } from "@server/openApi";
const createResourceRuleSchema = z const createResourceRuleSchema = z
.object({ .object({
action: z.enum(["ACCEPT", "DROP"]), action: z.enum(["ACCEPT", "DROP", "PASS"]),
match: z.enum(["CIDR", "IP", "PATH"]), match: z.enum(["CIDR", "IP", "PATH"]),
value: z.string().min(1), value: z.string().min(1),
priority: z.number().int(), priority: z.number().int(),

View File

@@ -29,7 +29,7 @@ const updateResourceRuleParamsSchema = z
// Define Zod schema for request body validation // Define Zod schema for request body validation
const updateResourceRuleSchema = z const updateResourceRuleSchema = z
.object({ .object({
action: z.enum(["ACCEPT", "DROP"]).optional(), action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(),
match: z.enum(["CIDR", "IP", "PATH"]).optional(), match: z.enum(["CIDR", "IP", "PATH"]).optional(),
value: z.string().min(1).optional(), value: z.string().min(1).optional(),
priority: z.number().int(), priority: z.number().int(),

View File

@@ -76,7 +76,7 @@ import { useTranslations } from "next-intl";
// Schema for rule validation // Schema for rule validation
const addRuleSchema = z.object({ const addRuleSchema = z.object({
action: z.string(), action: z.enum(["ACCEPT", "DROP", "PASS"]),
match: z.string(), match: z.string(),
value: z.string(), value: z.string(),
priority: z.coerce.number().int().optional() priority: z.coerce.number().int().optional()
@@ -104,7 +104,8 @@ export default function ResourceRules(props: {
const RuleAction = { const RuleAction = {
ACCEPT: t('alwaysAllow'), ACCEPT: t('alwaysAllow'),
DROP: t('alwaysDeny') DROP: t('alwaysDeny'),
PASS: t('passToAuth')
} as const; } as const;
const RuleMatch = { const RuleMatch = {
@@ -113,7 +114,7 @@ export default function ResourceRules(props: {
CIDR: t('ipAddressRange') CIDR: t('ipAddressRange')
} as const; } as const;
const addRuleForm = useForm({ const addRuleForm = useForm<z.infer<typeof addRuleSchema>>({
resolver: zodResolver(addRuleSchema), resolver: zodResolver(addRuleSchema),
defaultValues: { defaultValues: {
action: "ACCEPT", action: "ACCEPT",
@@ -437,7 +438,7 @@ export default function ResourceRules(props: {
cell: ({ row }) => ( cell: ({ row }) => (
<Select <Select
defaultValue={row.original.action} defaultValue={row.original.action}
onValueChange={(value: "ACCEPT" | "DROP") => onValueChange={(value: "ACCEPT" | "DROP" | "PASS") =>
updateRule(row.original.ruleId, { action: value }) updateRule(row.original.ruleId, { action: value })
} }
> >
@@ -449,6 +450,7 @@ export default function ResourceRules(props: {
{RuleAction.ACCEPT} {RuleAction.ACCEPT}
</SelectItem> </SelectItem>
<SelectItem value="DROP">{RuleAction.DROP}</SelectItem> <SelectItem value="DROP">{RuleAction.DROP}</SelectItem>
<SelectItem value="PASS">{RuleAction.PASS}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
) )
@@ -629,6 +631,9 @@ export default function ResourceRules(props: {
<SelectItem value="DROP"> <SelectItem value="DROP">
{RuleAction.DROP} {RuleAction.DROP}
</SelectItem> </SelectItem>
<SelectItem value="PASS">
{RuleAction.PASS}
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</FormControl> </FormControl>