mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-06-10 08:37:02 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b342b0b8d | |||
| d58a34b605 | |||
| 44b257a2b5 | |||
| 2b78dcc9cc | |||
| 11d8050085 | |||
| 1c6ca098d5 | |||
| 5343353bb5 | |||
| f0fb065dc2 | |||
| b71afd933b | |||
| 24f8a126cb |
@@ -15,9 +15,10 @@ readonly class DesecController {
|
||||
|
||||
public function Register(Request $request, Response $response, array $args): Response {
|
||||
try {
|
||||
$email = (string)($request->getParsedBody()['desec_email'] ?? '');
|
||||
$slug = (string)($request->getParsedBody()['desec_slug'] ?? '');
|
||||
$this->desecManager->register($email, $slug);
|
||||
$email = (string)($request->getParsedBody()['desec_email'] ?? '');
|
||||
$slug = (string)($request->getParsedBody()['desec_slug'] ?? '');
|
||||
$password = (string)($request->getParsedBody()['desec_password'] ?? '');
|
||||
$this->desecManager->register($email, $slug, $password);
|
||||
return $response->withStatus(201)->withHeader('Location', '.');
|
||||
} catch (\Exception $ex) {
|
||||
$response->getBody()->write($ex->getMessage());
|
||||
|
||||
@@ -29,9 +29,14 @@ class DesecManager {
|
||||
* Full registration flow: validates inputs, creates an account if needed,
|
||||
* registers the domain, enables required containers, and updates the DNS record.
|
||||
*
|
||||
* When $password is non-empty the user is logging into an existing deSEC account
|
||||
* rather than creating a new one. When $password is empty a new account is created
|
||||
* with a randomly generated password (unless an account was already registered in a
|
||||
* previous attempt).
|
||||
*
|
||||
* @throws \Exception on any validation or API error
|
||||
*/
|
||||
public function register(string $email, string $slug): void {
|
||||
public function register(string $email, string $slug, string $password = ''): void {
|
||||
if ($this->configurationManager->domain !== '') {
|
||||
throw new \Exception('A domain is already configured. Reset the AIO instance first to register a new domain.');
|
||||
}
|
||||
@@ -41,22 +46,34 @@ class DesecManager {
|
||||
? $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);
|
||||
$validatedEmail = $this->validateEmail($email);
|
||||
$validatedPassword = trim($password);
|
||||
|
||||
if ($validatedPassword !== '') {
|
||||
// The user supplied their existing deSEC password — log in instead of registering.
|
||||
// Store an empty password: the token is all we need; the user's password must not be persisted.
|
||||
$token = $this->loginAccount($validatedEmail, $validatedPassword);
|
||||
$this->saveAccountCredentials($token, '', $validatedEmail);
|
||||
} else {
|
||||
// 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.
|
||||
$generatedPassword = bin2hex(random_bytes(24));
|
||||
$token = $this->registerAccount($validatedEmail, $generatedPassword);
|
||||
$this->saveAccountCredentials($token, $generatedPassword, $validatedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
$isNewAccount = !$accountAlreadyRegistered && trim($password) === '';
|
||||
|
||||
$domain = $this->registerDomain($token, $validatedSlug);
|
||||
|
||||
if ($isNewAccount) {
|
||||
$this->createWildcardCname($token, $domain);
|
||||
}
|
||||
|
||||
$this->enableDesecContainers();
|
||||
$this->configurationManager->setDomain($domain, true);
|
||||
$this->updateIpIfDesecDomain();
|
||||
@@ -114,7 +131,7 @@ class DesecManager {
|
||||
if (is_array($data) && isset($data['email'])) {
|
||||
throw new \Exception(
|
||||
'This email address is already registered at deSEC. '
|
||||
. 'Please log in at https://desec.io to retrieve your token and set up your domain manually.',
|
||||
. 'If this is your account, please enter your deSEC password in the password field and try again.',
|
||||
);
|
||||
}
|
||||
throw new \Exception('Registration at deSEC failed (HTTP 400): ' . $body);
|
||||
@@ -132,6 +149,39 @@ class DesecManager {
|
||||
return $data['token']['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates with an existing deSEC account and returns the API token issued for it.
|
||||
*
|
||||
* @throws \Exception on invalid credentials, network failure, or an unexpected HTTP response
|
||||
*/
|
||||
public function loginAccount(string $email, string $password): string {
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/auth/login/', [
|
||||
'json' => ['email' => $email, 'password' => $password],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
$body = $res->getBody()->getContents();
|
||||
|
||||
if ($code === 400 || $code === 403) {
|
||||
throw new \Exception('Could not log in to deSEC: invalid email address or password.');
|
||||
}
|
||||
|
||||
if ($code !== 200 && $code !== 201) {
|
||||
throw new \Exception('Unexpected response from deSEC during login (HTTP ' . $code . '): ' . $body);
|
||||
}
|
||||
|
||||
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($data) || !isset($data['token']) || !is_string($data['token'])) {
|
||||
throw new \Exception('Could not extract the API token from the deSEC login response. Please try again.');
|
||||
}
|
||||
|
||||
return $data['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a dedyn.io domain for the authenticated account.
|
||||
* When $slug is empty a random 10-character slug is tried up to MAX_SLUG_ATTEMPTS times.
|
||||
@@ -174,6 +224,32 @@ class DesecManager {
|
||||
throw new \Exception('Could not register a free dedyn.io domain after ' . self::MAX_SLUG_ATTEMPTS . ' attempts. Please try again.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wildcard CNAME rrset (*.domain → domain.) for a newly registered domain.
|
||||
* Errors are logged but do not abort the overall registration.
|
||||
*/
|
||||
private function createWildcardCname(string $token, string $domain): void {
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/domains/' . $domain . '/rrsets/', [
|
||||
'headers' => ['Authorization' => 'Token ' . $token],
|
||||
'json' => [
|
||||
'subname' => '*',
|
||||
'type' => 'CNAME',
|
||||
'ttl' => 3600,
|
||||
'records' => [$domain . '.'],
|
||||
],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
error_log('Could not create wildcard CNAME for ' . $domain . ': ' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
if ($code !== 201) {
|
||||
error_log('Unexpected response when creating wildcard CNAME for ' . $domain . ' (HTTP ' . $code . '): ' . $res->getBody()->getContents());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists deSEC account credentials to the AIO configuration atomically.
|
||||
*/
|
||||
|
||||
@@ -132,30 +132,7 @@
|
||||
{% 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{% 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>
|
||||
{% 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>
|
||||
<p>Your deSEC login credentials (for <a target="_blank" href="https://desec.io">desec.io</a>): Email: <strong>{{ desec_email }}</strong>. <details style="display:inline"><summary>Reveal deSEC password</summary><strong>{{ desec_password }}</strong></details>. Please save these in a safe place.</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 (1–63 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 (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>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% include 'includes/desec-register.twig' %}
|
||||
{% endif %}
|
||||
|
||||
<h2>Restore former AIO instance from backup</h2>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<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>
|
||||
{% 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>
|
||||
<p>Your deSEC login credentials (for <a target="_blank" href="https://desec.io">desec.io</a>): Email: <strong>{{ desec_email }}</strong>. <details style="display:inline"><summary>Reveal deSEC password</summary><strong>{{ desec_password }}</strong></details>. Please save these in a safe place.</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 (1–63 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>
|
||||
<p>If you already have a deSEC account for this email address, enter your deSEC password in the optional password field below to log in with it instead of creating a new account.</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="password" name="desec_password" placeholder="deSEC password (only if already registered)" autocomplete="current-password" />
|
||||
<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>
|
||||
{% endif %}
|
||||
</details>
|
||||
Reference in New Issue
Block a user