mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-18 10:56:38 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c9b445be6 | ||
|
|
06b17fa941 | ||
|
|
e1d4c029e7 | ||
|
|
293fd70ccb | ||
|
|
4ee863db5a | ||
|
|
2717be0fed | ||
|
|
1f312e146f | ||
|
|
b91557ebb0 | ||
|
|
465380b5a3 | ||
|
|
60af901feb | ||
|
|
ea78a654ff | ||
|
|
f28b6ad0a5 | ||
|
|
a3bdab1318 | ||
|
|
f8c5d01e3c | ||
|
|
3ebe218b7f | ||
|
|
7d039ab729 | ||
|
|
b2b6c8c268 | ||
|
|
4950f25063 | ||
|
|
524d6b48d9 | ||
|
|
29fb5735e2 | ||
|
|
247fc85440 | ||
|
|
2b4302572c | ||
|
|
9b28780e62 | ||
|
|
8656f68008 | ||
|
|
15651b6919 | ||
|
|
adbcd1a2e0 | ||
|
|
5b7727fab4 | ||
|
|
9627dfa90c |
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Update Client",
|
"actionUpdateClient": "Update Client",
|
||||||
"actionListClients": "List Clients",
|
"actionListClients": "List Clients",
|
||||||
"actionGetClient": "Get Client",
|
"actionGetClient": "Get Client",
|
||||||
|
"actionCreateSiteResource": "Create Site Resource",
|
||||||
|
"actionDeleteSiteResource": "Delete Site Resource",
|
||||||
|
"actionGetSiteResource": "Get Site Resource",
|
||||||
|
"actionListSiteResources": "List Site Resources",
|
||||||
|
"actionUpdateSiteResource": "Update Site Resource",
|
||||||
"noneSelected": "None selected",
|
"noneSelected": "None selected",
|
||||||
"orgNotFound2": "No organizations found.",
|
"orgNotFound2": "No organizations found.",
|
||||||
"searchProgress": "Search...",
|
"searchProgress": "Search...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Update Client",
|
"actionUpdateClient": "Update Client",
|
||||||
"actionListClients": "List Clients",
|
"actionListClients": "List Clients",
|
||||||
"actionGetClient": "Get Client",
|
"actionGetClient": "Get Client",
|
||||||
|
"actionCreateSiteResource": "Create Site Resource",
|
||||||
|
"actionDeleteSiteResource": "Delete Site Resource",
|
||||||
|
"actionGetSiteResource": "Get Site Resource",
|
||||||
|
"actionListSiteResources": "List Site Resources",
|
||||||
|
"actionUpdateSiteResource": "Update Site Resource",
|
||||||
"noneSelected": "None selected",
|
"noneSelected": "None selected",
|
||||||
"orgNotFound2": "No organizations found.",
|
"orgNotFound2": "No organizations found.",
|
||||||
"searchProgress": "Search...",
|
"searchProgress": "Search...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Kunde aktualisieren",
|
"actionUpdateClient": "Kunde aktualisieren",
|
||||||
"actionListClients": "Kunden auflisten",
|
"actionListClients": "Kunden auflisten",
|
||||||
"actionGetClient": "Kunde holen",
|
"actionGetClient": "Kunde holen",
|
||||||
|
"actionCreateSiteResource": "Site-Ressource erstellen",
|
||||||
|
"actionDeleteSiteResource": "Site-Ressource löschen",
|
||||||
|
"actionGetSiteResource": "Site-Ressource abrufen",
|
||||||
|
"actionListSiteResources": "Site-Ressourcen auflisten",
|
||||||
|
"actionUpdateSiteResource": "Site-Ressource aktualisieren",
|
||||||
"noneSelected": "Keine ausgewählt",
|
"noneSelected": "Keine ausgewählt",
|
||||||
"orgNotFound2": "Keine Organisationen gefunden.",
|
"orgNotFound2": "Keine Organisationen gefunden.",
|
||||||
"searchProgress": "Suche...",
|
"searchProgress": "Suche...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Update Client",
|
"actionUpdateClient": "Update Client",
|
||||||
"actionListClients": "List Clients",
|
"actionListClients": "List Clients",
|
||||||
"actionGetClient": "Get Client",
|
"actionGetClient": "Get Client",
|
||||||
|
"actionCreateSiteResource": "Create Site Resource",
|
||||||
|
"actionDeleteSiteResource": "Delete Site Resource",
|
||||||
|
"actionGetSiteResource": "Get Site Resource",
|
||||||
|
"actionListSiteResources": "List Site Resources",
|
||||||
|
"actionUpdateSiteResource": "Update Site Resource",
|
||||||
"noneSelected": "None selected",
|
"noneSelected": "None selected",
|
||||||
"orgNotFound2": "No organizations found.",
|
"orgNotFound2": "No organizations found.",
|
||||||
"searchProgress": "Search...",
|
"searchProgress": "Search...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Actualizar cliente",
|
"actionUpdateClient": "Actualizar cliente",
|
||||||
"actionListClients": "Listar clientes",
|
"actionListClients": "Listar clientes",
|
||||||
"actionGetClient": "Obtener cliente",
|
"actionGetClient": "Obtener cliente",
|
||||||
|
"actionCreateSiteResource": "Crear Recurso del Sitio",
|
||||||
|
"actionDeleteSiteResource": "Eliminar recurso del sitio",
|
||||||
|
"actionGetSiteResource": "Obtener recurso del sitio",
|
||||||
|
"actionListSiteResources": "Listar recursos del sitio",
|
||||||
|
"actionUpdateSiteResource": "Actualizar recurso del sitio",
|
||||||
"noneSelected": "Ninguno seleccionado",
|
"noneSelected": "Ninguno seleccionado",
|
||||||
"orgNotFound2": "No se encontraron organizaciones.",
|
"orgNotFound2": "No se encontraron organizaciones.",
|
||||||
"searchProgress": "Buscar...",
|
"searchProgress": "Buscar...",
|
||||||
|
|||||||
@@ -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": "Retour à la maison",
|
"goHome": "Aller à l’accueil",
|
||||||
"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": "Supprimez",
|
"delete": "Supprimer",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"online": "En ligne",
|
"online": "En ligne",
|
||||||
"offline": "Hors ligne",
|
"offline": "Hors ligne",
|
||||||
"site": "Site",
|
"site": "Site",
|
||||||
"dataIn": "Données dans",
|
"dataIn": "Données entrantes",
|
||||||
"dataOut": "Données épuisées",
|
"dataOut": "Données sortantes",
|
||||||
"connectionType": "Type de connexion",
|
"connectionType": "Type de connexion",
|
||||||
"tunnelType": "Type de tunnel",
|
"tunnelType": "Type de tunnel",
|
||||||
"local": "Locale",
|
"local": "Local",
|
||||||
"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, en savoir plus",
|
"siteLearnLocal": "Les sites locaux ne tunnel plus, 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. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES",
|
"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",
|
||||||
"siteLocalDescription": "Ressources locales seulement. Pas de tunneling.",
|
"siteLocalDescription": "Ressources locales seulement. Pas de tunneling.",
|
||||||
"siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES",
|
"siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. NE FONCTIONNE QUE SUR LES NŒUDS AUTO-HÉBERGÉS",
|
||||||
"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. Assurez-vous de le copier.",
|
"shareSeeOnce": "Vous ne pourrez voir ce lien qu’une seule fois. 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 de la page",
|
"title": "Titre",
|
||||||
"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éraux",
|
"general": "Général",
|
||||||
"generalSettings": "Paramètres généraux",
|
"generalSettings": "Paramètres généraux",
|
||||||
"proxy": "Proxy",
|
"proxy": "Proxy",
|
||||||
"internal": "Interne",
|
"internal": "Interne",
|
||||||
@@ -593,7 +593,7 @@
|
|||||||
"newtId": "ID Newt",
|
"newtId": "ID Newt",
|
||||||
"newtSecretKey": "Clé secrète Newt",
|
"newtSecretKey": "Clé secrète Newt",
|
||||||
"architecture": "Architecture",
|
"architecture": "Architecture",
|
||||||
"sites": "Espaces",
|
"sites": "Sites",
|
||||||
"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 +959,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 supporter",
|
"supporterStatus": "Statut de supporteur",
|
||||||
"buy": "Acheter",
|
"buy": "Acheter",
|
||||||
"supportKeyOptionLimited": "Support limité",
|
"supportKeyOptionLimited": "Support limité",
|
||||||
"forFiveUsers": "Pour 5 utilisateurs ou moins",
|
"forFiveUsers": "Pour 5 utilisateurs ou moins",
|
||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Mettre à jour le client",
|
"actionUpdateClient": "Mettre à jour le client",
|
||||||
"actionListClients": "Liste des clients",
|
"actionListClients": "Liste des clients",
|
||||||
"actionGetClient": "Obtenir le client",
|
"actionGetClient": "Obtenir le client",
|
||||||
|
"actionCreateSiteResource": "Créer une ressource de site",
|
||||||
|
"actionDeleteSiteResource": "Supprimer une ressource de site",
|
||||||
|
"actionGetSiteResource": "Obtenir une ressource de site",
|
||||||
|
"actionListSiteResources": "Lister les ressources de site",
|
||||||
|
"actionUpdateSiteResource": "Mettre à jour une ressource de site",
|
||||||
"noneSelected": "Aucune sélection",
|
"noneSelected": "Aucune sélection",
|
||||||
"orgNotFound2": "Aucune organisation trouvée.",
|
"orgNotFound2": "Aucune organisation trouvée.",
|
||||||
"searchProgress": "Rechercher...",
|
"searchProgress": "Rechercher...",
|
||||||
@@ -1098,7 +1103,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 supporter est requise",
|
"supportKeyRequired": "La clé de supporteur 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",
|
||||||
@@ -1110,14 +1115,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": "Domicile",
|
"sidebarHome": "Accueil",
|
||||||
"sidebarSites": "Espaces",
|
"sidebarSites": "Sites",
|
||||||
"sidebarResources": "Ressource",
|
"sidebarResources": "Ressources",
|
||||||
"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 partagables",
|
"sidebarShareableLinks": "Liens partageables",
|
||||||
"sidebarApiKeys": "Clés API",
|
"sidebarApiKeys": "Clés API",
|
||||||
"sidebarSettings": "Réglages",
|
"sidebarSettings": "Réglages",
|
||||||
"sidebarAllUsers": "Tous les utilisateurs",
|
"sidebarAllUsers": "Tous les utilisateurs",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Aggiorna Client",
|
"actionUpdateClient": "Aggiorna Client",
|
||||||
"actionListClients": "Elenco Clienti",
|
"actionListClients": "Elenco Clienti",
|
||||||
"actionGetClient": "Ottieni Client",
|
"actionGetClient": "Ottieni Client",
|
||||||
|
"actionCreateSiteResource": "Crea Risorsa del Sito",
|
||||||
|
"actionDeleteSiteResource": "Elimina Risorsa del Sito",
|
||||||
|
"actionGetSiteResource": "Ottieni Risorsa del Sito",
|
||||||
|
"actionListSiteResources": "Elenca Risorse del Sito",
|
||||||
|
"actionUpdateSiteResource": "Aggiorna Risorsa del Sito",
|
||||||
"noneSelected": "Nessuna selezione",
|
"noneSelected": "Nessuna selezione",
|
||||||
"orgNotFound2": "Nessuna organizzazione trovata.",
|
"orgNotFound2": "Nessuna organizzazione trovata.",
|
||||||
"searchProgress": "Ricerca...",
|
"searchProgress": "Ricerca...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "클라이언트 업데이트",
|
"actionUpdateClient": "클라이언트 업데이트",
|
||||||
"actionListClients": "클라이언트 목록",
|
"actionListClients": "클라이언트 목록",
|
||||||
"actionGetClient": "클라이언트 가져오기",
|
"actionGetClient": "클라이언트 가져오기",
|
||||||
|
"actionCreateSiteResource": "사이트 리소스 생성",
|
||||||
|
"actionDeleteSiteResource": "사이트 리소스 삭제",
|
||||||
|
"actionGetSiteResource": "사이트 리소스 가져오기",
|
||||||
|
"actionListSiteResources": "사이트 리소스 목록",
|
||||||
|
"actionUpdateSiteResource": "사이트 리소스 업데이트",
|
||||||
"noneSelected": "선택된 항목 없음",
|
"noneSelected": "선택된 항목 없음",
|
||||||
"orgNotFound2": "조직이 없습니다.",
|
"orgNotFound2": "조직이 없습니다.",
|
||||||
"searchProgress": "검색...",
|
"searchProgress": "검색...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Oppdater klient",
|
"actionUpdateClient": "Oppdater klient",
|
||||||
"actionListClients": "List klienter",
|
"actionListClients": "List klienter",
|
||||||
"actionGetClient": "Hent klient",
|
"actionGetClient": "Hent klient",
|
||||||
|
"actionCreateSiteResource": "Opprett stedsressurs",
|
||||||
|
"actionDeleteSiteResource": "Slett Stedsressurs",
|
||||||
|
"actionGetSiteResource": "Hent Stedsressurs",
|
||||||
|
"actionListSiteResources": "List opp Stedsressurser",
|
||||||
|
"actionUpdateSiteResource": "Oppdater Stedsressurs",
|
||||||
"noneSelected": "Ingen valgt",
|
"noneSelected": "Ingen valgt",
|
||||||
"orgNotFound2": "Ingen organisasjoner funnet.",
|
"orgNotFound2": "Ingen organisasjoner funnet.",
|
||||||
"searchProgress": "Søker...",
|
"searchProgress": "Søker...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Klant bijwerken",
|
"actionUpdateClient": "Klant bijwerken",
|
||||||
"actionListClients": "Lijst klanten",
|
"actionListClients": "Lijst klanten",
|
||||||
"actionGetClient": "Client ophalen",
|
"actionGetClient": "Client ophalen",
|
||||||
|
"actionCreateSiteResource": "Sitebron maken",
|
||||||
|
"actionDeleteSiteResource": "Document verwijderen van site",
|
||||||
|
"actionGetSiteResource": "Bron van site ophalen",
|
||||||
|
"actionListSiteResources": "Bronnen van site weergeven",
|
||||||
|
"actionUpdateSiteResource": "Document bijwerken van site",
|
||||||
"noneSelected": "Niet geselecteerd",
|
"noneSelected": "Niet geselecteerd",
|
||||||
"orgNotFound2": "Geen organisaties gevonden.",
|
"orgNotFound2": "Geen organisaties gevonden.",
|
||||||
"searchProgress": "Zoeken...",
|
"searchProgress": "Zoeken...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Aktualizuj klienta",
|
"actionUpdateClient": "Aktualizuj klienta",
|
||||||
"actionListClients": "Lista klientów",
|
"actionListClients": "Lista klientów",
|
||||||
"actionGetClient": "Pobierz klienta",
|
"actionGetClient": "Pobierz klienta",
|
||||||
|
"actionCreateSiteResource": "Utwórz zasób witryny",
|
||||||
|
"actionDeleteSiteResource": "Usuń zasób strony",
|
||||||
|
"actionGetSiteResource": "Pobierz zasób strony",
|
||||||
|
"actionListSiteResources": "Lista zasobów strony",
|
||||||
|
"actionUpdateSiteResource": "Aktualizuj zasób strony",
|
||||||
"noneSelected": "Nie wybrano",
|
"noneSelected": "Nie wybrano",
|
||||||
"orgNotFound2": "Nie znaleziono organizacji.",
|
"orgNotFound2": "Nie znaleziono organizacji.",
|
||||||
"searchProgress": "Szukaj...",
|
"searchProgress": "Szukaj...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Atualizar Cliente",
|
"actionUpdateClient": "Atualizar Cliente",
|
||||||
"actionListClients": "Listar Clientes",
|
"actionListClients": "Listar Clientes",
|
||||||
"actionGetClient": "Obter Cliente",
|
"actionGetClient": "Obter Cliente",
|
||||||
|
"actionCreateSiteResource": "Criar Recurso do Site",
|
||||||
|
"actionDeleteSiteResource": "Eliminar Recurso do Site",
|
||||||
|
"actionGetSiteResource": "Obter Recurso do Site",
|
||||||
|
"actionListSiteResources": "Listar Recursos do Site",
|
||||||
|
"actionUpdateSiteResource": "Atualizar Recurso do Site",
|
||||||
"noneSelected": "Nenhum selecionado",
|
"noneSelected": "Nenhum selecionado",
|
||||||
"orgNotFound2": "Nenhuma organização encontrada.",
|
"orgNotFound2": "Nenhuma organização encontrada.",
|
||||||
"searchProgress": "Pesquisar...",
|
"searchProgress": "Pesquisar...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Обновить Клиента",
|
"actionUpdateClient": "Обновить Клиента",
|
||||||
"actionListClients": "Список Клиентов",
|
"actionListClients": "Список Клиентов",
|
||||||
"actionGetClient": "Получить Клиента",
|
"actionGetClient": "Получить Клиента",
|
||||||
|
"actionCreateSiteResource": "Создать ресурс сайта",
|
||||||
|
"actionDeleteSiteResource": "Удалить ресурс сайта ",
|
||||||
|
"actionGetSiteResource": "Получить ресурс сайта",
|
||||||
|
"actionListSiteResources": "Список ресурсов сайта",
|
||||||
|
"actionUpdateSiteResource": "Обновить ресурс сайта",
|
||||||
"noneSelected": "Ничего не выбрано",
|
"noneSelected": "Ничего не выбрано",
|
||||||
"orgNotFound2": "Организации не найдены.",
|
"orgNotFound2": "Организации не найдены.",
|
||||||
"searchProgress": "Поиск...",
|
"searchProgress": "Поиск...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "Müşteri Güncelle",
|
"actionUpdateClient": "Müşteri Güncelle",
|
||||||
"actionListClients": "Müşterileri Listele",
|
"actionListClients": "Müşterileri Listele",
|
||||||
"actionGetClient": "Müşteriyi Al",
|
"actionGetClient": "Müşteriyi Al",
|
||||||
|
"actionCreateSiteResource": "Site Kaynağı Oluştur",
|
||||||
|
"actionDeleteSiteResource": "Site Kaynağını Sil",
|
||||||
|
"actionGetSiteResource": "Site Kaynağını Al",
|
||||||
|
"actionListSiteResources": "Site Kaynaklarını Listele",
|
||||||
|
"actionUpdateSiteResource": "Site Kaynağını Güncelle",
|
||||||
"noneSelected": "Hiçbiri seçili değil",
|
"noneSelected": "Hiçbiri seçili değil",
|
||||||
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
|
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
|
||||||
"searchProgress": "Ara...",
|
"searchProgress": "Ara...",
|
||||||
|
|||||||
@@ -1052,6 +1052,11 @@
|
|||||||
"actionUpdateClient": "更新客户端",
|
"actionUpdateClient": "更新客户端",
|
||||||
"actionListClients": "列出客户端",
|
"actionListClients": "列出客户端",
|
||||||
"actionGetClient": "获取客户端",
|
"actionGetClient": "获取客户端",
|
||||||
|
"actionCreateSiteResource": "创建站点资源",
|
||||||
|
"actionDeleteSiteResource": "删除站点资源",
|
||||||
|
"actionGetSiteResource": "获取站点资源",
|
||||||
|
"actionListSiteResources": "列出站点资源",
|
||||||
|
"actionUpdateSiteResource": "更新站点资源",
|
||||||
"noneSelected": "未选择",
|
"noneSelected": "未选择",
|
||||||
"orgNotFound2": "未找到组织。",
|
"orgNotFound2": "未找到组织。",
|
||||||
"searchProgress": "搜索中...",
|
"searchProgress": "搜索中...",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
|
|||||||
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
||||||
import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
|
import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import rateLimit from "express-rate-limit";
|
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "./types/HttpCode";
|
import HttpCode from "./types/HttpCode";
|
||||||
import requestTimeoutMiddleware from "./middlewares/requestTimeout";
|
import requestTimeoutMiddleware from "./middlewares/requestTimeout";
|
||||||
@@ -70,7 +70,7 @@ export function createApiServer() {
|
|||||||
60 *
|
60 *
|
||||||
1000,
|
1000,
|
||||||
max: config.getRawConfig().rate_limits.global.max_requests,
|
max: config.getRawConfig().rate_limits.global.max_requests,
|
||||||
keyGenerator: (req) => `apiServerGlobal:${req.ip}:${req.path}`,
|
keyGenerator: (req) => `apiServerGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `Rate limit exceeded. You can make ${config.getRawConfig().rate_limits.global.max_requests} requests every ${config.getRawConfig().rate_limits.global.window_minutes} minute(s).`;
|
const message = `Rate limit exceeded. You can make ${config.getRawConfig().rate_limits.global.max_requests} requests every ${config.getRawConfig().rate_limits.global.window_minutes} minute(s).`;
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ export const configSchema = z
|
|||||||
trust_proxy: z.number().int().gte(0).optional().default(1),
|
trust_proxy: z.number().int().gte(0).optional().default(1),
|
||||||
secret: z
|
secret: z
|
||||||
.string()
|
.string()
|
||||||
.transform(getEnvOrYaml("SERVER_SECRET"))
|
|
||||||
.pipe(z.string().min(8))
|
.pipe(z.string().min(8))
|
||||||
.optional()
|
.optional()
|
||||||
}).optional().default({
|
}).optional().default({
|
||||||
@@ -324,7 +323,10 @@ export const configSchema = z
|
|||||||
if (data.managed) {
|
if (data.managed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// If hybrid is not defined, server secret must be defined
|
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env
|
||||||
|
if (data.server?.secret === undefined) {
|
||||||
|
data.server.secret = process.env.SERVER_SECRET;
|
||||||
|
}
|
||||||
return data.server?.secret !== undefined && data.server.secret.length > 0;
|
return data.server?.secret !== undefined && data.server.secret.length > 0;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ export * from "./verifyAccessTokenAccess";
|
|||||||
export * from "./verifyApiKeyIsRoot";
|
export * from "./verifyApiKeyIsRoot";
|
||||||
export * from "./verifyApiKeyApiKeyAccess";
|
export * from "./verifyApiKeyApiKeyAccess";
|
||||||
export * from "./verifyApiKeyClientAccess";
|
export * from "./verifyApiKeyClientAccess";
|
||||||
|
export * from "./verifyApiKeySiteResourceAccess";
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { db } from "@server/db";
|
||||||
|
import { siteResources, apiKeyOrg } from "@server/db";
|
||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
||||||
|
export async function verifyApiKeySiteResourceAccess(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const apiKey = req.apiKey;
|
||||||
|
const siteResourceId = parseInt(req.params.siteResourceId);
|
||||||
|
const siteId = parseInt(req.params.siteId);
|
||||||
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!siteResourceId || !siteId || !orgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Missing required parameters"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey.isRoot) {
|
||||||
|
// Root keys can access any resource in any org
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the site resource exists and belongs to the specified site and org
|
||||||
|
const [siteResource] = await db
|
||||||
|
.select()
|
||||||
|
.from(siteResources)
|
||||||
|
.where(and(
|
||||||
|
eq(siteResources.siteResourceId, siteResourceId),
|
||||||
|
eq(siteResources.siteId, siteId),
|
||||||
|
eq(siteResources.orgId, orgId)
|
||||||
|
))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!siteResource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
"Site resource not found"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the API key has access to the organization
|
||||||
|
if (!req.apiKeyOrg) {
|
||||||
|
const apiKeyOrgRes = await db
|
||||||
|
.select()
|
||||||
|
.from(apiKeyOrg)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
|
||||||
|
eq(apiKeyOrg.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (apiKeyOrgRes.length === 0) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"Key does not have access to this organization"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.apiKeyOrg = apiKeyOrgRes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the siteResource to the request for use in the next middleware/route
|
||||||
|
// @ts-ignore - Extending Request type
|
||||||
|
req.siteResource = siteResource;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error verifying site resource access"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ import { createStore } from "@server/lib/rateLimitStore";
|
|||||||
import { ActionsEnum } from "@server/auth/actions";
|
import { ActionsEnum } from "@server/auth/actions";
|
||||||
import { createNewt, getNewtToken } from "./newt";
|
import { createNewt, getNewtToken } from "./newt";
|
||||||
import { getOlmToken } from "./olm";
|
import { getOlmToken } from "./olm";
|
||||||
import rateLimit from "express-rate-limit";
|
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
|
||||||
@@ -815,7 +815,7 @@ authRouter.use(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: config.getRawConfig().rate_limits.auth.window_minutes,
|
windowMs: config.getRawConfig().rate_limits.auth.window_minutes,
|
||||||
max: config.getRawConfig().rate_limits.auth.max_requests,
|
max: config.getRawConfig().rate_limits.auth.max_requests,
|
||||||
keyGenerator: (req) => `authRouterGlobal:${req.ip}:${req.path}`,
|
keyGenerator: (req) => `authRouterGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `Rate limit exceeded. You can make ${config.getRawConfig().rate_limits.auth.max_requests} requests every ${config.getRawConfig().rate_limits.auth.window_minutes} minute(s).`;
|
const message = `Rate limit exceeded. You can make ${config.getRawConfig().rate_limits.auth.max_requests} requests every ${config.getRawConfig().rate_limits.auth.window_minutes} minute(s).`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -829,7 +829,7 @@ authRouter.put(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => `signup:${req.ip}:${req.body.email}`,
|
keyGenerator: (req) => `signup:${ipKeyGenerator(req.ip || "")}:${req.body.email}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only sign up ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only sign up ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -843,7 +843,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => `login:${req.body.email || req.ip}`,
|
keyGenerator: (req) => `login:${req.body.email || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only log in ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only log in ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -858,7 +858,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 900,
|
max: 900,
|
||||||
keyGenerator: (req) => `newtGetToken:${req.body.newtId || req.ip}`,
|
keyGenerator: (req) => `newtGetToken:${req.body.newtId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request a Newt token ${900} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request a Newt token ${900} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -872,7 +872,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 900,
|
max: 900,
|
||||||
keyGenerator: (req) => `olmGetToken:${req.body.newtId || req.ip}`,
|
keyGenerator: (req) => `olmGetToken:${req.body.newtId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request an Olm token ${900} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request an Olm token ${900} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -888,7 +888,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => {
|
keyGenerator: (req) => {
|
||||||
return `signup:${req.body.email || req.user?.userId || req.ip}`;
|
return `signup:${req.body.email || req.user?.userId || ipKeyGenerator(req.ip || "")}`;
|
||||||
},
|
},
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only enable 2FA ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only enable 2FA ${15} times every ${15} minutes. Please try again later.`;
|
||||||
@@ -904,7 +904,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => {
|
keyGenerator: (req) => {
|
||||||
return `signup:${req.body.email || req.user?.userId || req.ip}`;
|
return `signup:${req.body.email || req.user?.userId || ipKeyGenerator(req.ip || "")}`;
|
||||||
},
|
},
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request a 2FA code ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request a 2FA code ${15} times every ${15} minutes. Please try again later.`;
|
||||||
@@ -920,7 +920,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => `signup:${req.user?.userId || req.ip}`,
|
keyGenerator: (req) => `signup:${req.user?.userId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only disable 2FA ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only disable 2FA ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -934,7 +934,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => `signup:${req.body.email || req.ip}`,
|
keyGenerator: (req) => `signup:${req.body.email || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only sign up ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only sign up ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -952,7 +952,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`requestEmailVerificationCode:${req.body.email || req.ip}`,
|
`requestEmailVerificationCode:${req.body.email || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request an email verification code ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request an email verification code ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -974,7 +974,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`requestPasswordReset:${req.body.email || req.ip}`,
|
`requestPasswordReset:${req.body.email || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request a password reset ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request a password reset ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -989,7 +989,7 @@ authRouter.post(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) => `resetPassword:${req.body.email || req.ip}`,
|
keyGenerator: (req) => `resetPassword:${req.body.email || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request a password reset ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request a password reset ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -1005,7 +1005,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`authWithPassword:${req.ip}:${req.params.resourceId || req.ip}`,
|
`authWithPassword:${ipKeyGenerator(req.ip || "")}:${req.params.resourceId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only authenticate with password ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only authenticate with password ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -1020,7 +1020,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`authWithPincode:${req.ip}:${req.params.resourceId || req.ip}`,
|
`authWithPincode:${ipKeyGenerator(req.ip || "")}:${req.params.resourceId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only authenticate with pincode ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only authenticate with pincode ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -1036,7 +1036,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 15,
|
max: 15,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`authWithWhitelist:${req.ip}:${req.body.email}:${req.params.resourceId}`,
|
`authWithWhitelist:${ipKeyGenerator(req.ip || "")}:${req.body.email}:${req.params.resourceId}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request an email OTP ${15} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request an email OTP ${15} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -1069,7 +1069,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
max: 5, // Allow 5 security key registrations per 15 minutes
|
max: 5, // Allow 5 security key registrations per 15 minutes
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`securityKeyRegister:${req.user?.userId || req.ip}`,
|
`securityKeyRegister:${req.user?.userId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only register a security key ${5} times every ${15} minutes. Please try again later.`;
|
const message = `You can only register a security key ${5} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
@@ -1089,7 +1089,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
max: 10, // Allow 10 authentication attempts per 15 minutes per IP
|
max: 10, // Allow 10 authentication attempts per 15 minutes per IP
|
||||||
keyGenerator: (req) => {
|
keyGenerator: (req) => {
|
||||||
return `securityKeyAuth:${req.body.email || req.ip}`;
|
return `securityKeyAuth:${req.body.email || ipKeyGenerator(req.ip || "")}`;
|
||||||
},
|
},
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only attempt security key authentication ${10} times every ${15} minutes. Please try again later.`;
|
const message = `You can only attempt security key authentication ${10} times every ${15} minutes. Please try again later.`;
|
||||||
@@ -1111,7 +1111,7 @@ authRouter.delete(
|
|||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
max: 20, // Allow 10 authentication attempts per 15 minutes per IP
|
max: 20, // Allow 10 authentication attempts per 15 minutes per IP
|
||||||
keyGenerator: (req) => `securityKeyAuth:${req.user?.userId || req.ip}`,
|
keyGenerator: (req) => `securityKeyAuth:${req.user?.userId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only delete a security key ${10} times every ${15} minutes. Please try again later.`;
|
const message = `You can only delete a security key ${10} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as client from "./client";
|
|||||||
import * as accessToken from "./accessToken";
|
import * as accessToken from "./accessToken";
|
||||||
import * as apiKeys from "./apiKeys";
|
import * as apiKeys from "./apiKeys";
|
||||||
import * as idp from "./idp";
|
import * as idp from "./idp";
|
||||||
|
import * as siteResource from "./siteResource";
|
||||||
import {
|
import {
|
||||||
verifyApiKey,
|
verifyApiKey,
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
@@ -22,7 +23,8 @@ import {
|
|||||||
verifyApiKeyAccessTokenAccess,
|
verifyApiKeyAccessTokenAccess,
|
||||||
verifyApiKeyIsRoot,
|
verifyApiKeyIsRoot,
|
||||||
verifyApiKeyClientAccess,
|
verifyApiKeyClientAccess,
|
||||||
verifyClientsEnabled
|
verifyClientsEnabled,
|
||||||
|
verifyApiKeySiteResourceAccess
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
@@ -128,6 +130,69 @@ authenticated.delete(
|
|||||||
site.deleteSite
|
site.deleteSite
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/user-resources",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
resource.getUserResources
|
||||||
|
);
|
||||||
|
// Site Resource endpoints
|
||||||
|
authenticated.put(
|
||||||
|
"/org/:orgId/site/:siteId/resource",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeySiteAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.createSiteResource),
|
||||||
|
siteResource.createSiteResource
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/site/:siteId/resources",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeySiteAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.listSiteResources),
|
||||||
|
siteResource.listSiteResources
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/site-resources",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.listSiteResources),
|
||||||
|
siteResource.listAllSiteResourcesByOrg
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/site/:siteId/resource/:siteResourceId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeySiteAccess,
|
||||||
|
verifyApiKeySiteResourceAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.getSiteResource),
|
||||||
|
siteResource.getSiteResource
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
"/org/:orgId/site/:siteId/resource/:siteResourceId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeySiteAccess,
|
||||||
|
verifyApiKeySiteResourceAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.updateSiteResource),
|
||||||
|
siteResource.updateSiteResource
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.delete(
|
||||||
|
"/org/:orgId/site/:siteId/resource/:siteResourceId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeySiteAccess,
|
||||||
|
verifyApiKeySiteResourceAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.deleteSiteResource),
|
||||||
|
siteResource.deleteSiteResource
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.put(
|
||||||
|
"/org/:orgId/resource",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.createResource),
|
||||||
|
resource.createResource
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/org/:orgId/site/:siteId/resource",
|
"/org/:orgId/site/:siteId/resource",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export type ListRolesResponse = {
|
|||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/orgs/{orgId}/roles",
|
path: "/org/{orgId}/roles",
|
||||||
description: "List roles.",
|
description: "List roles.",
|
||||||
tags: [OpenAPITags.Org, OpenAPITags.Role],
|
tags: [OpenAPITags.Org, OpenAPITags.Role],
|
||||||
request: {
|
request: {
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ export default async function migration() {
|
|||||||
|
|
||||||
await db.execute(sql`ALTER TABLE "clientSites" ADD COLUMN "endpoint" varchar;`);
|
await db.execute(sql`ALTER TABLE "clientSites" ADD COLUMN "endpoint" varchar;`);
|
||||||
|
|
||||||
|
await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "online" boolean DEFAULT false NOT NULL;`);
|
||||||
|
|
||||||
|
await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "lastPing" integer;`);
|
||||||
|
|
||||||
|
await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "type" text DEFAULT 'gerbil';`);
|
||||||
|
|
||||||
await db.execute(sql`ALTER TABLE "olms" ADD COLUMN "version" text;`);
|
await db.execute(sql`ALTER TABLE "olms" ADD COLUMN "version" text;`);
|
||||||
|
|
||||||
await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "createdAt" text;`);
|
await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "createdAt" text;`);
|
||||||
|
|||||||
@@ -777,15 +777,6 @@ export default function Page() {
|
|||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 mt-8">
|
<div className="flex justify-end space-x-2 mt-8">
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
router.push(`/${orgId}/settings/access/users`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
{userType && dataLoaded && (
|
{userType && dataLoaded && (
|
||||||
<Button
|
<Button
|
||||||
type={inviteLink ? "button" : "submit"}
|
type={inviteLink ? "button" : "submit"}
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t('actionSetResourcePassword')]: "setResourcePassword",
|
[t('actionSetResourcePassword')]: "setResourcePassword",
|
||||||
[t('actionSetResourcePincode')]: "setResourcePincode",
|
[t('actionSetResourcePincode')]: "setResourcePincode",
|
||||||
[t('actionSetResourceEmailWhitelist')]: "setResourceWhitelist",
|
[t('actionSetResourceEmailWhitelist')]: "setResourceWhitelist",
|
||||||
[t('actionGetResourceEmailWhitelist')]: "getResourceWhitelist"
|
[t('actionGetResourceEmailWhitelist')]: "getResourceWhitelist",
|
||||||
|
[t('actionCreateSiteResource')]: "createSiteResource",
|
||||||
|
[t('actionDeleteSiteResource')]: "deleteSiteResource",
|
||||||
|
[t('actionGetSiteResource')]: "getSiteResource",
|
||||||
|
[t('actionListSiteResources')]: "listSiteResources",
|
||||||
|
[t('actionUpdateSiteResource')]: "updateSiteResource"
|
||||||
},
|
},
|
||||||
|
|
||||||
Target: {
|
Target: {
|
||||||
|
|||||||
Reference in New Issue
Block a user