feat: retry deSEC domain registration without re-entering email when account already exists

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/f779f575-e0fb-4e25-8e5c-5d5cb7668a91

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-24 21:40:06 +00:00
committed by GitHub
parent 68df1dd857
commit 83129d6a55
4 changed files with 58 additions and 20 deletions

View File

@@ -183,6 +183,7 @@ $app->get('/containers', function (Request $request, Response $response, array $
'bypass_container_update' => $bypass_container_update,
'desec_email' => $configurationManager->desecEmail,
'is_desec_domain' => $configurationManager->isDesecDomain(),
'desec_account_registered' => $configurationManager->isDesecAccountRegistered(),
]);
})->setName('profile');
$app->get('/login', function (Request $request, Response $response, array $args) use ($container) {

View File

@@ -36,10 +36,19 @@ readonly class DesecController {
return $response->withStatus(422);
}
$email = trim((string)($request->getParsedBody()['desec_email'] ?? ''));
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$response->getBody()->write('Please provide a valid email address.');
return $response->withStatus(422);
// When a deSEC account was already registered (token exists) but domain creation previously
// failed, we skip account registration and re-use the stored token and email.
$accountAlreadyRegistered = $this->configurationManager->isDesecAccountRegistered();
if ($accountAlreadyRegistered) {
$token = $this->configurationManager->getDesecToken();
// email is already stored; no need to validate or update it
} else {
$email = trim((string)($request->getParsedBody()['desec_email'] ?? ''));
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$response->getBody()->write('Please provide a valid email address.');
return $response->withStatus(422);
}
}
$slug = trim((string)($request->getParsedBody()['desec_slug'] ?? ''));
@@ -52,17 +61,26 @@ readonly class DesecController {
}
try {
// Register an account at deSEC and obtain an API token
$password = bin2hex(random_bytes(24));
$token = $this->registerDesecAccount($email, $password);
if (!$accountAlreadyRegistered) {
// Register an account at deSEC and obtain an API token.
// The password is intentionally ephemeral: only the API token is needed for
// subsequent calls, so the password does not need to be stored.
$password = bin2hex(random_bytes(24));
$token = $this->registerDesecAccount($email, $password);
// Persist the token and email immediately so that a subsequent domain-registration
// failure leaves the account credentials stored and allows the user to retry.
$this->configurationManager->startTransaction();
$this->configurationManager->setDesecToken($token);
$this->configurationManager->desecEmail = $email;
$this->configurationManager->commitTransaction();
}
// Register a free dedyn.io subdomain
$domain = $this->registerDesecDomain($token, $slug);
// Persist the credentials and auto-enable caddy as the reverse proxy
// Auto-enable caddy and dnsmasq (idempotent — safe to call even on retry)
$this->configurationManager->startTransaction();
$this->configurationManager->setDesecToken($token);
$this->configurationManager->desecEmail = $email;
$enabled = array_values(array_filter(
$this->configurationManager->aioCommunityContainers,
fn(string $cc): bool => $cc !== '',

View File

@@ -228,6 +228,15 @@ class ConfigurationManager
return str_ends_with($this->domain, '.dedyn.io') && $this->getDesecToken() !== '';
}
/**
* Returns true when a deSEC account token is stored but no domain has been configured yet.
* This happens when account registration succeeded but domain registration subsequently failed.
* In this state the user can retry domain registration with a different slug.
*/
public function isDesecAccountRegistered(): bool {
return $this->getDesecToken() !== '' && $this->desecEmail !== '' && $this->domain === '';
}
public string $apachePort {
get => $this->getEnvironmentalVariableOrConfig('APACHE_PORT', 'apache_port', '443');
set { $this->set('apache_port', $value); }

View File

@@ -132,18 +132,28 @@
{% endif %}
<p><strong>Hint:</strong> If the domain validation fails but you are completely sure that you've configured everything correctly, you may skip the domain validation by following <a target="_blank" href="https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation">this documentation</a>.</p>
</details>
<details>
<details{% if desec_account_registered %} open{% endif %}>
<summary>Don't have a domain? Get a free one from deSEC</summary>
<p><a target="_blank" href="https://desec.io">deSEC</a> offers free dynamic DNS subdomains under <strong>dedyn.io</strong>. AIO can register an account and a subdomain for you automatically. The <strong>caddy</strong> community container will be enabled as a reverse proxy, the <strong>dnsmasq</strong> container will be enabled for local DNS resolution, and the mastercontainer will keep your DNS record up to date automatically.</p>
<p>Please enter your email address. You can also enter a desired subdomain slug (the part before <code>.dedyn.io</code>); leave it blank for a random one.</p>
<form method="POST" action="api/desec/register" class="xhr">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="email" name="desec_email" placeholder="your@email.com" required />
<input type="text" name="desec_slug" placeholder="my-nextcloud (optional)" pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Only lowercase letters, digits and hyphens (163 characters). No leading or trailing hyphen." />
<input type="submit" value="Register free domain via deSEC" />
</form>
<p><strong>Note:</strong> By submitting this form you agree to the <a target="_blank" href="https://desec.io/terms">deSEC terms of service</a>. The registered domain and your deSEC account credentials are stored in the AIO configuration. After registration, set your router's DHCP DNS server to this machine's local IP address so LAN devices resolve the domain locally (see the <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq">dnsmasq documentation</a>). Alternatively adjust the hosts files on your clients so that they can reach the server using the local ip-address.</p>
{% if desec_account_registered %}
<p>Your deSEC account (<strong>{{ desec_email }}</strong>) was registered successfully but the domain could not be registered. Please enter a desired subdomain slug (the part before <code>.dedyn.io</code>) and try again, or leave it blank for a random one.</p>
<form method="POST" action="api/desec/register" class="xhr">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="text" name="desec_slug" placeholder="my-nextcloud (optional)" pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Only lowercase letters, digits and hyphens (163 characters). No leading or trailing hyphen." />
<input type="submit" value="Register free domain via deSEC" />
</form>
{% else %}
<p>Please enter your email address. You can also enter a desired subdomain slug (the part before <code>.dedyn.io</code>); leave it blank for a random one.</p>
<form method="POST" action="api/desec/register" class="xhr">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="email" name="desec_email" placeholder="your@email.com" required />
<input type="text" name="desec_slug" placeholder="my-nextcloud (optional)" pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Only lowercase letters, digits and hyphens (163 characters). No leading or trailing hyphen." />
<input type="submit" value="Register free domain via deSEC" />
</form>
<p><strong>Note:</strong> By submitting this form you agree to the <a target="_blank" href="https://desec.io/terms">deSEC terms of service</a>. The registered domain and your deSEC account credentials are stored in the AIO configuration. After registration, set your router's DHCP DNS server to this machine's local IP address so LAN devices resolve the domain locally (see the <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq">dnsmasq documentation</a>). Alternatively adjust the hosts files on your clients so that they can reach the server using the local ip-address.</p>
{% endif %}
</details>
{% endif %}