Compare commits

..

3 Commits

Author SHA1 Message Date
Maël Gangloff
76377116ce
fix: do not flag the TLDs as deleted inside the foreach loop 2025-12-19 00:53:22 +01:00
Maël Gangloff
545677ddf7
feat: add log message when a domain is deleted from WHOIS 2025-12-14 13:53:30 +01:00
Maël Gangloff
94a29bc8df
feat: add ipAddress to the logs if anonymous query 2025-12-13 13:43:46 +01:00
7 changed files with 45 additions and 30 deletions

View File

@ -31,7 +31,8 @@ export default defineConfig({
lastUpdated: true,
social: [
{icon: 'github', label: 'GitHub', href: 'https://github.com/maelgangloff/domain-watchdog'},
{icon: 'seti:docker', label: 'Docker', href: 'https://hub.docker.com/r/maelgangloff/domain-watchdog'}
{icon: 'seti:docker', label: 'Docker', href: 'https://hub.docker.com/r/maelgangloff/domain-watchdog'},
{icon: 'external', label: 'Demo', href: 'https://demo.domainwatchdog.eu'}
],
sidebar: [
{slug: 'features'},

View File

@ -49,55 +49,59 @@ flowchart LR
### Framework
The programming language is **PHP**.
The backend is written in [**PHP**](https://www.php.net/docs.php).
The backend is developed using the **Symfony** framework ([documentation](https://symfony.com/doc)).
The backend is developed using the [**Symfony** framework](https://symfony.com/doc).
The API is made possible by the **API Platform**
project ([documentation](https://api-platform.com/docs/symfony/)).
The API is made possible by the [**API Platform** project](https://api-platform.com/docs/symfony/).
### SQL database
### SQL Database
This project requires a **PostgreSQL** database ([documentation](https://www.postgresql.org/docs/current/)).
This project requires a [**PostgreSQL** database](https://www.postgresql.org/docs/current/).
Other database types cannot be used because some migrations were specifically written to leverage the performance of
this database management system.
Other database types cannot be used because some migrations were specifically written to leverage PostgreSQL-specific features and performance optimizations.
### Key-value database
### Key-value Database
A **Redis-compatible** key-value database is required to:
A [**Redis-compatible**](https://redis.io/docs/latest/apis/) key-value database is required to:
- Cache certain values
- Implement locks to limit the possibility of conditional raises
- Store messages to be distributed to workers to process asynchronous actions. For example: updating domain names in a
Watchlist on a high-priority RDAP client queue.
- Store messages for workers to process asynchronous actions
(e.g. updating domain names in a Watchlist on a high-priority RDAP client queue)
## Time Series database
### Time-Series Database
The **InfluxDB** database is optional.
The [**InfluxDB**](https://docs.influxdata.com/) database is optional.
A data point is added for the following events:
- RDAP requests from your instance: response time, requested domain name, HTTP status code, IP address of the RDAP
server, etc.
- RDAP requests from your instance: response time, requested domain name, HTTP status code, IP address of the RDAP server, etc.
- User notifications: adding events to a domain name, changing EPP statuses, etc.
### SSO authentication
An **OAuth 2.0** server is not required to authenticate users.
An **OAuth 2.0** server is optional for user authentication.
Using Single Sign-On (SSO) allows you to delegate user authentication to a third party. This can be useful if you only
want people within your organization to be able to use this project instance. Furthermore, you can then configure
advanced security policies such as passwordless login, passkeys, multifactor authentication, and more.
### Error Reporting
The [**Sentry**](https://docs.sentry.io/platforms/php/guides/symfony/) integration is optional.
When enabled, Sentry captures and reports application errors, providing useful insights for debugging.
You can configure this feature through your projects environment variables.
___
## Frontend
### Framework
The language for frontend development is **TypeScript**.
The language for frontend development is [**TypeScript**](https://www.typescriptlang.org/docs/).
The framework used for the frontend is **React** ([documentation](https://react.dev/reference/react)).
The framework used for the frontend is [**React**](https://react.dev/reference/react).
### Component Library
The component library used is **Ant Design** ([documentation](https://ant.design/components/overview/)).
The component library used is [**Ant Design**](https://ant.design/components/overview/).

View File

@ -166,6 +166,11 @@ final readonly class UpdateDomainHandler
$newDomain = $this->domainRepository->findOneBy(['ldhName' => $domain->getLdhName()]);
if (!$deleted && null !== $newDomain && $newDomain->getDeleted()) {
$this->logger->info('Domain has been deleted from WHOIS: a notification is sent to user', [
'ldhName' => $message->ldhName,
'username' => $watchlist->getUser()->getUserIdentifier(),
]);
$notification = new DomainDeletedNotification($this->sender, $domain);
$this->mailer->send($notification->asEmailMessage(new Recipient($watchlist->getUser()->getEmail()))->getMessage());
$this->chatNotificationService->sendChatNotification($watchlist, $notification);

View File

@ -18,7 +18,8 @@ final readonly class UpdateRdapServersHandler
{
public function __construct(
private OfficialDataService $officialDataService,
private ParameterBagInterface $bag, private DomainRepository $domainRepository,
private ParameterBagInterface $bag,
private DomainRepository $domainRepository,
) {
}

View File

@ -159,13 +159,13 @@ class OfficialDataService
));
array_shift($tldList);
$this->tldRepository->setAllTldAsDeleted();
foreach ($tldList as $tld) {
if ('' === $tld) {
continue;
}
$this->tldRepository->setAllTldAsDeleted();
$tldEntity = $this->tldRepository->findOneBy(['tld' => $tld]);
if (null === $tldEntity) {

View File

@ -65,6 +65,10 @@ class RDAPService
'Private',
];
public const PENDING_DELETE_DURATION_DAYS = 5;
public const REDEMPTION_PERIOD_DURATION_DAYS = 30;
public const AUTO_RENEW_PERIOD_DURATION_DAYS = 45;
public function __construct(
private readonly HttpClientInterface $client,
private readonly EntityRepository $entityRepository,
@ -740,13 +744,13 @@ class RDAPService
in_array('pending delete', $lastStatus->getAddStatus())
|| in_array('redemption period', $lastStatus->getDeleteStatus()))
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'. 5 .'D')));
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'.self::PENDING_DELETE_DURATION_DAYS.'D')));
}
if ($domain->isRedemptionPeriod()
&& in_array('redemption period', $lastStatus->getAddStatus())
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'.(30 + 5).'D')));
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'.(self::REDEMPTION_PERIOD_DURATION_DAYS + self::PENDING_DELETE_DURATION_DAYS).'D')));
}
return null;
@ -772,7 +776,7 @@ class RDAPService
[$expiredAt, $deletedAt] = $this->getRelevantDates($domain);
if ($expiredAt) {
$guess = self::daysBetween($now, $expiredAt->add(new \DateInterval('P'.(45 + 30 + 5).'D')));
$guess = self::daysBetween($now, $expiredAt->add(new \DateInterval('P'.(self::AUTO_RENEW_PERIOD_DURATION_DAYS + self::REDEMPTION_PERIOD_DURATION_DAYS + self::PENDING_DELETE_DURATION_DAYS).'D')));
}
if ($deletedAt) {
@ -781,7 +785,7 @@ class RDAPService
return 0;
}
$guess = self::daysBetween($now, $deletedAt->add(new \DateInterval('P'. 30 .'D')));
$guess = self::daysBetween($now, $deletedAt->add(new \DateInterval('P'.self::REDEMPTION_PERIOD_DURATION_DAYS.'D')));
}
return self::returnExpiresIn([

View File

@ -72,6 +72,7 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
$idnDomain = RDAPService::convertToIdn($uriVariables['ldhName']);
$user = $this->security->getUser();
$request = $this->requestStack->getCurrentRequest();
if (null !== $user) {
$this->logger->info('User wants to update a domain name', [
@ -81,11 +82,10 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
} else {
$this->logger->info('Anonymous wants to update a domain name', [
'ldhName' => $idnDomain,
'ipAddress' => $request->getClientIp(),
]);
}
$request = $this->requestStack->getCurrentRequest();
/** @var ?Domain $domain */
$domain = $this->domainRepository->findOneBy(['ldhName' => $idnDomain]);
// If the domain name exists in the database, recently updated and not important, we return the stored Domain