diff --git a/.env b/.env
index 9662893..cfb68d2 100644
--- a/.env
+++ b/.env
@@ -86,6 +86,7 @@ LIMITED_FEATURES=false
LIMIT_MAX_WATCHLIST=0
LIMIT_MAX_WATCHLIST_DOMAINS=0
LIMIT_MAX_WATCHLIST_WEBHOOKS=0
+PUBLIC_RDAP_LOOKUP_ENABLE=false
# STATISTICS
INFLUXDB_ENABLED=false
diff --git a/assets/App.tsx b/assets/App.tsx
index e7d9abc..1f6fe05 100644
--- a/assets/App.tsx
+++ b/assets/App.tsx
@@ -1,4 +1,4 @@
-import {Button, ConfigProvider, Drawer, Flex, Layout, theme, Typography} from 'antd'
+import {Button, ConfigProvider, Drawer, Flex, Layout, message, theme, Typography} from 'antd'
import {Link, Navigate, Route, Routes, useLocation, useNavigate} from 'react-router-dom'
import TextPage from './pages/TextPage'
import DomainSearchPage from './pages/search/DomainSearchPage'
@@ -9,8 +9,8 @@ import WatchlistPage from './pages/tracking/WatchlistPage'
import UserPage from './pages/UserPage'
import type {PropsWithChildren} from 'react'
import React, { useCallback, useEffect, useMemo, useState} from 'react'
-import {getUser} from './utils/api'
-import LoginPage, {AuthenticatedContext} from './pages/LoginPage'
+import {getConfiguration, getUser, type InstanceConfig} from './utils/api'
+import LoginPage from './pages/LoginPage'
import ConnectorPage from './pages/tracking/ConnectorPage'
import NotFoundPage from './pages/NotFoundPage'
import useBreakpoint from './hooks/useBreakpoint'
@@ -19,12 +19,14 @@ import {jt, t} from 'ttag'
import {MenuOutlined} from '@ant-design/icons'
import TrackedDomainPage from './pages/tracking/TrackedDomainPage'
import IcannRegistrarPage from "./pages/infrastructure/IcannRegistrarPage"
+import type {AuthContextType} from "./contexts"
+import {AuthenticatedContext, ConfigurationContext} from "./contexts"
const PROJECT_LINK = 'https://github.com/maelgangloff/domain-watchdog'
const LICENSE_LINK = 'https://www.gnu.org/licenses/agpl-3.0.txt'
const ProjectLink = Domain Watchdog
-const LicenseLink = AGPL-3.0-or-later
+const LicenseLink = AGPL-3.0-or-later
function SiderWrapper(props: PropsWithChildren<{sidebarCollapsed: boolean, setSidebarCollapsed: (collapsed: boolean) => void}>): React.ReactElement {
const {sidebarCollapsed, setSidebarCollapsed, children} = props
@@ -68,18 +70,22 @@ export default function App(): React.ReactElement {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
- const authenticated = useCallback((authenticated: boolean) => {
- setIsAuthenticated(authenticated)
- }, [])
-
- const contextValue = useMemo(() => ({
- authenticated,
- setIsAuthenticated
- }), [authenticated, setIsAuthenticated])
+ const [configuration, setConfiguration] = useState(undefined)
const [darkMode, setDarkMode] = useState(false)
-
const windowQuery = window.matchMedia('(prefers-color-scheme:dark)')
+ const [messageApi, contextHolder] = message.useMessage()
+
+
+ const authContextValue: AuthContextType = useMemo(() => ({
+ isAuthenticated,
+ setIsAuthenticated
+ }), [isAuthenticated])
+
+ const configContextValue = useMemo(() => ({
+ configuration,
+ }), [configuration])
+
const darkModeChange = useCallback((event: MediaQueryListEvent) => {
setDarkMode(event.matches)
}, [])
@@ -93,14 +99,19 @@ export default function App(): React.ReactElement {
useEffect(() => {
setDarkMode(windowQuery.matches)
- getUser().then(() => {
- setIsAuthenticated(true)
- if (location.pathname === '/login') navigate('/home')
- }).catch(() => {
- setIsAuthenticated(false)
- const pathname = location.pathname
- if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) navigate('/home')
- })
+ getConfiguration().then(configuration => {
+ setConfiguration(configuration)
+
+ getUser().then(() => {
+ setIsAuthenticated(true)
+ if (location.pathname === '/login') navigate('/home')
+ }).catch(() => {
+ setIsAuthenticated(false)
+ const pathname = location.pathname
+ if(configuration.publicRdapLookupEnabled) return navigate('/search/domain')
+ if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) return navigate('/home')
+ })
+ }).catch(() => messageApi.error(t`Unable to contact the server, please reload the page.`))
}, [])
return (
@@ -109,10 +120,11 @@ export default function App(): React.ReactElement {
algorithm: darkMode ? theme.darkAlgorithm : undefined
}}
>
-
+
+
-
+
@@ -128,8 +140,9 @@ export default function App(): React.ReactElement {
minHeight: 360
}}
>
+ {contextHolder}
- }/>
+ }/>
}/>
}/>
@@ -158,8 +171,8 @@ export default function App(): React.ReactElement {
-
-
+
+
+
)
}
diff --git a/assets/components/LoginForm.tsx b/assets/components/LoginForm.tsx
index 966353f..c78496b 100644
--- a/assets/components/LoginForm.tsx
+++ b/assets/components/LoginForm.tsx
@@ -2,10 +2,10 @@ import {Button, Flex, Form, Input, message} from 'antd'
import {t} from 'ttag'
import React, {useContext, useEffect, useState} from 'react'
import {getUser, login} from '../utils/api'
-import {AuthenticatedContext} from '../pages/LoginPage'
import {useNavigate} from 'react-router-dom'
import {showErrorAPI} from '../utils/functions/showErrorAPI'
+import {AuthenticatedContext} from "../contexts"
interface FieldType {
username: string
@@ -66,9 +66,9 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
-
+
{ssoLogin &&
- {domain
+ {domain && isAuthenticated
&& setAddToWatchlistModal(false),
cancelText: t`Cancel`,
okText: t`Add`
- }}
+ }}
/>
>
}
diff --git a/assets/utils/api/index.ts b/assets/utils/api/index.ts
index e6aa850..565459d 100644
--- a/assets/utils/api/index.ts
+++ b/assets/utils/api/index.ts
@@ -109,6 +109,7 @@ export interface InstanceConfig {
ssoLogin: boolean
limtedFeatures: boolean
registerEnabled: boolean
+ publicRdapLookupEnabled: boolean
}
export interface Statistics {
diff --git a/config/packages/rate_limiter.yaml b/config/packages/rate_limiter.yaml
index 6c0af89..49f9b41 100644
--- a/config/packages/rate_limiter.yaml
+++ b/config/packages/rate_limiter.yaml
@@ -22,5 +22,10 @@ framework:
user_rdap_requests:
policy: sliding_window
- limit: 10
+ limit: 60
+ interval: '1 hour'
+
+ public_rdap_requests:
+ policy: sliding_window
+ limit: 30
interval: '1 hour'
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 07fe14e..48744a2 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -60,6 +60,7 @@ security:
- { path: ^/api$, roles: PUBLIC_ACCESS }
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
- { path: ^/api/register$, roles: PUBLIC_ACCESS }
+ - { path: ^/api/domains/*, roles: CAN_RDAP_LOOKUP }
- { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/calendar$", roles: PUBLIC_ACCESS }
- { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/rss", roles: PUBLIC_ACCESS }
- { path: "^/api/config$", roles: PUBLIC_ACCESS }
diff --git a/config/services.yaml b/config/services.yaml
index 9f7d0c6..f6ba5e5 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -7,13 +7,27 @@ parameters:
custom_rdap_servers_file: '%kernel.project_dir%/config/app/custom_rdap_servers.yaml'
mailer_sender_email: '%env(string:MAILER_SENDER_EMAIL)%'
+
mailer_sender_name: '%env(string:MAILER_SENDER_NAME)%'
+ env(MAILER_SENDER_NAME): Domain Watchdog
+
oauth_enabled: '%env(OAUTH_CLIENT_ID)%'
+
sso_auto_redirect: '%env(bool:SSO_AUTO_REDIRECT)%'
+ env(SSO_AUTO_REDIRECT): false
+
registration_enabled: '%env(bool:REGISTRATION_ENABLED)%'
+ env(REGISTRATION_ENABLED): true
+
registration_verify_email: '%env(bool:REGISTRATION_VERIFY_EMAIL)%'
+ env(REGISTRATION_VERIFY_EMAIL): false
+
+ public_rdap_lookup_enabled: '%env(bool:PUBLIC_RDAP_LOOKUP_ENABLED)%'
+ env(PUBLIC_RDAP_LOOKUP_ENABLED): false
limited_features: '%env(bool:LIMITED_FEATURES)%'
+ env(LIMITED_FEATURES): false
+
limit_max_watchlist: '%env(int:LIMIT_MAX_WATCHLIST)%'
limit_max_watchlist_domains: '%env(int:LIMIT_MAX_WATCHLIST_DOMAINS)%'
limit_max_watchlist_webhooks: '%env(int:LIMIT_MAX_WATCHLIST_WEBHOOKS)%'
@@ -21,6 +35,8 @@ parameters:
outgoing_ip: '%env(string:OUTGOING_IP)%'
influxdb_enabled: '%env(bool:INFLUXDB_ENABLED)%'
+ env(INFLUXDB_ENABLED): false
+
influxdb_url: '%env(string:INFLUXDB_URL)%'
influxdb_token: '%env(string:INFLUXDB_TOKEN)%'
influxdb_bucket: '%env(string:INFLUXDB_BUCKET)%'
diff --git a/docs/src/content/docs/en/install-config/configuration.mdx b/docs/src/content/docs/en/install-config/configuration.mdx
index 3a0f29e..080eee0 100644
--- a/docs/src/content/docs/en/install-config/configuration.mdx
+++ b/docs/src/content/docs/en/install-config/configuration.mdx
@@ -9,31 +9,32 @@ import {LinkCard} from '@astrojs/starlight/components';
## Environment variables
-| Variable | Description | Default |
-|--------------------------------|----------------------------------------------|:---------------------------:|
-| `DATABASE_URL` | Please check Symfony config | |
-| `OUTGOING_IP` | Outgoing IPv4, needed for some providers | |
-| `INFLUXDB_ENABLED` | Enable the connection with InfluxDB | `false` |
-| `INFLUXDB_URL` | InfluxDB URL | `http://localhost:8086` |
-| `INFLUXDB_TOKEN` | InfluxDB token | |
-| `INFLUXDB_BUCKET` | InfluxDB bucket name | `domainwatchdog` |
-| `INFLUXDB_ORG` | InfluxDB organization | `domainwatchdog` |
-| `LIMITED_FEATURES` | Limit certain features for users | `false` |
-| `LIMIT_MAX_WATCHLIST` | Maximum number of Watchlists per user | `0` |
-| `LIMIT_MAX_WATCHLIST_DOMAINS` | Maximum number of domains per Watchlist | `0` |
-| `LIMIT_MAX_WATCHLIST_WEBHOOKS` | Maximum number of webhooks per Watchlist | `0` |
-| `MAILER_SENDER_NAME` | Name of the sender of emails | `Domain Watchdog` |
-| `MAILER_SENDER_EMAIL` | Sender's email address | `notifications@example.com` |
-| `REGISTRATION_ENABLED` | Enable user registration | `true` |
-| `REGISTRATION_VERIFY_EMAIL` | Verify email addresses during registration | `false` |
-| `MAILER_DSN` | Please check Symfony config | `null://null` |
-| `OAUTH_CLIENT_ID` | Client ID (OAuth 2.0) for using external SSO | |
-| `OAUTH_CLIENT_SECRET` | Client secret (OAuth 2.0) | |
-| `OAUTH_AUTHORIZATION_URL` | Authorization URL (OAuth 2.0) | |
-| `OAUTH_TOKEN_URL` | Token URL (OAuth 2.0) | |
-| `OAUTH_USERINFO_URL` | User Info URL (OAuth 2.0) | |
-| `OAUTH_SCOPE` | Scope (OAuth 2.0) | |
-| `SSO_AUTO_REDIRECT` | Redirection to the SSO auth URL | `false` |
+| Variable | Description | Default |
+|--------------------------------|------------------------------------------------|:---------------------------:|
+| `DATABASE_URL` | Please check Symfony config | |
+| `OUTGOING_IP` | Outgoing IPv4, needed for some providers | |
+| `INFLUXDB_ENABLED` | Enable the connection with InfluxDB | `false` |
+| `INFLUXDB_URL` | InfluxDB URL | `http://localhost:8086` |
+| `INFLUXDB_TOKEN` | InfluxDB token | |
+| `INFLUXDB_BUCKET` | InfluxDB bucket name | `domainwatchdog` |
+| `INFLUXDB_ORG` | InfluxDB organization | `domainwatchdog` |
+| `LIMITED_FEATURES` | Limit certain features for users | `false` |
+| `LIMIT_MAX_WATCHLIST` | Maximum number of Watchlists per user | `0` |
+| `LIMIT_MAX_WATCHLIST_DOMAINS` | Maximum number of domains per Watchlist | `0` |
+| `LIMIT_MAX_WATCHLIST_WEBHOOKS` | Maximum number of webhooks per Watchlist | `0` |
+| `MAILER_SENDER_NAME` | Name of the sender of emails | `Domain Watchdog` |
+| `MAILER_SENDER_EMAIL` | Sender's email address | `notifications@example.com` |
+| `REGISTRATION_ENABLED` | Enable user registration | `true` |
+| `REGISTRATION_VERIFY_EMAIL` | Verify email addresses during registration | `false` |
+| `MAILER_DSN` | Please check Symfony config | `null://null` |
+| `OAUTH_CLIENT_ID` | Client ID (OAuth 2.0) for using external SSO | |
+| `OAUTH_CLIENT_SECRET` | Client secret (OAuth 2.0) | |
+| `OAUTH_AUTHORIZATION_URL` | Authorization URL (OAuth 2.0) | |
+| `OAUTH_TOKEN_URL` | Token URL (OAuth 2.0) | |
+| `OAUTH_USERINFO_URL` | User Info URL (OAuth 2.0) | |
+| `OAUTH_SCOPE` | Scope (OAuth 2.0) | |
+| `SSO_AUTO_REDIRECT` | Redirect to the SSO auth URL | `false` |
+| `PUBLIC_RDAP_LOOKUP_ENABLE` | Allow unauthenticated domain name name lookups | `false` |
## Authentication
diff --git a/docs/src/content/docs/fr/install-config/configuration.mdx b/docs/src/content/docs/fr/install-config/configuration.mdx
index 4f829bd..35c241e 100644
--- a/docs/src/content/docs/fr/install-config/configuration.mdx
+++ b/docs/src/content/docs/fr/install-config/configuration.mdx
@@ -34,6 +34,7 @@ import {LinkCard} from '@astrojs/starlight/components';
| `OAUTH_USERINFO_URL` | URL des informations utilisateur (OAuth 2.0) | |
| `OAUTH_SCOPE` | Scope (OAuth 2.0) | |
| `SSO_AUTO_REDIRECT` | Redirection vers l'URL d'authentification du SSO | `false` |
+| `PUBLIC_RDAP_LOOKUP_ENABLE` | Autoriser les recherches anonymes de domaines | `false` |
## Authentification
diff --git a/src/Controller/InstanceController.php b/src/Controller/InstanceController.php
index 7223380..68bc5c8 100644
--- a/src/Controller/InstanceController.php
+++ b/src/Controller/InstanceController.php
@@ -15,7 +15,8 @@ class InstanceController extends AbstractController
->setLimitedFeatures($this->getParameter('limited_features') ?? false)
->setOauthEnabled($this->getParameter('oauth_enabled') ?? false)
->setRegisterEnabled($this->getParameter('registration_enabled') ?? false)
- ->setSsoAutoRedirect($this->getParameter('sso_auto_redirect') ?? false);
+ ->setSsoAutoRedirect($this->getParameter('sso_auto_redirect') ?? false)
+ ->setPublicRdapLookupEnabled($this->getParameter('public_rdap_lookup_enabled') ?? false);
return $instance;
}
diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php
index d1c990d..3507300 100644
--- a/src/Entity/Domain.php
+++ b/src/Entity/Domain.php
@@ -71,7 +71,7 @@ use Symfony\Component\Serializer\Attribute\SerializedName;
],
),
],
- provider: AutoRegisterDomainProvider::class
+ provider: AutoRegisterDomainProvider::class,
)]
class Domain
{
diff --git a/src/Entity/Instance.php b/src/Entity/Instance.php
index a40b953..64fe656 100644
--- a/src/Entity/Instance.php
+++ b/src/Entity/Instance.php
@@ -30,6 +30,8 @@ class Instance
private ?bool $ssoAutoRedirect = null;
+ private ?bool $publicRdapLookupEnabled = null;
+
public function isSsoLogin(): ?bool
{
return $this->oauthEnabled;
@@ -77,4 +79,16 @@ class Instance
return $this;
}
+
+ public function getPublicRdapLookupEnabled(): ?bool
+ {
+ return $this->publicRdapLookupEnabled;
+ }
+
+ public function setPublicRdapLookupEnabled(?bool $publicRdapLookupEnabled): static
+ {
+ $this->publicRdapLookupEnabled = $publicRdapLookupEnabled;
+
+ return $this;
+ }
}
diff --git a/src/Security/Voter/RdapLookupVoter.php b/src/Security/Voter/RdapLookupVoter.php
new file mode 100644
index 0000000..3d10c4f
--- /dev/null
+++ b/src/Security/Voter/RdapLookupVoter.php
@@ -0,0 +1,33 @@
+security->isGranted('IS_AUTHENTICATED_FULLY')) {
+ return true;
+ }
+
+ return $this->parameterBag->get('public_rdap_lookup_enabled');
+ }
+}
diff --git a/src/State/AutoRegisterDomainProvider.php b/src/State/AutoRegisterDomainProvider.php
index 27a1ebb..50be99a 100644
--- a/src/State/AutoRegisterDomainProvider.php
+++ b/src/State/AutoRegisterDomainProvider.php
@@ -41,6 +41,7 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
private KernelInterface $kernel,
private ParameterBagInterface $parameterBag,
private RateLimiterFactory $userRdapRequestsLimiter,
+ private RateLimiterFactory $publicRdapRequestsLimiter,
private Security $security,
private LoggerInterface $logger,
private DomainRepository $domainRepository,
@@ -68,14 +69,20 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$fromWatchlist = array_key_exists('root_operation', $context) && Watchlist::class === $context['root_operation']?->getClass();
-
- $userId = $this->security->getUser()->getUserIdentifier();
$idnDomain = RDAPService::convertToIdn($uriVariables['ldhName']);
- $this->logger->info('User wants to update a domain name', [
- 'username' => $userId,
- 'ldhName' => $idnDomain,
- ]);
+ $user = $this->security->getUser();
+
+ if (null !== $user) {
+ $this->logger->info('User wants to update a domain name', [
+ 'username' => $user->getUserIdentifier(),
+ 'ldhName' => $idnDomain,
+ ]);
+ } else {
+ $this->logger->info('Anonymous wants to update a domain name', [
+ 'ldhName' => $idnDomain,
+ ]);
+ }
$request = $this->requestStack->getCurrentRequest();
@@ -96,7 +103,7 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
}
if (false === $this->kernel->isDebug() && true === $this->parameterBag->get('limited_features')) {
- $limiter = $this->userRdapRequestsLimiter->create($userId);
+ $limiter = $user ? $this->userRdapRequestsLimiter->create($user->getUserIdentifier()) : $this->publicRdapRequestsLimiter->create($request->getClientIp());
$limit = $limiter->consume();
if (!$limit->isAccepted()) {
diff --git a/tests/State/AutoRegisterDomainProviderTest.php b/tests/State/AutoRegisterDomainProviderTest.php
index 2a4f390..4cd4b88 100644
--- a/tests/State/AutoRegisterDomainProviderTest.php
+++ b/tests/State/AutoRegisterDomainProviderTest.php
@@ -27,6 +27,15 @@ final class AutoRegisterDomainProviderTest extends ApiTestCase
$this->assertMatchesResourceItemJsonSchema(Domain::class);
}
+ #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')]
+ public function testRegisterDomainAnonymousUnauthorized(): void
+ {
+ $client = $this->createClient();
+ $client->request('GET', '/api/domains/example.com');
+
+ $this->assertResponseStatusCodeSame(401);
+ }
+
#[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')]
public function testRegisterDomainAlreadyUpdated(): void
{
diff --git a/translations/translations.pot b/translations/translations.pot
index d7e97ec..08beb1c 100644
--- a/translations/translations.pot
+++ b/translations/translations.pot
@@ -3,31 +3,35 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
-#: assets/App.tsx:161
+#: assets/App.tsx:114
+msgid "Unable to contact the server, please reload the page."
+msgstr ""
+
+#: assets/App.tsx:174
msgid "TOS"
msgstr ""
-#: assets/App.tsx:162
+#: assets/App.tsx:175
msgid "Privacy Policy"
msgstr ""
-#: assets/App.tsx:163
+#: assets/App.tsx:176
msgid "FAQ"
msgstr ""
-#: assets/App.tsx:169
+#: assets/App.tsx:182
msgid "Documentation"
msgstr ""
-#: assets/App.tsx:177
+#: assets/App.tsx:190
msgid "Source code"
msgstr ""
-#: assets/App.tsx:185
+#: assets/App.tsx:198
msgid "Submit an issue"
msgstr ""
-#: assets/App.tsx:190
+#: assets/App.tsx:203
#, javascript-format
msgid ""
"${ ProjectLink } is an open source project distributed under the ${ "
@@ -87,7 +91,7 @@ msgid "Log in with SSO"
msgstr ""
#: assets/components/RegisterForm.tsx:54
-#: assets/pages/LoginPage.tsx:60
+#: assets/pages/LoginPage.tsx:50
msgid "Register"
msgstr ""
@@ -168,60 +172,60 @@ msgstr ""
msgid "This domain name does not appear to be valid"
msgstr ""
-#: assets/components/Sider.tsx:31
+#: assets/components/Sider.tsx:35
msgid "Home"
msgstr ""
-#: assets/components/Sider.tsx:37
+#: assets/components/Sider.tsx:41
msgid "Search"
msgstr ""
-#: assets/components/Sider.tsx:43
+#: assets/components/Sider.tsx:47
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:179
msgid "Domain"
msgstr ""
-#: assets/components/Sider.tsx:44
+#: assets/components/Sider.tsx:48
msgid "Domain Finder"
msgstr ""
-#: assets/components/Sider.tsx:63
-#: assets/components/Sider.tsx:64
+#: assets/components/Sider.tsx:67
+#: assets/components/Sider.tsx:68
msgid "Infrastructure"
msgstr ""
-#: assets/components/Sider.tsx:70
+#: assets/components/Sider.tsx:74
#: assets/pages/StatisticsPage.tsx:112
#: assets/pages/infrastructure/TldPage.tsx:81
msgid "TLD"
msgstr ""
-#: assets/components/Sider.tsx:71
+#: assets/components/Sider.tsx:75
msgid "TLD list"
msgstr ""
-#: assets/components/Sider.tsx:78
-#: assets/components/Sider.tsx:79
+#: assets/components/Sider.tsx:82
+#: assets/components/Sider.tsx:83
msgid "ICANN list"
msgstr ""
-#: assets/components/Sider.tsx:87
+#: assets/components/Sider.tsx:91
msgid "Tracking"
msgstr ""
-#: assets/components/Sider.tsx:93
+#: assets/components/Sider.tsx:97
msgid "My Watchlists"
msgstr ""
-#: assets/components/Sider.tsx:100
+#: assets/components/Sider.tsx:104
msgid "Tracking table"
msgstr ""
-#: assets/components/Sider.tsx:107
+#: assets/components/Sider.tsx:111
msgid "My Connectors"
msgstr ""
-#: assets/components/Sider.tsx:131
+#: assets/components/Sider.tsx:135
#.
#. {
#. key: 'tools',
@@ -240,18 +244,18 @@ msgstr ""
msgid "Statistics"
msgstr ""
-#: assets/components/Sider.tsx:141
+#: assets/components/Sider.tsx:145
#: assets/pages/UserPage.tsx:17
msgid "My Account"
msgstr ""
-#: assets/components/Sider.tsx:146
+#: assets/components/Sider.tsx:150
msgid "Log out"
msgstr ""
-#: assets/components/Sider.tsx:154
-#: assets/pages/LoginPage.tsx:46
-#: assets/pages/LoginPage.tsx:60
+#: assets/components/Sider.tsx:158
+#: assets/pages/LoginPage.tsx:36
+#: assets/pages/LoginPage.tsx:50
msgid "Log in"
msgstr ""
@@ -690,7 +694,7 @@ msgstr ""
msgid "Sponsored Top-Level-Domains"
msgstr ""
-#: assets/pages/LoginPage.tsx:46
+#: assets/pages/LoginPage.tsx:36
msgid "Create an account"
msgstr ""
@@ -698,35 +702,35 @@ msgstr ""
msgid "Sorry, the page you visited does not exist."
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:36
+#: assets/pages/search/DomainSearchPage.tsx:37
msgid "Found !"
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:53
+#: assets/pages/search/DomainSearchPage.tsx:54
#, javascript-format
msgid "${ ldhName } added to ${ watchlist.name }"
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:70
+#: assets/pages/search/DomainSearchPage.tsx:71
msgid ""
"Although the domain exists in my database, it has been deleted from the "
"WHOIS by its registrar."
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:82
+#: assets/pages/search/DomainSearchPage.tsx:83
msgid "Add to Watchlist"
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:92
+#: assets/pages/search/DomainSearchPage.tsx:93
#, javascript-format
msgid "Add ${ domainLdhName } to a Watchlist"
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:95
+#: assets/pages/search/DomainSearchPage.tsx:96
msgid "Cancel"
msgstr ""
-#: assets/pages/search/DomainSearchPage.tsx:96
+#: assets/pages/search/DomainSearchPage.tsx:97
msgid "Add"
msgstr ""