mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-21 02:40:09 +00:00
feat: allow custom subdomain slug for deSEC registration, fix slug validation regex
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/60fbe9fa-94d1-4fea-9f6e-d1fb56861388 Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a12f505dcd
commit
7598f6534d
@@ -6,6 +6,8 @@ if [ -z "$NC_DOMAIN" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOCAL_IP=""
|
||||
|
||||
# Determine the server's primary LAN IP - use the source address chosen by the kernel
|
||||
# for a route to a well-known public IP (1.1.1.1 is used purely to query the routing table;
|
||||
# no traffic is sent there).
|
||||
|
||||
@@ -15,6 +15,7 @@ readonly class DesecController {
|
||||
private const string DEDYN_SUFFIX = '.dedyn.io';
|
||||
private const int MAX_SLUG_ATTEMPTS = 5;
|
||||
private const int SLUG_BYTES = 5; // 10-char hex slug
|
||||
private const string SLUG_PATTERN = '/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/';
|
||||
|
||||
private Client $guzzleClient;
|
||||
|
||||
@@ -41,13 +42,22 @@ readonly class DesecController {
|
||||
return $response->withStatus(422);
|
||||
}
|
||||
|
||||
$slug = trim((string)($request->getParsedBody()['desec_slug'] ?? ''));
|
||||
if ($slug !== '' && !preg_match(self::SLUG_PATTERN, $slug)) {
|
||||
$response->getBody()->write(
|
||||
'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 $response->withStatus(422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Register an account at deSEC and obtain an API token
|
||||
$password = bin2hex(random_bytes(24));
|
||||
$token = $this->registerDesecAccount($email, $password);
|
||||
|
||||
// Register a free dedyn.io subdomain
|
||||
$domain = $this->registerDesecDomain($token);
|
||||
$domain = $this->registerDesecDomain($token, $slug);
|
||||
|
||||
// Persist the credentials and auto-enable caddy as the reverse proxy
|
||||
$this->configurationManager->startTransaction();
|
||||
@@ -160,11 +170,47 @@ readonly class DesecController {
|
||||
|
||||
/**
|
||||
* Registers a new dedyn.io subdomain and returns its full name.
|
||||
* Retries with a different random slug on name conflicts.
|
||||
*
|
||||
* When $requestedSlug is non-empty the caller's preferred slug is tried once; a 409
|
||||
* conflict returns an actionable error immediately (no silent retry).
|
||||
* When $requestedSlug is empty a random 10-char hex slug is generated and retried up
|
||||
* to MAX_SLUG_ATTEMPTS times on 409 conflicts.
|
||||
*
|
||||
* @throws \Exception when all attempts fail or a network/API error occurs
|
||||
*/
|
||||
private function registerDesecDomain(string $token): string {
|
||||
private function registerDesecDomain(string $token, string $requestedSlug = ''): string {
|
||||
if ($requestedSlug !== '') {
|
||||
// User chose a specific slug — try it exactly once.
|
||||
$domain = $requestedSlug . self::DEDYN_SUFFIX;
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/domains/', [
|
||||
'headers' => ['Authorization' => 'Token ' . $token],
|
||||
'json' => ['name' => $domain],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$httpCode = $res->getStatusCode();
|
||||
|
||||
if ($httpCode === 201) {
|
||||
return $domain;
|
||||
}
|
||||
|
||||
if ($httpCode === 409) {
|
||||
throw new \Exception(
|
||||
'"' . $domain . '" is already taken. Please choose a different subdomain and try again.',
|
||||
);
|
||||
}
|
||||
|
||||
$body = $res->getBody()->getContents();
|
||||
throw new \Exception(
|
||||
'Unexpected response from deSEC during domain registration '
|
||||
. '(HTTP ' . $httpCode . '): ' . $body,
|
||||
);
|
||||
}
|
||||
|
||||
// No slug provided — generate random slugs and retry on conflicts.
|
||||
$lastError = '';
|
||||
|
||||
for ($attempt = 0; $attempt < self::MAX_SLUG_ATTEMPTS; $attempt++) {
|
||||
|
||||
@@ -135,11 +135,12 @@
|
||||
<details>
|
||||
<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. A deSEC account and a random <em>subdomain.dedyn.io</em> domain will be created for you.</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 (1–63 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>
|
||||
|
||||
Reference in New Issue
Block a user