From 8fe352be69be87c0436152f352db07ebaac313e5 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:42:00 -0400 Subject: [PATCH] Offer to add the current address to Allowed Addresses --- e2e/allowed-hosts.spec.js | 42 +++++++++++++++++++++++++++++++++++++++ ui/admin/index.html | 4 +++- ui/i18n/de.json | 1 + ui/i18n/en.json | 1 + ui/i18n/es.json | 1 + ui/i18n/fa.json | 1 + ui/i18n/fr.json | 1 + ui/i18n/zh-Hans.json | 1 + ui/js/admin-settings.js | 28 ++++++++++++++++++++++++++ ui/js/admin.js | 2 +- 10 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 e2e/allowed-hosts.spec.js diff --git a/e2e/allowed-hosts.spec.js b/e2e/allowed-hosts.spec.js new file mode 100644 index 00000000..a5583cbd --- /dev/null +++ b/e2e/allowed-hosts.spec.js @@ -0,0 +1,42 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); +const { seedConfig } = require('./helpers'); +const { BASE_URL } = require('./base-url'); + +const PROTECTED = { enabled: true, authenticated: true, passwordSet: true }; + +test.beforeEach(async ({ request }) => { + await seedConfig(request, { items: [] }); +}); + +async function openGeneral(page, origin) { + await page.goto(`${origin}/admin/`); + await page.locator('body.authed').waitFor({ state: 'attached' }); + await page.locator('.nl[data-sec="general"]').click(); +} + +test('signed in on an unlisted name, Settings offers to list it', async ({ page }) => { + const name = 'dash.example.test'; + await page.route(`http://${name}/**`, async route => { + const url = new URL(route.request().url()); + if (url.pathname === '/api/auth/check') return route.fulfill({ json: PROTECTED }); + const response = await route.fetch({ url: `${BASE_URL}${url.pathname}${url.search}` }); + return route.fulfill({ response }); + }); + await openGeneral(page, `http://${name}`); + + await expect(page.locator('#hosts-add-name')).toHaveText(name); + await expect(page.locator('#hosts-add-tip')).toBeVisible(); + await page.locator('#hosts-add').click(); + await expect(page.locator('#ie-hosts-v')).toHaveText(name); + await expect(page.locator('#hosts-add-wrap')).not.toHaveClass(/\bopen\b/); + await expect(page.locator('#ie-hosts .pe')).toBeFocused(); + await expect(page.locator('#srv-save')).toBeEnabled(); +}); + +test('on a local address Settings offers nothing', async ({ page }) => { + await page.route('**/api/auth/check', route => route.fulfill({ json: PROTECTED })); + await openGeneral(page, BASE_URL); + await expect(page.locator('#sec-en')).toBeChecked(); + await expect(page.locator('#hosts-add-wrap')).not.toHaveClass(/\bopen\b/); +}); diff --git a/ui/admin/index.html b/ui/admin/index.html index 5401639e..998dbaa3 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -106,7 +106,9 @@
Leave blank to keep existing password. Min 8 characters. For stronger protection, consider placing the dashboard behind a dedicated authentication proxy. This built-in password is a lightweight gate, not a full authentication layer.
This page is open on an address that isn’t listed. Add it so a page on another site can’t use up your sign-in attempts.
While no password is set, Stackyard answers only on IP addresses, local names such as nas or nas.local, and the addresses listed here. Separate them with commas.