mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-22 04:05:41 +00:00
Merge branch 'feat/namecom-support'
This commit is contained in:
commit
8f7b81bff3
@ -1,8 +1,8 @@
|
|||||||
import type { FormInstance} from 'antd'
|
import type {FormInstance} from 'antd'
|
||||||
import {Alert, Button, Checkbox, Form, Input, Popconfirm, Select, Space, Typography} from 'antd'
|
import {Alert, Button, Checkbox, Form, Input, Popconfirm, Select, Space, Typography} from 'antd'
|
||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import type {Connector} from '../../../utils/api/connectors'
|
import type {Connector} from '../../../utils/api/connectors'
|
||||||
import { ConnectorProvider} from '../../../utils/api/connectors'
|
import {ConnectorProvider} from '../../../utils/api/connectors'
|
||||||
import {t} from 'ttag'
|
import {t} from 'ttag'
|
||||||
import {BankOutlined} from '@ant-design/icons'
|
import {BankOutlined} from '@ant-design/icons'
|
||||||
import {
|
import {
|
||||||
@ -21,7 +21,7 @@ const formItemLayoutWithOutLabel = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate: (values: Connector) => void }) {
|
export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate: (values: Connector) => void }) {
|
||||||
const [provider, setProvider] = useState<string>()
|
const [provider, setProvider] = useState<ConnectorProvider>()
|
||||||
const ovhFields = ovhFieldsFunction()
|
const ovhFields = ovhFieldsFunction()
|
||||||
const ovhEndpointList = ovhEndpointListFunction()
|
const ovhEndpointList = ovhEndpointListFunction()
|
||||||
const ovhSubsidiaryList = ovhSubsidiaryListFunction()
|
const ovhSubsidiaryList = ovhSubsidiaryListFunction()
|
||||||
@ -63,7 +63,16 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{
|
{
|
||||||
provider === ConnectorProvider.OVH && <>
|
provider !== undefined && <>
|
||||||
|
{
|
||||||
|
[ConnectorProvider.AutoDNS, ConnectorProvider['Name.com']].includes(provider) && <Alert
|
||||||
|
message={t`This provider does not provide a list of supported TLD. Please double check if the domain you want to register is supported.`}
|
||||||
|
type='warning'
|
||||||
|
style={{marginBottom: '2em'}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
provider === ConnectorProvider.OVHcloud && <>
|
||||||
{
|
{
|
||||||
Object.keys(ovhFields).map(fieldName => <Form.Item
|
Object.keys(ovhFields).map(fieldName => <Form.Item
|
||||||
key={ovhFields[fieldName as keyof typeof ovhFields]}
|
key={ovhFields[fieldName as keyof typeof ovhFields]}
|
||||||
@ -120,7 +129,7 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
provider === ConnectorProvider.GANDI && <>
|
provider === ConnectorProvider.Gandi && <>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t`Personal Access Token (PAT)`}
|
label={t`Personal Access Token (PAT)`}
|
||||||
name={['authData', 'token']}
|
name={['authData', 'token']}
|
||||||
@ -142,11 +151,7 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
provider === ConnectorProvider.AUTODNS && <>
|
provider === ConnectorProvider.AutoDNS && <>
|
||||||
<Alert
|
|
||||||
message={t`This provider does not provide a list of supported TLD. Please double check if the domain you want to register is supported.`}
|
|
||||||
type='warning'
|
|
||||||
/>
|
|
||||||
<br/>
|
<br/>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t`AutoDNS Username`}
|
label={t`AutoDNS Username`}
|
||||||
@ -195,7 +200,6 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
>
|
>
|
||||||
<Input autoComplete='off' required={false} placeholder='4'/>
|
<Input autoComplete='off' required={false} placeholder='4'/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
valuePropName='checked'
|
valuePropName='checked'
|
||||||
label={t`Owner confirmation`}
|
label={t`Owner confirmation`}
|
||||||
@ -212,7 +216,7 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
provider === ConnectorProvider.NAMECHEAP && <>
|
provider === ConnectorProvider.Namecheap && <>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t`Username`}
|
label={t`Username`}
|
||||||
name={['authData', 'ApiUser']}
|
name={['authData', 'ApiUser']}
|
||||||
@ -227,9 +231,22 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
provider !== undefined && <>
|
provider === ConnectorProvider['Name.com'] && <>
|
||||||
|
<Form.Item
|
||||||
|
label={t`Username`}
|
||||||
|
name={['authData', 'username']}
|
||||||
|
>
|
||||||
|
<Input autoComplete='off'/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t`API key`}
|
||||||
|
name={['authData', 'token']}
|
||||||
|
>
|
||||||
|
<Input autoComplete='off'/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
valuePropName='checked'
|
valuePropName='checked'
|
||||||
label={t`API Terms of Service`}
|
label={t`API Terms of Service`}
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import {jt, t} from 'ttag'
|
|||||||
import {DeleteFilled} from '@ant-design/icons'
|
import {DeleteFilled} from '@ant-design/icons'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type {Connector} from '../../../utils/api/connectors'
|
import type {Connector} from '../../../utils/api/connectors'
|
||||||
import {deleteConnector} from '../../../utils/api/connectors'
|
import { ConnectorProvider, deleteConnector} from '../../../utils/api/connectors'
|
||||||
|
import {tosHyperlink} from "../../../utils/providers"
|
||||||
|
|
||||||
const {useToken} = theme
|
const {useToken} = theme
|
||||||
|
|
||||||
@ -24,12 +25,13 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
|
|||||||
{new Date(connector.createdAt).toLocaleString()}
|
{new Date(connector.createdAt).toLocaleString()}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
const {watchlistCount} = connector
|
const {watchlistCount} = connector
|
||||||
|
const connectorName = Object.keys(ConnectorProvider).find(p => ConnectorProvider[p as keyof typeof ConnectorProvider] === connector.provider)
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<Card
|
<Card
|
||||||
hoverable title={<Space>
|
hoverable title={<Space>
|
||||||
{t`Connector ${connector.provider}`}<Typography.Text code>{connector.id}</Typography.Text>
|
{t`Connector ${connectorName}`}<Typography.Text code>{connector.id}</Typography.Text>
|
||||||
</Space>}
|
</Space>}
|
||||||
size='small'
|
size='small'
|
||||||
style={{width: '100%'}}
|
style={{width: '100%'}}
|
||||||
@ -44,8 +46,16 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
|
|||||||
>
|
>
|
||||||
<Typography.Paragraph>{jt`Creation date: ${createdAt}`}</Typography.Paragraph>
|
<Typography.Paragraph>{jt`Creation date: ${createdAt}`}</Typography.Paragraph>
|
||||||
<Typography.Paragraph>{t`Used in: ${watchlistCount} Watchlist`}</Typography.Paragraph>
|
<Typography.Paragraph>{t`Used in: ${watchlistCount} Watchlist`}</Typography.Paragraph>
|
||||||
<Card.Meta description={t`You can stop using a connector at any time. To delete a connector, you must remove it from each linked Watchlist.
|
<Card.Meta description={
|
||||||
The creation date corresponds to the date on which you consented to the creation of the connector and on which you declared in particular that you fulfilled the conditions of use of the supplier's API, waived the right of withdrawal and were of the minimum age to consent to these conditions.`}/>
|
<>
|
||||||
|
{t`You can stop using a connector at any time. To delete a connector, you must remove it from each linked Watchlist.
|
||||||
|
The creation date corresponds to the date on which you consented to the creation of the connector and on which you declared in particular that you fulfilled the conditions of use of the supplier's API, waived the right of withdrawal and were of the minimum age to consent to these conditions.`}
|
||||||
|
|
||||||
|
<Typography.Link href={tosHyperlink(connector.provider)}>
|
||||||
|
{t`The Provider’s conditions are accessible by following this hyperlink.`}
|
||||||
|
</Typography.Link>
|
||||||
|
</>
|
||||||
|
}/>
|
||||||
</Card>
|
</Card>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import {request} from './index'
|
|||||||
import type {ConnectorElement} from '../../components/tracking/connector/ConnectorsList'
|
import type {ConnectorElement} from '../../components/tracking/connector/ConnectorsList'
|
||||||
|
|
||||||
export enum ConnectorProvider {
|
export enum ConnectorProvider {
|
||||||
OVH = 'ovh',
|
OVHcloud = 'ovh',
|
||||||
GANDI = 'gandi',
|
Gandi = 'gandi',
|
||||||
AUTODNS = 'autodns',
|
AutoDNS = 'autodns',
|
||||||
NAMECHEAP = 'namecheap'
|
Namecheap = 'namecheap',
|
||||||
|
'Name.com' = 'namecom'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Connector {
|
export interface Connector {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React from 'react'
|
|||||||
|
|
||||||
export const helpGetTokenLink = (provider?: string) => {
|
export const helpGetTokenLink = (provider?: string) => {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case ConnectorProvider.OVH:
|
case ConnectorProvider.OVHcloud:
|
||||||
return (
|
return (
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
target='_blank'
|
target='_blank'
|
||||||
@ -15,24 +15,30 @@ export const helpGetTokenLink = (provider?: string) => {
|
|||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
)
|
)
|
||||||
|
|
||||||
case ConnectorProvider.GANDI:
|
case ConnectorProvider.Gandi:
|
||||||
return (
|
return (
|
||||||
<Typography.Link target='_blank' href='https://admin.gandi.net/organizations/account/pat'>
|
<Typography.Link target='_blank' href='https://admin.gandi.net/organizations/account/pat'>
|
||||||
{t`Retrieve a Personal Access Token from your customer account on the Provider's website`}
|
{t`Retrieve a Personal Access Token from your customer account on the Provider's website`}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
)
|
)
|
||||||
case ConnectorProvider.NAMECHEAP:
|
case ConnectorProvider.Namecheap:
|
||||||
return (
|
return (
|
||||||
<Typography.Link target='_blank' href='https://ap.www.namecheap.com/settings/tools/apiaccess/'>
|
<Typography.Link target='_blank' href='https://ap.www.namecheap.com/settings/tools/apiaccess/'>
|
||||||
{t`Retreive an API key and whitelist this instance's IP address on Namecheap's website`}
|
{t`Retreive an API key and whitelist this instance's IP address on Namecheap's website`}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
)
|
)
|
||||||
case ConnectorProvider.AUTODNS:
|
case ConnectorProvider.AutoDNS:
|
||||||
return (
|
return (
|
||||||
<Typography.Link target='_blank' href='https://en.autodns.com/domain-robot-api/'>
|
<Typography.Link target='_blank' href='https://en.autodns.com/domain-robot-api/'>
|
||||||
{t`Because of some limitations in API of AutoDNS, we suggest to create an dedicated user for API with limited rights`}
|
{t`Because of some limitations in API of AutoDNS, we suggest to create an dedicated user for API with limited rights`}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
)
|
)
|
||||||
|
case ConnectorProvider['Name.com']:
|
||||||
|
return (
|
||||||
|
<Typography.Link target='_blank' href='https://www.name.com/account/settings/api'>
|
||||||
|
{t`Retrieve a set of tokens from your customer account on the Provider's website`}
|
||||||
|
</Typography.Link>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
@ -40,14 +46,16 @@ export const helpGetTokenLink = (provider?: string) => {
|
|||||||
|
|
||||||
export const tosHyperlink = (provider?: string) => {
|
export const tosHyperlink = (provider?: string) => {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case ConnectorProvider.OVH:
|
case ConnectorProvider.OVHcloud:
|
||||||
return 'https://www.ovhcloud.com/fr/terms-and-conditions/contracts/'
|
return 'https://www.ovhcloud.com/en/terms-and-conditions/contracts/'
|
||||||
case ConnectorProvider.GANDI:
|
case ConnectorProvider.Gandi:
|
||||||
return 'https://www.gandi.net/en/contracts/terms-of-service'
|
return 'https://www.gandi.net/en/contracts/terms-of-service'
|
||||||
case ConnectorProvider.NAMECHEAP:
|
case ConnectorProvider.Namecheap:
|
||||||
return 'https://www.namecheap.com/legal/universal/universal-tos/'
|
return 'https://www.namecheap.com/legal/universal/universal-tos/'
|
||||||
case ConnectorProvider.AUTODNS:
|
case ConnectorProvider.AutoDNS:
|
||||||
return 'https://www.internetx.com/agb/'
|
return 'https://www.internetx.com/agb/'
|
||||||
|
case ConnectorProvider['Name.com']:
|
||||||
|
return 'https://www.name.com/policies/'
|
||||||
default:
|
default:
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace App\Config;
|
|||||||
use App\Service\Connector\AutodnsProvider;
|
use App\Service\Connector\AutodnsProvider;
|
||||||
use App\Service\Connector\GandiProvider;
|
use App\Service\Connector\GandiProvider;
|
||||||
use App\Service\Connector\NamecheapProvider;
|
use App\Service\Connector\NamecheapProvider;
|
||||||
|
use App\Service\Connector\NameComProvider;
|
||||||
use App\Service\Connector\OvhProvider;
|
use App\Service\Connector\OvhProvider;
|
||||||
|
|
||||||
enum ConnectorProvider: string
|
enum ConnectorProvider: string
|
||||||
@ -13,6 +14,7 @@ enum ConnectorProvider: string
|
|||||||
case GANDI = 'gandi';
|
case GANDI = 'gandi';
|
||||||
case AUTODNS = 'autodns';
|
case AUTODNS = 'autodns';
|
||||||
case NAMECHEAP = 'namecheap';
|
case NAMECHEAP = 'namecheap';
|
||||||
|
case NAMECOM = 'namecom';
|
||||||
|
|
||||||
public function getConnectorProvider(): string
|
public function getConnectorProvider(): string
|
||||||
{
|
{
|
||||||
@ -21,6 +23,7 @@ enum ConnectorProvider: string
|
|||||||
ConnectorProvider::GANDI => GandiProvider::class,
|
ConnectorProvider::GANDI => GandiProvider::class,
|
||||||
ConnectorProvider::AUTODNS => AutodnsProvider::class,
|
ConnectorProvider::AUTODNS => AutodnsProvider::class,
|
||||||
ConnectorProvider::NAMECHEAP => NamecheapProvider::class,
|
ConnectorProvider::NAMECHEAP => NamecheapProvider::class,
|
||||||
|
ConnectorProvider::NAMECOM => NameComProvider::class,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,8 +131,6 @@ class Domain
|
|||||||
'pending transfer',
|
'pending transfer',
|
||||||
'pending update',
|
'pending update',
|
||||||
'add period',
|
'add period',
|
||||||
'client hold',
|
|
||||||
'server hold',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|||||||
127
src/Service/Connector/NameComProvider.php
Normal file
127
src/Service/Connector/NameComProvider.php
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\Connector;
|
||||||
|
|
||||||
|
use App\Entity\Domain;
|
||||||
|
use Psr\Cache\CacheItemInterface;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Psr\Cache\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||||
|
use Symfony\Component\HttpClient\HttpOptions;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
|
#[Autoconfigure(public: true)]
|
||||||
|
class NameComProvider extends AbstractProvider
|
||||||
|
{
|
||||||
|
public function __construct(CacheItemPoolInterface $cacheItemPool,
|
||||||
|
private readonly HttpClientInterface $client,
|
||||||
|
private readonly KernelInterface $kernel)
|
||||||
|
{
|
||||||
|
parent::__construct($cacheItemPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const BASE_URL = 'https://api.name.com';
|
||||||
|
private const DEV_BASE_URL = 'https://api.dev.name.com';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order a domain name with the Gandi API.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
* @throws DecodingExceptionInterface
|
||||||
|
*/
|
||||||
|
public function orderDomain(Domain $domain, bool $dryRun = false): void
|
||||||
|
{
|
||||||
|
$ldhName = $domain->getLdhName();
|
||||||
|
if (!$ldhName) {
|
||||||
|
throw new \InvalidArgumentException('Domain name cannot be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client->request(
|
||||||
|
'POST',
|
||||||
|
'/v4/domains',
|
||||||
|
(new HttpOptions())
|
||||||
|
->setHeader('Accept', 'application/json')
|
||||||
|
->setAuthBasic($this->authData['username'], $this->authData['token'])
|
||||||
|
->setBaseUri($dryRun ? self::DEV_BASE_URL : self::BASE_URL)
|
||||||
|
->setJson([
|
||||||
|
'domain' => [
|
||||||
|
[
|
||||||
|
'domainName' => $domain->getLdhName(),
|
||||||
|
'locked' => false,
|
||||||
|
'autorenewEnabled' => false,
|
||||||
|
],
|
||||||
|
'purchaseType' => 'registration',
|
||||||
|
'years' => 1,
|
||||||
|
// 'tldRequirements' => []
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->toArray()
|
||||||
|
)->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifySpecificAuthData(array $authData): array
|
||||||
|
{
|
||||||
|
$username = $authData['username'];
|
||||||
|
$token = $authData['token'];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!is_string($username) || empty($username)
|
||||||
|
|| !is_string($token) || empty($token)
|
||||||
|
) {
|
||||||
|
throw new BadRequestHttpException('Bad authData schema');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'username' => $authData['username'],
|
||||||
|
'token' => $authData['token'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSupported(Domain ...$domainList): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSupportedTldList(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getCachedTldList(): CacheItemInterface
|
||||||
|
{
|
||||||
|
return $this->cacheItemPool->getItem('app.provider.namecom.supported-tld');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
*/
|
||||||
|
protected function assertAuthentication(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = $this->client->request(
|
||||||
|
'GET',
|
||||||
|
'/v4/hello',
|
||||||
|
(new HttpOptions())
|
||||||
|
->setHeader('Accept', 'application/json')
|
||||||
|
->setAuthBasic($this->authData['username'], $this->authData['token'])
|
||||||
|
->setBaseUri($this->kernel->isDebug() ? self::DEV_BASE_URL : self::BASE_URL)
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
} catch (\Exception) {
|
||||||
|
throw new BadRequestHttpException('Invalid Login');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Response::HTTP_OK !== $response->getStatusCode()) {
|
||||||
|
throw new BadRequestHttpException('The status of these credentials is not valid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user