diff --git a/CHANGES b/CHANGES index cd68f0742..bf1591ee4 100644 --- a/CHANGES +++ b/CHANGES @@ -2854,4 +2854,5 @@ [8.1.0] * backups: add hetzner object storage * registry: cloudron container registry +* gandi: add PAT token support diff --git a/dashboard/public/translation/en.json b/dashboard/public/translation/en.json index 93baf58a5..2be504d7b 100644 --- a/dashboard/public/translation/en.json +++ b/dashboard/public/translation/en.json @@ -1086,7 +1086,10 @@ "ovhConsumerKey": "Consumer Key", "ovhAppKey": "Application Key", "ovhAppSecret": "Application Secret", - "deSecToken": "deSEC Token" + "deSecToken": "deSEC Token", + "gandiTokenType": "Token Type", + "gandiTokenTypeApiKey": "API Key (Deprecated)", + "gandiTokenTypePAT": "Personal Access Token (PAT)" }, "removeDialog": { "title": "Really remove {{ domain }}?", diff --git a/dashboard/public/translation/es.json b/dashboard/public/translation/es.json index c373edb3e..a68e5f338 100644 --- a/dashboard/public/translation/es.json +++ b/dashboard/public/translation/es.json @@ -130,7 +130,8 @@ "justNow": "Ahora mismo", "yeserday": "Ayer", "minutesAgo": "Hace {{ m }} minutos", - "hoursAgo": "Hace {{ h }} horas" + "hoursAgo": "Hace {{ h }} horas", + "never": "Nunca" }, "multiselect": { "selected": "{{ n }} seleccionado", @@ -179,6 +180,9 @@ "addApplinkAction": "Añadir enlace de la Aplicación", "filter": { "clearAll": "Borrar todo" + }, + "apps": { + "count": "Total Aplicaciones: {{ count }}" } }, "users": { @@ -200,7 +204,7 @@ "configureAction": "Configurar", "syncAction": "Sincronizar", "showLogsAction": "Mostrar Registros", - "autocreateUsersOnLogin": "Crear automaticamente usuarios cuando inician sesión en Cloudron", + "autocreateUsersOnLogin": "Crear usuarios automáticamente al iniciar sesión", "groupnameField": "Campo de Nombre de Grupo", "groupFilter": "Filtro de Grupo", "syncGroups": "Sincronizar Grupos", @@ -211,11 +215,12 @@ "provider": "Proveedor", "noopInfo": "La autentificación LDAP no está configurada.", "subscriptionRequiredAction": "Configura tu Suscripción Ahora", - "description": "Cloudron sincronizará usuarios y grupos desde un servidor LDAP o ActiveDirectory externo. La verificación de la contraseña para autentificar a esos usuarios se realiza en el servidor externo. La sincronización no se ejecuta automáticamente, sino que debe activarse manualmente.", + "description": "Esta configuración sincronizará y autentificará usuarios y grupos desde un servidor LDAP o Active Directory externo. La sincronización se ejecuta periódicamente pero también se puede activar manualmente.", "title": "Conectar un directorio externo", "auth": "Auth", "providerOther": "Otra", - "providerDisabled": "Deshabilitada" + "providerDisabled": "Deshabilitada", + "disableWarning": "La fuente de autentificación de todos los usuarios existentes se restablecerá para autentificarse en la base de datos de contraseñas local." }, "settings": { "saveAction": "Guardar", @@ -331,7 +336,9 @@ "username": "Usuario", "fullName": "Nombre Completo", "displayNamePlaceholder": "Opcional. Si no se proporciona, el usuario puede proporcionarlo durante el registro", - "fallbackEmailPlaceholder": "Opcional. Si no se especifica, se utilizará el correo electrónico principal" + "fallbackEmailPlaceholder": "Opcional. Si no se especifica, se utilizará el correo electrónico principal", + "external2FA": "La configuración 2FA es administrada por una fuente de autentificación externa", + "ldapGroups": "Grupos LDAP" }, "setGhostDialog": { "title": "Crear contraseña para suplantar {{ username }}", @@ -358,7 +365,7 @@ }, "exposedLdap": { "ipRestriction": { - "description": "El servidor de directorio puede limitarse a una IP específica o rangos.", + "description": "Acceso limitado del Servidor de Directorio a IP o rangos específicos. Las líneas que comienzan con # se tratan como comentarios.", "placeholder": "Dirección IP o Subred separada por líneas", "label": "Acceso Restringido" }, @@ -369,7 +376,8 @@ "label": "Vincular Contraseña", "description": "Todas las consultas LDAP deben autentificarse con este secreto y el DN de usuario {{ userDN }}", "url": "URL del Servidor" - } + }, + "cloudflarePortWarning": "El proxy de Cloudflare debe estar deshabilitado en el dominio del panel para acceder al servidor LDAP" }, "userImportDialog": { "title": "Importar Usuarios", @@ -419,7 +427,7 @@ "configure": "Configurar", "retentionPolicy": "Política de retención", "schedule": "Programar", - "description": "Cloudron realiza una copia de seguridad completa de su sistema en función de este intervalo programado y mantiene copias de seguridad con la política de retención especificada.", + "description": "Se crea una copia de seguridad completa del sistema según la programación especificada en la Zona horaria del sistema. Las copias de seguridad antiguas se eliminan según la Política de retención.", "title": "Programación y retención" }, "location": { @@ -430,7 +438,7 @@ "provider": "Proveedor", "disabledList": "Las siguientes aplicaciones tienen las copias de seguridad deshabilitadas:", "title": "Ubicación", - "description": "Cloudron realiza una copia de seguridad completa de su sistema en la ubicación configurada.", + "description": "Se guarda una copia de seguridad completa de su sistema en la ubicación de almacenamiento con el formato configurado.", "remount": "Volver a montar almacenamiento" }, "title": "Backups", @@ -510,7 +518,7 @@ "title": "Detalles de la Copia de Seguridad" }, "check": { - "sameDisk": "Las copias de seguridad de Cloudron se encuentran actualmente en el mismo disco que la instancia del servidor Cloudron. Esto es peligroso y puede provocar la pérdida total de datos si falla el disco. Consulta https://docs.cloudron.io/backups/#storage-providers para almacenar copias de seguridad en una ubicación externa.", + "sameDisk": "Las copias de seguridad se encuentran actualmente en el mismo disco que el propio Cloudron. Si el disco se llena con estas copias de seguridad, Cloudron no funcionará. Una falla del disco también puede provocar la pérdida total de datos. Consulte https://docs.cloudron.io/backups/#storage-providers para almacenar copias de seguridad en una ubicación externa.", "noop": "Las copias de seguridad de Cloudron están deshabilitadas. Asegúrate de que se haya realizado una copia de seguridad de este servidor utilizando medios alternativos. Consulta https://docs.cloudron.io/backups/#storage-providers para más información." }, "backupEdit": { @@ -562,7 +570,10 @@ "changeEmail": { "errorEmailRequired": "Se requiere una dirección de email válida", "errorEmailInvalid": "La dirección de email no es válida", - "title": "Cambiar el email principal" + "title": "Cambiar el email principal", + "email": "Nuevo Correo Electrónico", + "password": "Contraseña para confirmación", + "errorWrongPassword": "Contraseña errónea" }, "loginTokens": { "logoutAll": "Cerrar sesión de todo", @@ -769,7 +780,9 @@ "title": "Pie de página" }, "cloudronName": "Nombre de Cloudron", - "title": "Marca" + "title": "Marca", + "backgroundImage": "Imagen de fondo de la página de inicio de sesión", + "clearBackgroundImage": "Limpiar" }, "network": { "firewall": { @@ -788,14 +801,14 @@ "configure": "Configurar", "interface": "Nombre de la interfaz de red", "provider": "Proveedor", - "description": "Cloudron usa esta dirección IP al configurar los registros DNS.", - "title": "Dirección IP", + "description": "Esta dirección IPv4 se utiliza para configurar registros A en los DNS.", + "title": "IP v4", "address": "Dirección IP" }, "title": "Red", "configureIp": { "providerGenericDescription": "La dirección de IP pública del servidor será detectada automáticamente.", - "title": "Configurar Proveedor de IP" + "title": "Configurar Proveedor de IPv4" }, "dyndns": { "description": "Habilite esta opción para mantener todos sus registros DNS sincronizados con una dirección IP cambiante. Esto es útil cuando Cloudron se ejecuta en una red con una dirección IP pública que cambia con frecuencia, como una conexión doméstica.", @@ -808,7 +821,7 @@ "ipv6": { "address": "Dirección IPv6", "title": "IPv6", - "description": "Cloudron usa esta dirección IPv6 para configurar los registros DNS AAAA.\n" + "description": "Esta dirección IPv6 se utiliza para configurar registros AAAA en los DNS." }, "configureIpv6": { "title": "Configurar Proveedor de IPv6" @@ -836,7 +849,7 @@ "memoryLimit": "Límite de Memoria", "memoryUsage": "Uso de Memoria", "service": "Servicio", - "description": "Los servicios de Cloudron implementan funcionalidades como bases de datos, correo electrónico y autentificación.", + "description": "Los servicios implementan funcionalidades como bases de datos, correo electrónico y autentificación.", "title": "Servicios", "refresh": "Refrescar" }, @@ -850,7 +863,7 @@ "setupAction": "Configurar Cuenta", "subscription": "Suscripción", "cloudronId": "ID de Cloudron", - "subscriptionChangeAction": "Cambiar Suscripción", + "subscriptionChangeAction": "Gestionar Suscripción", "description": "Se utiliza una cuenta de Cloudron.io para acceder a la App Store y administrar su suscripción.", "emailNotVerified": "Correo aún no verificado" }, @@ -871,15 +884,18 @@ "changeScheduleAction": "Cambiar Programación", "showLogsAction": "Mostrar Registros", "version": "Versión de la Plataforma", - "title": "Actualizaciones" + "title": "Actualizaciones", + "description": "Las actualizaciones de plataformas y aplicaciones se aplican automáticamente según la programación en la Zona horaria del sistema.", + "disabled": "Deshabilitado", + "schedule": "Programar" }, "language": { "description": "El idioma predeterminado de este Cloudron se puede configurar aquí. Esto se utilizará también para correos electrónicos transaccionales como invitaciones de usuario y restablecimiento de contraseña. Cada usuario también puede cambiar el idioma preferido para el panel individualmente en el perfil.", "title": "Idioma" }, "timezone": { - "description": "La configuración de zona horaria actual es {{timeZone}} .\nEsta configuración se utiliza para programar tareas de respaldo y actualización.", - "title": "Zona horaria" + "description": "La configuración de zona horaria actual es {{ timeZone }}. Esta configuración se utiliza para programar tareas de copia de seguridad y actualizaciones. Las marcas de tiempo en la interfaz de usuario siempre se muestran utilizando la zona horaria del navegador.", + "title": "Zona horaria del Sistema" }, "privateDockerRegistry": { "subscriptionRequired": "Esta funcionalidad solo está disponible en planes de pago.", @@ -973,7 +989,8 @@ "ovhEndpoint": "Punto final", "ovhConsumerKey": "Clave del consumidor", "ovhAppKey": "Clave de Aplicación", - "ovhAppSecret": "Clave Secreta Aplicación" + "ovhAppSecret": "Clave Secreta Aplicación", + "deSecToken": "Token deSEC" }, "subscriptionRequired": { "setupAction": "Configura tu suscripción", @@ -983,7 +1000,7 @@ "renewCerts": { "showLogsAction": "Mostrar Registros", "renewAllAction": "Renovar todos los Certificados", - "description": "Cloudron renueva los certificados Let's Encrypt automáticamente. Utilice esta opción para activar una renovación de inmediato.", + "description": "Los certificados de Let's Encrypt se renuevan automáticamente. Utiliza esta opción para activar una renovación inmediatamente.", "title": "Renovar certificados" }, "tooltipRemove": "Borrar Dominio", @@ -1018,27 +1035,29 @@ "firstTimeTitle": "Uso de primera vez", "firstTimeCollapseHeader": "Instrucciones de ajustes de primera vez", "appDocsUrl": "Consulta la {{title}} documentación para obtener información útil y temas comunes sobre esta aplicación. Si necesita más ayuda, consulta la {{title}} sección del foro de Cloudron.", - "package": "Paquete" + "package": "Paquete", + "checklist": "Lista de verificación del administrador" }, "updates": { "auto": { - "enableAction": "Habilitar Actualizaciones Automáticas", - "disableAction": "Deshabilitar Actualizaciones Automáticas", + "enableAction": "Habilitar actualizaciones automáticas", + "disableAction": "Deshabilitar la actualización automática", "disabled": "Las Actualizaciones Automáticas están deshabilitadas actualmente.", "enabled": "Las Actualizaciones Automáticas están habilitadas actualmente.", - "description": "Cloudron comprueba periódicamente la App Store en busca de actualizaciones. Si deshabilitas las actualizaciones automáticas, asegúrate de aplicarlas manualmente.", + "description": "Cloudron comprueba periódicamente la Tienda de Aplicaciones en busca de actualizaciones.", "title": "Actualizaciones Automáticas" }, "info": { "updateAvailableAction": "Actualización disponible", - "customAppUpdateInfo": "Actualizaciones no están habilitadas para Aplicaciones personalizadas", + "customAppUpdateInfo": "La actualización automática no está disponible para aplicaciones personalizadas.", "checkForUpdatesAction": "Buscar Actualizaciones", "lastUpdated": "Última Actualización", "packageVersion": "Versión del Paquete", "appId": "ID de la Aplicación", "description": "Título y Versión de la Aplicación", "title": "Información de la Aplicación", - "repository": "Repositorio de paquetes" + "repository": "Repositorio de paquetes", + "installedAt": "Instalado en" }, "noUpdates": "No hay actualizaciones disponibles" }, @@ -1110,7 +1129,7 @@ "disabled": "Los Backups automáticos están actualmente deshabilitados.", "disableAction": "Deshabilitar Backups automáticos", "enableAction": "Habilitar Backups automáticos", - "description": "Cloudron crea periódicamente una copia de seguridad basada en la configuración de copia de seguridad ." + "description": "Las copias de seguridad se crean periódicamente según la Programación de copias de seguridad." } }, "security": { @@ -1152,14 +1171,14 @@ }, "resources": { "cpu": { - "description": "Porcentaje de tiempo de CPU cuando el sistema está con carga pesada.", - "title": "Recursos compartidos de CPU", - "setAction": "Ajustar" + "description": "Porcentaje máximo de CPU que la aplicación puede usar", + "title": "Límite de CPU", + "setAction": "Escalar" }, "memory": { "resizeAction": "Redimensionar", "error": "No se puede establecer el límite de memoria, inténtalo con menos.", - "description": "Cloudron asigna el 50% de este valor como RAM y el 50% como intercambio.", + "description": "Memoria máxima que la Aplicación puede usar", "title": "Límite de Memoria" } }, @@ -1247,7 +1266,7 @@ "backupWarning": "Las copias de seguridad de las aplicaciones no se eliminan y se borrarán según la política de copias de seguridad. Puede restaurar esta aplicación a partir de una copia de seguridad de la aplicación existente mediante las siguientes instrucciones .", "uninstallAction": "Desinstalar", "title": "Desinstalar", - "description": "Esto desinstalará la aplicación inmediatamente y eliminará todos sus datos. El sitio será inaccesible." + "description": "Esto desinstalará la aplicación inmediatamente y eliminará tus datos. El sitio será inaccesible." }, "startStop": { "title": "Arrancar / Parar", @@ -1348,6 +1367,12 @@ "title": "Configuración de Redis", "enable": "Configura la aplicación para usar Redis", "disable": "Deshabilitar Redis" + }, + "infoTabTitle": "Información", + "info": { + "notes": { + "title": "Notas del Administrador" + } } }, "lang": { @@ -1363,7 +1388,8 @@ "es": "Español", "ru": "Ruso", "pt": "Portugués", - "da": "Danés" + "da": "Danés", + "id": "Indonesio" }, "system": { "title": "Información del Sistema", @@ -1664,7 +1690,7 @@ }, "tabTitle": "Saliente", "title": "Retransmisión de correo electrónico", - "description": "Cloudron utilizará este servidor de correo (host inteligente) para enviar los correos salientes de las aplicaciones instaladas en este dominio." + "description": "Este servidor de correo (host inteligente) se utilizará para enviar los correos salientes de las aplicaciones instaladas en este dominio." }, "backAction": "Volver a Correo Electrónico", "config": { @@ -1703,7 +1729,7 @@ "title": "¿Habilitar el correo electrónico para {{ domain }}?", "setupDnsCheckbox": "Configura los registros DNS de correo ahora", "enableAction": "Habilitar", - "cloudflareInfo": "Cloudflare administra el dominio {{adminDomain}} . Verifica que el proxy de Cloudflare esté inhabilitado para {{mailFqdn}} y configurado en solo DNS . Esto es necesario porque Cloudflare no es un proxy de correo electrónico." + "cloudflareInfo": "El dominio del servidor de correo {{ adminDomain }} es administrado por Cloudflare. Verifica que el proxy de Cloudflare esté deshabilitado para {{ mailFqdn }} y configurado en DNS only. Esto es necesario porque Cloudflare no realiza proxy de correo electrónico." }, "disableEmailDialog": { "description": "Esto configurará Cloudron para que deje de recibir correos electrónicos para {{dominio}} . Los buzones de correo y las listas asociadas con este dominio no se eliminarán.", @@ -1821,7 +1847,8 @@ "clear": "Borrar", "pasteInfo": "Para Pegar usa Ctrl+v" }, - "uploading": "Subiendo…" + "uploading": "Subiendo…", + "uploadTo": "Subir a {{ path }}" }, "passwordReset": { "newPassword": { @@ -1890,7 +1917,8 @@ "signInAction": "Iniciar Sesión", "resetPasswordAction": "Resetear contraseña", "errorIncorrect2FAToken": "El token 2FA es inválido", - "errorInternal": "Error interno, prueba de nuevo más tarde" + "errorInternal": "Error interno, prueba de nuevo más tarde", + "loginWith": "Iniciar sesión con Cloudron" }, "newLoginEmail": { "subject": "[<% = cloudron%>] Nuevo inicio de sesión en tu cuenta", diff --git a/dashboard/public/translation/nl.json b/dashboard/public/translation/nl.json index 734ebd2eb..c6e573dbf 100644 --- a/dashboard/public/translation/nl.json +++ b/dashboard/public/translation/nl.json @@ -526,7 +526,7 @@ "location": "Locatie", "endpoint": "Eindpunt", "configure": "Configureer", - "description": "Cloudron maakt een volledige backup van je systeem op de geconfigureerde locatie.", + "description": "Een volledige backup van je systeem is opgeslagen op de geconfigureerde locatie met het geconfigureerde formaat.", "format": "Opslagformaat", "remount": "Her-koppel Storage" }, @@ -535,7 +535,7 @@ "schedule": "Planning", "retentionPolicy": "Bewaartermijn", "configure": "Configureer", - "description": "Cloudron maakt een volledige backup van je systeem gebaseerd op dit geplande interval en bewaart de backups volgens de ingestelde bewaartermijn." + "description": "Een volledige backup van het systeem is aangemaakt op basis van het gespecificeerde Schema in de System Time Zone. Oude backups worden verwijderd volgens de ingestelde bewaartermijn." }, "listing": { "title": "Lijst met bestaande back-ups", @@ -850,7 +850,7 @@ "title": "Vernieuw certificaten", "renewAllAction": "Vernieuw alle certificaten", "showLogsAction": "Toon logbestanden", - "description": "Cloudron vernieuwt Let's Encrypt certificaten automatisch. Gebruik deze optie om nu te vernieuwen." + "description": "Let's Encrypt certificaten worden automatisch vernieuwd. Gebruik deze optie om nu te vernieuwen." }, "changeDashboardDomain": { "changeAction": "Domein aanpassen", @@ -1072,7 +1072,7 @@ }, "auto": { "title": "Automatische backups", - "description": "Cloudron maakt periodiek een backup op basis van deze backup instellingen.", + "description": "Backups worden periodiek gemaakt op basis van dit Backup Schema.", "enabled": "Automatische backups zijn momenteel ingeschakeld.", "disabled": "Automatische backups zijn momenteel uitgeschakeld.", "disableAction": "Automatische backups uitschakelen", @@ -1239,7 +1239,7 @@ "interface": "Naam netwerkinterface", "configure": "Configureer", "interfaceDescription": "Toon beschikbare apparaten op deze server met:", - "description": "Cloudron gebruikt dit IPv4 adres om de DNS records in te stellen.", + "description": "Dit IPv4 adres is gebruikt om de DNS records in te stellen.", "detected": "gedetecteerd", "address": "IP adres" }, @@ -1268,7 +1268,7 @@ "ipv6": { "address": "IPv6 adres", "title": "IPv6", - "description": "Cloudron gebruikt dit IPv6 adres om DNS AAAA records te configureren.\n" + "description": "Dit IPv6 adres is gebruikt om DNS AAAA records te configureren." }, "configureIpv6": { "title": "Configureer IPv6 aanbieder" @@ -1297,7 +1297,7 @@ "recoveryModeDescription": "Indien de dienst continue herstart of niet reageert vanwege datacorruptie, plaats de dienst in dan Herstelmodus. Bekijk de volgende instructies om de dienst weer werkend te krijgen.", "enableRecoveryMode": "Inschakelen Herstelmodus" }, - "description": "Cloudron diensten bestaan uit functionaliteiten zoals databases, e-mail en authenticatie. Hint: alle diensten dienen 'groen' te zijn, zo niet dan kun je deze herstarten of het geheugenlimiet verhogen.", + "description": "Diensten bestaan uit functionaliteiten zoals databases, e-mail en authenticatie.", "refresh": "Ververs" }, "settings": { @@ -1315,7 +1315,7 @@ "emailNotVerified": "E-mail is niet geverifieerd" }, "timezone": { - "title": "Tijdzone", + "title": "Systeem Tijdzone", "description": "De huidige tijdzone instelling is {{ timeZone }}. Deze instelling wordt gebruikt voor backup planning en update taken. Tijdseenheden in de gebruikersschermen zijn op basis van de brower’s tijdzone." }, "updates": { @@ -1325,7 +1325,10 @@ "checkForUpdatesAction": "Controleer op updates", "updateAvailableAction": "Update beschikbaar", "stopUpdateAction": "Stop Update", - "version": "Platform versie" + "version": "Platform versie", + "description": "Platform en App Updates worden automatisch uitgevoerd op basis van het Schema in de Systeem Tijdzone.", + "disabled": "Uitgeschakeld", + "schedule": "Schema" }, "privateDockerRegistry": { "subscriptionRequired": "Deze functie is alleen beschikbaar voor betaalde abonnementen.", @@ -1649,7 +1652,7 @@ "outbound": { "tabTitle": "Uitgaand", "title": "E-mail Relay", - "description": "Cloudron gebruikt deze e-mailserver (Smart host) om uitgaande e-mails te versturen van apps in dit domein.", + "description": "Deze e-mailserver (Smart host) wordt gebruikt om uitgaande e-mails te versturen van apps met dit domein.", "mailRelay": { "host": "SMTP Host", "port": "SMTP Poort (STARTTLS)", diff --git a/dashboard/public/views/domains.html b/dashboard/public/views/domains.html index 92d810ea2..23a908296 100644 --- a/dashboard/public/views/domains.html +++ b/dashboard/public/views/domains.html @@ -69,6 +69,14 @@ +
+ + +
+
diff --git a/dashboard/public/views/domains.js b/dashboard/public/views/domains.js index 380ea220d..3f88d24e0 100644 --- a/dashboard/public/views/domains.js +++ b/dashboard/public/views/domains.js @@ -249,6 +249,7 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat gcdnsKey: { keyFileName: '', content: '' }, digitalOceanToken: '', gandiApiKey: '', + gandiTokenType: 'PAT', godaddyApiKey: '', godaddyApiSecret: '', cloudflareToken: '', @@ -325,12 +326,14 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat $scope.domainConfigure.hetznerToken = domain.provider === 'hetzner' ? domain.config.token : ''; $scope.domainConfigure.vultrToken = domain.provider === 'vultr' ? domain.config.token : ''; $scope.domainConfigure.deSecToken = domain.provider === 'desec' ? domain.config.token : ''; - $scope.domainConfigure.gandiApiKey = domain.provider === 'gandi' ? domain.config.token : ''; $scope.domainConfigure.cloudflareToken = domain.provider === 'cloudflare' ? domain.config.token : ''; $scope.domainConfigure.cloudflareEmail = domain.provider === 'cloudflare' ? domain.config.email : ''; $scope.domainConfigure.cloudflareTokenType = domain.provider === 'cloudflare' ? domain.config.tokenType : 'GlobalApiKey'; $scope.domainConfigure.cloudflareDefaultProxyStatus = domain.provider === 'cloudflare' ? !!domain.config.defaultProxyStatus : false; + $scope.domainConfigure.gandiApiKey = domain.provider === 'gandi' ? domain.config.token : ''; + $scope.domainConfigure.gandiTokenType = domain.provider === 'gandi' ? domain.config.tokenType : 'ApiKey'; + $scope.domainConfigure.godaddyApiKey = domain.provider === 'godaddy' ? domain.config.apiKey : ''; $scope.domainConfigure.godaddyApiSecret = domain.provider === 'godaddy' ? domain.config.apiSecret : ''; @@ -410,6 +413,7 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat data.token = $scope.domainConfigure.deSecToken; } else if (provider === 'gandi') { data.token = $scope.domainConfigure.gandiApiKey; + data.tokenType = $scope.domainConfigure.gandiTokenType; } else if (provider === 'godaddy') { data.apiKey = $scope.domainConfigure.godaddyApiKey; data.apiSecret = $scope.domainConfigure.godaddyApiSecret; @@ -489,6 +493,7 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat $scope.domainConfigure.gcdnsKey.content = ''; $scope.domainConfigure.digitalOceanToken = ''; $scope.domainConfigure.gandiApiKey = ''; + $scope.domainConfigure.gandiTokenType = 'PAT'; $scope.domainConfigure.godaddyApiKey = ''; $scope.domainConfigure.godaddyApiSecret = ''; $scope.domainConfigure.cloudflareToken = ''; diff --git a/migrations/20241008145619-domains-gandi-tokenType.js b/migrations/20241008145619-domains-gandi-tokenType.js new file mode 100644 index 000000000..b3d59c194 --- /dev/null +++ b/migrations/20241008145619-domains-gandi-tokenType.js @@ -0,0 +1,16 @@ +'use strict'; + +exports.up = async function(db) { + const domains = await db.runSql('SELECT * FROM domains'); + for (const domain of domains) { + if (domain.provider !== 'gandi') continue; + + const config = JSON.parse(domain.configJson); + config.tokenType = 'ApiKey'; + + await db.runSql('UPDATE domains SET configJson = ? WHERE domain = ?', [ JSON.stringify(config), domain.domain ]); + } +}; + +exports.down = async function( /* db */) { +}; diff --git a/src/dns/gandi.js b/src/dns/gandi.js index db09d206a..183bf385e 100644 --- a/src/dns/gandi.js +++ b/src/dns/gandi.js @@ -36,6 +36,24 @@ function injectPrivateFields(newConfig, currentConfig) { if (newConfig.token === constants.SECRET_PLACEHOLDER) newConfig.token = currentConfig.token; } +function createRequest(method, url, domainConfig) { + assert.strictEqual(typeof method, 'string'); + assert.strictEqual(typeof url, 'string'); + assert.strictEqual(typeof domainConfig, 'object'); + + const request = superagent(method, url).timeout(30 * 1000).ok(() => true); + + // https://api.gandi.net/docs/authentication/ + if (domainConfig.tokenType === 'ApiKey') { + request.set('X-Api-Key', domainConfig.token); + request.set('Authorization', `Apikey ${domainConfig.token}`); + } else { // PAT + request.set('Authorization', `Bearer ${domainConfig.token}`); + } + + return request; +} + async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); @@ -53,11 +71,8 @@ async function upsert(domainObject, location, type, values) { 'rrset_values': values // for mx records, value is already of the ' ' format }; - const [error, response] = await safe(superagent.put(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) - .set('X-Api-Key', domainConfig.token) - .timeout(30 * 1000) - .send(data) - .ok(() => true)); + const [error, response] = await safe(createRequest('PUT', `${GANDI_API}/domains/${zoneName}/records/${name}/${type}`, domainConfig) + .send(data)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); @@ -76,10 +91,7 @@ async function get(domainObject, location, type) { debug(`get: ${name} in zone ${zoneName} of type ${type}`); - const [error, response] = await safe(superagent.get(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) - .set('X-Api-Key', domainConfig.token) - .timeout(30 * 1000) - .ok(() => true)); + const [error, response] = await safe(createRequest('GET', `${GANDI_API}/domains/${zoneName}/records/${name}/${type}`, domainConfig)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); @@ -101,10 +113,7 @@ async function del(domainObject, location, type, values) { debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - const [error, response] = await safe(superagent.del(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) - .set('X-Api-Key', domainConfig.token) - .timeout(30 * 1000) - .ok(() => true)); + const [error, response] = await safe(createRequest('DELETE', `${GANDI_API}/domains/${zoneName}/records/${name}/${type}`, domainConfig)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); if (response.statusCode === 404) return; @@ -131,9 +140,11 @@ async function verifyDomainConfig(domainObject) { zoneName = domainObject.zoneName; if (!domainConfig.token || typeof domainConfig.token !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'token must be a non-empty string'); + if (domainConfig.tokenType !== 'PAT' && domainConfig.tokenType !== 'ApiKey') throw new BoxError(BoxError.BAD_FIELD, 'tokenType is required'); const credentials = { - token: domainConfig.token + token: domainConfig.token, + tokenType: domainConfig.tokenType, }; const ip = '127.0.0.1';