From 454905c8525c14fa33ff4d3d25504ef3199ea39f Mon Sep 17 00:00:00 2001 From: "Simon L." Date: Mon, 22 Jun 2026 13:22:37 +0200 Subject: [PATCH] feat(desec): run registration flow in a modal iframe Move the deSEC "register a free domain" flow out of the inline containers page form and into a modal backed by a dedicated /desec view loaded in an iframe. The multi-step register -> verify -> domain process now re-renders inside the modal, so the user can adjust the details and complete email verification without reloading the whole page each step. Only once the domain is fully registered does the view reload the parent containers page. - add /desec route + desec.twig standalone view - add desec-modal.js (open/close, backdrop + Escape) and desec-done.js (parent reload on completion) - redirect register POST to the /desec view so steps stay in the modal - drop the redundant
wrapper in desec-register.twig, add heading - style the modal and let
- {% include 'includes/desec-register.twig' %} +
+ Don't have a domain? Get a free one from deSEC +

deSEC offers free dynamic DNS subdomains under dedyn.io. AIO can register an account and a subdomain for you automatically. Click the button below to start; the registration runs in a small window so you can adjust the details and complete the email verification without losing this page. Once it's done, this page reloads with your new domain configured.

+ {% if desec_awaiting_verification or desec_account_registered %} +

⚠️ A deSEC registration for {{ desec_email }} is already in progress but not finished yet. Click the button below to continue it.

+ + {% else %} + + {% endif %} +
+ + {% endif %}

Restore former AIO instance from backup

diff --git a/php/templates/desec.twig b/php/templates/desec.twig new file mode 100644 index 00000000..70c96259 --- /dev/null +++ b/php/templates/desec.twig @@ -0,0 +1,30 @@ + + + + Register a free deSEC domain + + + + + + + + + {% if is_desec_domain %} + {# The whole process finished: a deSEC domain is now configured. Tell the parent + window (the modal opener) to reload so the full containers page reflects the + new domain, then close the modal. When this view is opened directly (not in an + iframe) window.top === window, so we just reload ourselves. #} +

Your free domain {{ domain }} was registered successfully. Finishing up…

+ + {% else %} +
+ {% include 'includes/desec-register.twig' %} +
+ {% endif %} +
+
+ +
+ + diff --git a/php/templates/includes/desec-register.twig b/php/templates/includes/desec-register.twig index 041faa04..c52d7493 100644 --- a/php/templates/includes/desec-register.twig +++ b/php/templates/includes/desec-register.twig @@ -1,37 +1,35 @@ - - Don't have a domain? Get a free one from deSEC -

deSEC offers free dynamic DNS subdomains under dedyn.io. AIO can register an account and a subdomain for you automatically. The caddy community container will be enabled as a reverse proxy, the dnsmasq container will be enabled for local DNS resolution, and the mastercontainer will keep your DNS record up to date automatically.

- {% if desec_awaiting_verification %} -

⚠️ A deSEC account for {{ desec_email }} was requested but it could not be used yet. Please check your inbox: if deSEC sent you a verification email, click the link inside, then click the button below to finish setting up your domain.

-

If this email address already had a deSEC account, no new account was created and no verification email was sent. In that case, enter your existing deSEC password below and try again to log in with it.

-

Your deSEC login credentials (for desec.io): Email: {{ desec_email }}.

Reveal deSEC password{{ desec_password }}
. Please save these in a safe place.

-
- - - - - -
- {% elseif desec_account_registered %} -

Your deSEC account ({{ desec_email }}) was registered successfully but the domain could not be registered. Please enter a desired subdomain slug (the part before .dedyn.io) and try again, or leave it blank for a random one.

-

Your deSEC login credentials (for desec.io): Email: {{ desec_email }}.

Reveal deSEC password{{ desec_password }}
. Please save these in a safe place.

-
- - - - -
- {% else %} -

Please enter your email address. You can also enter a desired subdomain slug (the part before .dedyn.io); leave it blank for a random one.

-

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. When creating a new account, deSEC will email you a verification link that you must click before the domain can be set up.

-
- - - - - - -
-

Note: By submitting this form you agree to the deSEC terms of service. 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 dnsmasq documentation). Alternatively adjust the hosts files on your clients so that they can reach the server using the local ip-address.

- {% endif %} - +

Get a free domain from deSEC

+

deSEC offers free dynamic DNS subdomains under dedyn.io. AIO can register an account and a subdomain for you automatically. The caddy community container will be enabled as a reverse proxy, the dnsmasq container will be enabled for local DNS resolution, and the mastercontainer will keep your DNS record up to date automatically.

+{% if desec_awaiting_verification %} +

⚠️ A deSEC account for {{ desec_email }} was requested but it could not be used yet. Please check your inbox: if deSEC sent you a verification email, click the link inside, then click the button below to finish setting up your domain.

+

If this email address already had a deSEC account, no new account was created and no verification email was sent. In that case, enter your existing deSEC password below and try again to log in with it.

+

Your deSEC login credentials (for desec.io): Email: {{ desec_email }}.

Reveal deSEC password{{ desec_password }}
. Please save these in a safe place.

+
+ + + + + +
+{% elseif desec_account_registered %} +

Your deSEC account ({{ desec_email }}) was registered successfully but the domain could not be registered. Please enter a desired subdomain slug (the part before .dedyn.io) and try again, or leave it blank for a random one.

+

Your deSEC login credentials (for desec.io): Email: {{ desec_email }}.

Reveal deSEC password{{ desec_password }}
. Please save these in a safe place.

+
+ + + + +
+{% else %} +

Please enter your email address. You can also enter a desired subdomain slug (the part before .dedyn.io); leave it blank for a random one.

+

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. When creating a new account, deSEC will email you a verification link that you must click before the domain can be set up.

+
+ + + + + + +
+

Note: By submitting this form you agree to the deSEC terms of service. 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 dnsmasq documentation). Alternatively adjust the hosts files on your clients so that they can reach the server using the local ip-address.

+{% endif %} diff --git a/php/tests/tests/desec-existing.spec.js b/php/tests/tests/desec-existing.spec.js index 22a05075..7c5af394 100644 --- a/php/tests/tests/desec-existing.spec.js +++ b/php/tests/tests/desec-existing.spec.js @@ -5,6 +5,9 @@ import { DESEC_MOCK_URL, logInToContainersPage } from './desec-helpers.js'; // password logs straight in and registers the domain in one step (no email-verification // round-trip). See desec-register.spec.js for the full setup notes. // +// As in the register flow, the form is driven inside the modal iframe (the /desec view); +// on success the iframe reloads the parent containers page. +// // This flow also ends by registering a domain, so it is run as its own CI step with the // deSEC state re-seeded beforehand (configuration.json must have no domain set for the // registration UI to render). @@ -28,13 +31,18 @@ test('deSEC existing-account login flow', async ({ page: setupPage }) => { const slug = `aio-existing-${Math.floor(Math.random() * 2147483647)}`; + // Open the deSEC registration entry point and launch the modal. await containersPage.getByText("Don't have a domain? Get a free one from deSEC").click(); - await containersPage.locator('input[name="desec_email"]').fill(email); - await containersPage.locator('input[name="desec_password"]').fill(password); - await containersPage.locator('input[name="desec_slug"]').first().fill(slug); await containersPage.getByRole('button', { name: 'Register free domain via deSEC' }).click(); - // Supplying a valid password logs straight in and registers the domain in one step. + const frame = containersPage.frameLocator('#desec-frame'); + await frame.locator('input[name="desec_email"]').fill(email); + await frame.locator('input[name="desec_password"]').fill(password); + await frame.locator('input[name="desec_slug"]').first().fill(slug); + await frame.getByRole('button', { name: 'Register free domain via deSEC' }).click(); + + // Supplying a valid password logs straight in and registers the domain in one step; the + // modal view then reloads the parent containers page. await expect(containersPage.getByRole('button', { name: 'Download and start containers' })).toBeVisible({ timeout: 60 * 1000 }); await expect( containersPage.getByText("Don't have a domain? Get a free one from deSEC"), diff --git a/php/tests/tests/desec-register.spec.js b/php/tests/tests/desec-register.spec.js index a9d760a7..8c4816b0 100644 --- a/php/tests/tests/desec-register.spec.js +++ b/php/tests/tests/desec-register.spec.js @@ -3,11 +3,16 @@ import { DESEC_MOCK_URL, logInToContainersPage } from './desec-helpers.js'; // Drives the real AIO interface through the full deSEC "register a free domain" flow // against the local mock (php/tests/desec-mock.mjs). The mastercontainer must be started -// with SKIP_DOMAIN_VALIDATION=false (so the deSEC registration form is rendered) and its -// configuration.json must point desec_api_base / desec_update_url at the mock (see the +// with SKIP_DOMAIN_VALIDATION=false (so the deSEC registration entry point is rendered) and +// its configuration.json must point desec_api_base / desec_update_url at the mock (see the // Playwright CI workflow). The mock's control endpoint is reachable from the test runner // on the host at DESEC_MOCK_URL (default http://localhost:8090). // +// The registration runs inside a modal iframe (the /desec view): the multi-step +// register -> verify -> domain flow re-renders inside the iframe so the user can adjust the +// details and complete email verification without reloading the whole containers page. Only +// once the domain is fully registered does the iframe reload the parent page. +// // This flow ends by registering a domain, which persists in configuration.json. It is // therefore run as its own CI step; the deSEC state is reset (re-seeded) before the // separate existing-account flow runs. @@ -23,33 +28,37 @@ test('deSEC register -> verify -> domain flow', async ({ page: setupPage }) => { const email = `e2e-${Math.floor(Math.random() * 2147483647)}@example.com`; const slug = `aio-e2e-${Math.floor(Math.random() * 2147483647)}`; - // Open the deSEC registration section. + // Open the deSEC registration entry point and launch the modal. await containersPage.getByText("Don't have a domain? Get a free one from deSEC").click(); + await containersPage.getByRole('button', { name: 'Register free domain via deSEC' }).click(); + + // The flow lives inside the modal iframe (the /desec view). + const frame = containersPage.frameLocator('#desec-frame'); // 1) Submit email only -> a new account is "created" (mock 202) and AIO asks the user - // to verify their email. This is a normal (non-error) state transition: the page - // reloads into the awaiting-verification UI, which renders inside
and shows - // the dedicated re-submit button. - await containersPage.locator('input[name="desec_email"]').fill(email); - await containersPage.locator('input[name="desec_slug"]').first().fill(slug); - await containersPage.getByRole('button', { name: 'Register free domain via deSEC' }).click(); - await expect(containersPage.getByRole('main')).toContainText('check your inbox', { timeout: 30 * 1000 }); + // to verify their email. This is a normal (non-error) state transition: the iframe + // reloads into the awaiting-verification step inside the modal. + await frame.locator('input[name="desec_email"]').fill(email); + await frame.locator('input[name="desec_slug"]').first().fill(slug); + await frame.getByRole('button', { name: 'Register free domain via deSEC' }).click(); + await expect(frame.getByText('check your inbox')).toBeVisible({ timeout: 30 * 1000 }); await expect( - containersPage.getByRole('button', { name: 'I have verified my email – register domain' }), + frame.getByRole('button', { name: 'I have verified my email – register domain' }), ).toBeVisible(); // 2) Re-submit BEFORE verifying -> login still fails (mock 403) -> friendly hint shown. - await containersPage.getByRole('button', { name: 'I have verified my email – register domain' }).click(); - // Same as above: the failure hint is a transient toast, not
content. - await expect(containersPage.locator('.toast.error')).toContainText('Could not log in to deSEC', { timeout: 8 * 1000 }); + // The error is a transient toast rendered inside the iframe. + await frame.getByRole('button', { name: 'I have verified my email – register domain' }).click(); + await expect(frame.locator('.toast.error')).toContainText('Could not log in to deSEC', { timeout: 8 * 1000 }); // 3) Simulate the user clicking the verification link in their email. const verifyResponse = await fetch(`${DESEC_MOCK_URL}/__control/verify`, { method: 'POST' }); expect(verifyResponse.status).toBe(200); - // 4) Re-submit after verification -> login succeeds, domain is registered, the deSEC - // section disappears and the container-start UI appears. - await containersPage.getByRole('button', { name: 'I have verified my email – register domain' }).click(); + // 4) Re-submit after verification -> login succeeds, the domain is registered and the + // modal view reloads the parent page, where the deSEC entry point is gone and the + // container-start UI appears. + await frame.getByRole('button', { name: 'I have verified my email – register domain' }).click(); await expect(containersPage.getByRole('button', { name: 'Download and start containers' })).toBeVisible({ timeout: 60 * 1000 }); await expect( containersPage.getByText("Don't have a domain? Get a free one from deSEC"), diff --git a/tests/QA/002-new-instance.md b/tests/QA/002-new-instance.md index f8210895..57839732 100644 --- a/tests/QA/002-new-instance.md +++ b/tests/QA/002-new-instance.md @@ -10,10 +10,13 @@ For the below to work, it is important that you have a domain that you point ont - [ ] Entering `nextcloud.com` should report that the domain does not point to this server - [ ] Entering the domain that does point to your server e.g. `yourdomain.com` should finally redirect you to the next screen (if you did not configure your domain yet or did not open port 443, it should report that to you) - [ ] Below the domain input box there should be a collapsed `
` element labelled **"Don't have a domain? Get a free one from deSEC"** - - [ ] Expanding it should show a form with fields for email address and an optional subdomain slug, plus a **Register free domain via deSEC** submit button - - [ ] Submitting with an empty email should show an error asking for a valid email address - - [ ] Submitting with an invalid slug (e.g. `-bad-`) should show a validation error about the allowed slug format - - [ ] Submitting with a valid email and no slug should register a random `*.dedyn.io` domain, set it as the Nextcloud domain and redirect to the next screen + - [ ] Expanding it should show a short explanation and a **Register free domain via deSEC** button + - [ ] Clicking the button should open a modal window with the registration form (email address and an optional subdomain slug) without reloading the page + - [ ] Submitting with an empty email should show an error asking for a valid email address (inside the modal) + - [ ] Submitting with an invalid slug (e.g. `-bad-`) should show a validation error about the allowed slug format (inside the modal) + - [ ] Submitting with a valid email should step the modal through the register → verify email → register domain flow without closing it; you can adjust the details and re-submit between steps + - [ ] Closing the modal (via the × button, clicking the backdrop, or pressing Escape) and re-opening it should resume the registration where you left off + - [ ] Once the domain is fully registered the modal should close on its own and the whole page should reload with the new `*.dedyn.io` domain set as the Nextcloud domain - [ ] After successful registration, the `caddy` and `dnsmasq` community containers should be listed as enabled in the Community Containers section - [ ] After successful registration, there should be a **deSEC account credentials** section in the AIO interface that shows the registered email and allows revealing the password - [ ] Now you should see a button `Start containers` and an explanation which points out that clicking on the button will start the containers and that this can take a long time.