fix(zigbee,authelia): admin buttons read access URL from API (Lyrion pattern)

Apply the source-of-truth pattern from secubox-lyrion d4adc1a3 to two
sibling admin panels:

- Zigbee /zigbee/ admin webui: "Open Zigbee Manager →" button now reads
  the public URL (or LAN fallback) from /api/v1/zigbee/access instead
  of hardcoding https://zigbee.gk2.secubox.in/. The PUBLIC_URL env var
  the FastAPI already exposed is now the only place the URL lives.

- Authelia /authelia/ admin webui: same treatment for "Open SSO Portal"
  button. Plus autheliactl gets the same fixes lyrionctl did:
    * config_get strips inline TOML comments instead of concatenating
      them into the value (HTTP_PORT used to land as "9091  # comment")
    * emit_access_json drops the /authelia/ admin suffix from URLs —
      the SSO portal itself serves at the root of the dedicated vhost,
      not under a subpath
    * lan_url uses the LXC IP + http_port (10.100.0.20:9091) instead
      of the public-facing host's address
    * PUBLIC_HOSTNAME defaults to sso.gk2.secubox.in (was empty,
      legacy auth.maegia.tv only in the toml example)
    * stray `""|""` case pattern in cmd_status changed to `""|list`
      to match the cmd_components / cmd_access conventions

- conf/authelia.toml.example: public_hostname default
  auth.maegia.tv -> sso.gk2.secubox.in.

Live verified on gk2 — all three modules return the same shape:
  lyrion   lan http://192.168.1.200:9000/  public https://lyrion.gk2.secubox.in/
  zigbee   lan http://10.100.0.111:8080/   public https://zigbee.gk2.secubox.in/
  authelia lan http://10.100.0.20:9091/    public https://sso.gk2.secubox.in/

And the admin button on each module's /XXX/ page reads from /api/v1/
XXX/access, so any future hostname/port change in the toml propagates
automatically — no more hardcoded URL drift.
This commit is contained in:
CyberMind-FR 2026-05-24 09:55:00 +02:00
parent d4adc1a3e1
commit 54da8a7cb1
4 changed files with 51 additions and 17 deletions

View File

@ -34,8 +34,9 @@ inactivity = "5m"
enabled = false
[exposure]
# Public hostname for the Authelia login portal.
public_hostname = "auth.maegia.tv"
# Public hostname for the Authelia login portal. Used by the SecuBox
# admin webui's "Open SSO Portal" button + the /access endpoint.
public_hostname = "sso.gk2.secubox.in"
waf_inspect = true # route through mitmproxy (canonical maegia.tv pattern)
[oidc]

View File

@ -29,20 +29,33 @@ err() { printf '%b[error]%b %s\n' "$RED" "$NC" "$*" >&2; }
# ── TOML config (best-effort, grep-style — same as giteactl) ─────────────────
config_get() {
local key="$1" default="${2:-}"
# Strips TOML inline `# comment`, surrounding whitespace, and quotes.
# Previous version concatenated `# comment text` into the value
# (eg HTTP_PORT became "9091 # comment"), breaking URL construction.
local key="$1" default="${2:-}" raw=""
if [ -f "$CONFIG_FILE" ]; then
grep -E "^[[:space:]]*${key}[[:space:]]*=" "$CONFIG_FILE" 2>/dev/null \
| head -1 | cut -d= -f2- | tr -d ' "' | tr -d "'" || echo "$default"
else
raw=$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$CONFIG_FILE" 2>/dev/null | head -1)
fi
if [ -z "$raw" ]; then
echo "$default"
return
fi
local val
val=$(echo "$raw" | cut -d= -f2- | sed 's/[[:space:]]*#.*$//' \
| sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \
| tr -d '"' | tr -d "'")
if [ -z "$val" ]; then
echo "$default"
else
echo "$val"
fi
}
LXC_NAME=$(config_get "name" "authelia")
LXC_IP=$(config_get "ip" "10.100.0.20")
LXC_PATH=$(config_get "path" "/data/lxc")
HTTP_PORT=$(config_get "http_port" "3000")
PUBLIC_HOSTNAME=$(config_get "public_hostname" "")
HTTP_PORT=$(config_get "http_port" "9091")
PUBLIC_HOSTNAME=$(config_get "public_hostname" "sso.gk2.secubox.in")
# ── LXC helpers ───────────────────────────────────────────────────────────────
lxc_state() {
@ -126,10 +139,15 @@ EOF
}
emit_access_json() {
# Real Authelia login portal URLs — NOT the /authelia/ admin subpath.
# The admin webui's "Open SSO Portal" button reads this; consistency
# comes from one source.
# lan: http://<lxc_ip>:9091/ direct to Authelia daemon (LAN-only)
# public: https://<PUBLIC_HOSTNAME>/ dedicated vhost
local lan_url public_url
lan_url="https://$(hostname -I | awk '{print $1}')/authelia/"
lan_url="http://${LXC_IP}:${HTTP_PORT}/"
if [ -n "$PUBLIC_HOSTNAME" ]; then
public_url="https://${PUBLIC_HOSTNAME}/authelia/"
public_url="https://${PUBLIC_HOSTNAME}/"
else
public_url=""
fi
@ -137,9 +155,9 @@ emit_access_json() {
{
"module": "authelia",
"access": [
{"url": "$lan_url", "auth": "jwt|authelia-admin", "scope": "lan"}$( [ -n "$public_url" ] && echo ',' )
{"url": "$lan_url", "auth": "lan-only", "scope": "lan"}$( [ -n "$public_url" ] && echo ',' )
$( [ -n "$public_url" ] && cat <<PUB
{"url": "$public_url", "auth": "jwt|authelia-admin", "scope": "public"}
{"url": "$public_url", "auth": "Authelia SSO", "scope": "public"}
PUB
)
]
@ -159,7 +177,7 @@ cmd_components() {
cmd_status() {
case "${1:-}" in
--json) emit_status_json ;;
""|"") emit_components_text ; echo ; emit_status_json | python3 -c 'import json,sys; d=json.load(sys.stdin); print("overall:", d["overall"])' ;;
""|list) emit_components_text ; echo ; emit_status_json | python3 -c 'import json,sys; d=json.load(sys.stdin); print("overall:", d["overall"])' ;;
*) err "unknown verb for 'status': $1"; exit 1 ;;
esac
}

View File

@ -83,7 +83,7 @@
<h1><span class="icon">🎯</span> Auth — SSO control</h1>
<div class="header-actions">
<a class="btn" href="/" title="Back to SecuBox hub">← hub</a>
<a class="btn primary" href="https://sso.gk2.secubox.in/" title="Open the Authelia SSO portal (login surface)">Open SSO Portal →</a>
<a class="btn primary" id="open-sso" href="#" target="_blank" rel="noopener" title="Open the Authelia SSO portal (login surface)">Open SSO Portal →</a>
</div>
</div>
@ -191,8 +191,15 @@
document.getElementById('stat-cookies-sub').textContent = cookies.map(function (c) { return c.domain || c; }).join(', ') || 'session.cookies[]';
document.getElementById('stat-rules').textContent = String(rules.length || '?');
var v = (d.access || []).map(function (a) { return '<div>' + (a.scope || '') + ': <a href="' + (a.url || '#') + '">' + (a.url || '?') + '</a></div>'; }).join('');
var access = d.access || [];
var v = access.map(function (a) { return '<div>' + (a.scope || '') + ': <a href="' + (a.url || '#') + '">' + (a.url || '?') + '</a></div>'; }).join('');
document.getElementById('access-v').innerHTML = v || '<span style="color:var(--muted)">no access points</span>';
// Single source of truth for the "Open SSO Portal" button.
var pub = access.find(function (a) { return a.scope === 'public'; });
var lan = access.find(function (a) { return a.scope === 'lan'; });
var target = (pub && pub.url) || (lan && lan.url) || '#';
var btn = document.getElementById('open-sso');
if (btn) btn.href = target;
}).catch(function () {
document.getElementById('access-v').innerHTML = '<span style="color:var(--muted)">unavailable</span>';
});

View File

@ -96,7 +96,7 @@
<h1><span class="icon">📡</span> Zigbee — Mesh manager</h1>
<div class="header-actions">
<a class="btn" href="/" title="Back to SecuBox hub">← hub</a>
<a class="btn primary" href="https://zigbee.gk2.secubox.in/" title="Open the full zigbee2mqtt manager (SSO-gated)">Open Zigbee Manager →</a>
<a class="btn primary" id="open-z2m" href="#" target="_blank" rel="noopener" title="Open the full zigbee2mqtt manager (SSO-gated)">Open Zigbee Manager →</a>
</div>
</div>
@ -179,8 +179,16 @@
}).catch(function () { document.getElementById('status-v').innerHTML = '<span style="color:var(--muted)">unavailable</span>'; });
get('/access').then(function (d) {
var list = (d.access || []).map(function (a) { return '<div>' + a.scope + ': <a href="' + a.url + '">' + a.url + '</a></div>'; }).join('');
var access = d.access || [];
var list = access.map(function (a) { return '<div>' + a.scope + ': <a href="' + a.url + '">' + a.url + '</a></div>'; }).join('');
document.getElementById('access-v').innerHTML = list || '<span style="color:var(--muted)">no access points</span>';
// Single source of truth for the "Open Zigbee Manager" button:
// prefer the public (Authelia-gated) URL, fall back to LAN.
var pub = access.find(function (a) { return a.scope === 'public'; });
var lan = access.find(function (a) { return a.scope === 'lan'; });
var target = (pub && pub.url) || (lan && lan.url) || '#';
var btn = document.getElementById('open-z2m');
if (btn) btn.href = target;
}).catch(function () { document.getElementById('access-v').innerHTML = '<span style="color:var(--muted)">unavailable</span>'; });
// ── Backups card ─────────────────────────────────────────────