diff --git a/php/src/Controller/DesecController.php b/php/src/Controller/DesecController.php index 9855911b..6b9246b4 100644 --- a/php/src/Controller/DesecController.php +++ b/php/src/Controller/DesecController.php @@ -3,86 +3,25 @@ declare(strict_types=1); namespace AIO\Controller; -use AIO\Data\ConfigurationManager; use AIO\Desec\DesecManager; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; readonly class DesecController { - private const string SLUG_PATTERN = '/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/'; - public function __construct( - private ConfigurationManager $configurationManager, private DesecManager $desecManager, ) { } public function Register(Request $request, Response $response, array $args): Response { try { - $this->validateNoDomainAlreadyConfigured(); - - $accountAlreadyRegistered = $this->configurationManager->isDesecAccountRegistered(); - $token = $accountAlreadyRegistered - ? $this->configurationManager->desecToken - : null; - - $email = $accountAlreadyRegistered ? null : $this->getEmailFromRequest($request); - $slug = $this->getSlugFromRequest($request); - - if (!$accountAlreadyRegistered) { - // 24 random bytes → 48-char hex password; satisfies deSEC's minimum length - // and lets the user log in at desec.io if they ever need to. - $password = bin2hex(random_bytes(24)); - $token = $this->desecManager->registerAccount($email, $password); - $this->desecManager->saveAccountCredentials($token, $password, $email); - } - - $domain = $this->desecManager->registerDomain($token, $slug); - $this->desecManager->enableDesecContainers(); - $this->configurationManager->setDomain($domain, true); - $this->desecManager->updateIpIfDesecDomain(); - + $email = (string)($request->getParsedBody()['desec_email'] ?? ''); + $slug = (string)($request->getParsedBody()['desec_slug'] ?? ''); + $this->desecManager->register($email, $slug); return $response->withStatus(201)->withHeader('Location', '.'); } catch (\Exception $ex) { $response->getBody()->write($ex->getMessage()); return $response->withStatus(422); } } - - /** @throws \Exception if a domain is already configured */ - private function validateNoDomainAlreadyConfigured(): void { - if ($this->configurationManager->domain !== '') { - throw new \Exception('A domain is already configured. Reset the AIO instance first to register a new domain.'); - } - } - - /** - * Reads and validates the email address from the request body. - * - * @throws \Exception if the email is missing or syntactically invalid - */ - private function getEmailFromRequest(Request $request): string { - $email = trim((string)($request->getParsedBody()['desec_email'] ?? '')); - if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) { - throw new \Exception('Please provide a valid email address.'); - } - return $email; - } - - /** - * Reads and validates the optional subdomain slug from the request body. - * Returns an empty string when the user wants a randomly generated slug. - * - * @throws \Exception if the slug is present but does not match the allowed pattern - */ - private function getSlugFromRequest(Request $request): string { - $slug = trim((string)($request->getParsedBody()['desec_slug'] ?? '')); - if ($slug !== '' && !preg_match(self::SLUG_PATTERN, $slug)) { - throw new \Exception( - 'The desired subdomain must contain only lowercase letters, digits and hyphens, ' - . 'be between 1 and 63 characters long, and must not start or end with a hyphen.' - ); - } - return $slug; - } } diff --git a/php/src/Desec/DesecManager.php b/php/src/Desec/DesecManager.php index 4d3d3764..29b0d43e 100644 --- a/php/src/Desec/DesecManager.php +++ b/php/src/Desec/DesecManager.php @@ -11,6 +11,7 @@ class DesecManager { private const string DESEC_API_BASE = 'https://desec.io/api/v1'; private const int MAX_SLUG_ATTEMPTS = 5; private const int SLUG_BYTES = 5; // bin2hex → 10-char slug + private const string SLUG_PATTERN = '/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/'; private Client $guzzleClient; @@ -24,6 +25,73 @@ class DesecManager { ]); } + /** + * Full registration flow: validates inputs, creates an account if needed, + * registers the domain, enables required containers, and updates the DNS record. + * + * @throws \Exception on any validation or API error + */ + public function register(string $email, string $slug): void { + if ($this->configurationManager->domain !== '') { + throw new \Exception('A domain is already configured. Reset the AIO instance first to register a new domain.'); + } + + $accountAlreadyRegistered = $this->configurationManager->isDesecAccountRegistered(); + $token = $accountAlreadyRegistered + ? $this->configurationManager->desecToken + : null; + + $validatedEmail = null; + if (!$accountAlreadyRegistered) { + $validatedEmail = $this->validateEmail($email); + } + + $validatedSlug = $this->validateSlug($slug); + + if (!$accountAlreadyRegistered) { + // 24 random bytes → 48-char hex password; satisfies deSEC's minimum length + // and lets the user log in at desec.io if they ever need to. + $password = bin2hex(random_bytes(24)); + $token = $this->registerAccount($validatedEmail, $password); + $this->saveAccountCredentials($token, $password, $validatedEmail); + } + + $domain = $this->registerDomain($token, $validatedSlug); + $this->enableDesecContainers(); + $this->configurationManager->setDomain($domain, true); + $this->updateIpIfDesecDomain(); + } + + /** + * Validates an email address string. + * + * @throws \Exception if the email is empty or syntactically invalid + */ + private function validateEmail(string $email): string { + $email = trim($email); + if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) { + throw new \Exception('Please provide a valid email address.'); + } + return $email; + } + + /** + * Validates an optional subdomain slug. + * Returns an empty string when the caller wants a randomly generated slug. + * + * @throws \Exception if the slug is non-empty but does not match the allowed pattern + */ + private function validateSlug(string $slug): string { + $slug = trim($slug); + if ($slug !== '' && !preg_match(self::SLUG_PATTERN, $slug)) { + throw new \Exception( + 'The desired subdomain must contain only lowercase letters, digits and hyphens, ' + . 'be between 1 and 63 characters long, and must not start or end with a hyphen.' + ); + } + return $slug; + } + /** * Creates a new deSEC account and returns the API token issued for it. *