Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions e2e/allowed-hosts.spec.js
Original file line number Diff line number Diff line change
@@ -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/);
});
4 changes: 3 additions & 1 deletion ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@
<div class="reveal" id="pw-hint-wrap"><div class="reveal-in"><p class="grp-tip" id="pw-hint-static" data-i18n-html="general.passwordTip">Leave blank to keep existing password. Min 8 characters. For stronger protection, consider placing the dashboard behind a <strong>dedicated authentication proxy</strong>. This built-in password is a lightweight gate, not a full authentication layer.</p></div></div>
<div class="grp">
<div class="row ie-row" id="ie-hosts"><span class="rl" data-i18n="general.allowedHosts">Allowed Addresses</span><span class="rv is-ph" id="ie-hosts-v">Local addresses only</span><button class="pe" type="button"><svg class="sy-icon" width="17" height="17" aria-hidden="true" focusable="false"><use href="#sy-solid-edit"/></svg></button></div>
<div class="row-wrap reveal" id="hosts-add-wrap"><div class="reveal-in"><div class="row"><span class="rl" id="hosts-add-name"></span><div class="row-btns"><button class="btn bg sm" id="hosts-add" type="button" aria-describedby="hosts-add-tip" data-i18n="common.add">+ Add</button></div></div></div></div>
</div>
<div class="reveal" id="hosts-add-tip-wrap"><div class="reveal-in"><p class="grp-tip" id="hosts-add-tip" data-i18n="general.allowedHostsAddTip">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.</p></div></div>
<p class="grp-tip" data-i18n-html="general.allowedHostsTip">While no password is set, Stackyard answers only on IP addresses, local names such as <code>nas</code> or <code>nas.local</code>, and the addresses listed here. Separate them with commas.</p>

<div class="grp-hdr" role="heading" aria-level="2" data-i18n="general.backup">Backup</div>
Expand Down Expand Up @@ -300,6 +302,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=36288069"></script>
<script type="module" src="/js/admin.js?v=cbe9f554"></script>
</body>
</html>
1 change: 1 addition & 0 deletions ui/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "Damit können Badges Daten von der eigenen Adresse deines Servers abrufen. Ohne sie werden diese Anfragen blockiert.",
"allowedHosts": "Erlaubte Adressen",
"allowedHostsNone": "Nur lokale Adressen",
"allowedHostsAddTip": "Diese Seite ist unter einer Adresse geöffnet, die nicht aufgeführt ist. Füge sie hinzu, damit eine Seite einer anderen Website deine Anmeldeversuche nicht aufbrauchen kann.",
"allowedHostsTip": "Solange kein Passwort festgelegt ist, antwortet Stackyard nur unter IP-Adressen, lokalen Namen wie <code>nas</code> oder <code>nas.local</code> und den hier aufgeführten Adressen. Trenne sie mit Kommas.",
"monitoring": "Überwachung",
"loggingLevel": "Protokollstufe",
Expand Down
1 change: 1 addition & 0 deletions ui/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "Lets badges fetch from your server's own address. Without it, those requests are blocked.",
"allowedHosts": "Allowed Addresses",
"allowedHostsNone": "Local addresses only",
"allowedHostsAddTip": "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.",
"allowedHostsTip": "While no password is set, Stackyard answers only on IP addresses, local names such as <code>nas</code> or <code>nas.local</code>, and the addresses listed here. Separate them with commas.",
"monitoring": "Monitoring",
"loggingLevel": "Logging Level",
Expand Down
1 change: 1 addition & 0 deletions ui/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "Permite que las insignias obtengan datos de la propia dirección de tu servidor. Sin esto, esas solicitudes se bloquean.",
"allowedHosts": "Direcciones permitidas",
"allowedHostsNone": "Solo direcciones locales",
"allowedHostsAddTip": "Esta página está abierta en una dirección que no está en la lista. Añádela para que una página de otro sitio no pueda agotar tus intentos de inicio de sesión.",
"allowedHostsTip": "Mientras no haya contraseña, Stackyard solo responde en direcciones IP, nombres locales como <code>nas</code> o <code>nas.local</code>, y las direcciones de esta lista. Sepáralas con comas.",
"monitoring": "Monitorización",
"loggingLevel": "Nivel de registro",
Expand Down
1 change: 1 addition & 0 deletions ui/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "به نشان‌ها اجازه می‌دهد داده را از آدرس خود سرور بگیرند. بدون آن، این درخواست‌ها مسدود می‌شوند.",
"allowedHosts": "آدرس‌های مجاز",
"allowedHostsNone": "فقط آدرس‌های محلی",
"allowedHostsAddTip": "این صفحه روی آدرسی باز است که در این فهرست نیست. آن را اضافه کنید تا صفحه‌ای از سایتی دیگر نتواند تلاش‌های ورود شما را تمام کند.",
"allowedHostsTip": "تا وقتی رمز عبوری تعیین نشده، Stackyard فقط در آدرس‌های IP، نام‌های محلی مانند <code>nas</code> یا <code>nas.local</code> و آدرس‌های این فهرست پاسخ می‌دهد. آن‌ها را با ویرگول جدا کنید.",
"monitoring": "نظارت",
"loggingLevel": "سطح گزارش",
Expand Down
1 change: 1 addition & 0 deletions ui/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "Permet aux badges de récupérer des données à l’adresse de votre propre serveur. Sans cela, ces requêtes sont bloquées.",
"allowedHosts": "Adresses autorisées",
"allowedHostsNone": "Adresses locales uniquement",
"allowedHostsAddTip": "Cette page est ouverte sur une adresse qui n’est pas listée. Ajoutez-la pour qu’une page d’un autre site ne puisse pas épuiser vos tentatives de connexion.",
"allowedHostsTip": "Tant qu’aucun mot de passe n’est défini, Stackyard ne répond qu’aux adresses IP, aux noms locaux comme <code>nas</code> ou <code>nas.local</code>, et aux adresses listées ici. Séparez-les par des virgules.",
"monitoring": "Surveillance",
"loggingLevel": "Niveau de journalisation",
Expand Down
1 change: 1 addition & 0 deletions ui/i18n/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"hostIpTip": "允许徽章从服务器自身地址获取数据。否则这些请求会被拦截。",
"allowedHosts": "允许的地址",
"allowedHostsNone": "仅本地地址",
"allowedHostsAddTip": "此页面所在的地址不在列表中。添加它,以免其他网站的页面耗尽你的登录尝试次数。",
"allowedHostsTip": "在未设置密码时,Stackyard 只在 IP 地址、<code>nas</code> 或 <code>nas.local</code> 这样的本地名称以及此处列出的地址上响应。多个地址用逗号分隔。",
"monitoring": "监控",
"loggingLevel": "日志级别",
Expand Down
28 changes: 28 additions & 0 deletions ui/js/admin-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ function syncSessionRows(now = false) {
const canRevoke = on && _passwordSet;
reveal(el('sec-revoke-wrap'), canRevoke, now);
reveal(el('revoke-tip-wrap'), canRevoke, now);
syncHostOffer(now);
}

function syncHostOffer(now = false) {
const here = hostnameOf(location.host);
const listed = parseHostList(inp('srv-hosts')?.value || '') || [];
const show = !!inp('sec-en')?.checked && _passwordSet && !!here && !isLocalAddress(here) && !listed.includes(here);
const name = el('hosts-add-name');
if (name && here) setUserText(name, here);
reveal(el('hosts-add-wrap'), show, now);
reveal(el('hosts-add-tip-wrap'), show, now);
}

function addCurrentHost() {
const here = hostnameOf(location.host);
const hostsEl = inp('srv-hosts');
const list = parseHostList(hostsEl?.value || '');
if (!hostsEl || !here || !list) return;
hostsEl.value = [...list, here].join(', ');
const valEl = el('ie-hosts-v');
if (valEl) {
setUserText(valEl, hostsEl.value);
valEl.classList.remove('is-ph');
}
syncHostOffer();
/** @type {HTMLElement | null | undefined} */ (el('ie-hosts')?.querySelector('.pe'))?.focus();
}

/* Rebuild on every load. The control takes its value at render time and has no
Expand Down Expand Up @@ -279,6 +305,8 @@ export function loadSettings(c) {
}
});
secEnEl?.addEventListener('change', () => syncSessionRows());
el('hosts-add')?.addEventListener('click', addCurrentHost);
inp('srv-hosts')?.addEventListener('blur', () => syncHostOffer());

_srvTrack = trackSave('srv-save', readServerForm);
_bgTrack = trackSave('bg-save', readWallpaperForm);
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
showBgFields,
showWallpaperFile,
wireUnsplashKey,
} from '/js/admin-settings.js?v=41547198';
} from '/js/admin-settings.js?v=de5b1723';
import {
apiGet,
apiPost,
Expand Down
Loading