From 5d0b6355b153748cf629b5910e9ed73adcc2af2d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 20:10:10 +0000 Subject: [PATCH] security-dashboard: put TURN creds in the Sipnetic QR, widen the popup - ea_device_sipnetic_string() now sets Sipnetic's documented st= field to an explicit turn:user:pass@host:port URI built from the same TURN_SERVER/TURN_USERNAME/TURN_PASSWORD Asterisk itself reads from its .env (the shared VPS coturn on a droplet, or whichever coturn Asterisk is actually configured against). Previously the QR carried no TURN info at all, silently falling back to Sipnetic's own default STUN server instead -- registration/media then depends on whatever got typed in by hand instead of what Asterisk is actually using. - Popup widened (192px content -> 320px card) and the QR rendered at 3x its displayed resolution (physical size unchanged): the longer TURN-inclusive account string needs a denser code, and verified via a headless render + OpenCV/pyzbar decode that the extra module density needs the resolution bump to stay reliably scannable. - Restored (and expanded) the plain-text-credentials warning that was dropped when the box became a modal, now covering TURN creds too. --- services/security-dashboard.sh | 54 +++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/services/security-dashboard.sh b/services/security-dashboard.sh index b306e68..385de4e 100644 --- a/services/security-dashboard.sh +++ b/services/security-dashboard.sh @@ -3036,11 +3036,23 @@ def ea_device_sipnetic_string(extension): """Sipnetic's own documented "account string" QR-scan format (https://www.sipnetic.com/qr-codes): semicolon-separated key=value pairs, n=display name, u=username, d=domain/IP (no port), p=password, - dt=default transport (0=UDP, 1=TCP, 2=TLS). Unlike + dt=default transport (0=UDP, 1=TCP, 2=TLS), st=STUN/TURN server. Unlike ea_device_provisioning()'s deliberately generic plain-text file, this one IS a verified, documented format for one specific app, built from the exact same ea_device_details() data. + st is intentionally set to an explicit turn:user:pass@host:port URI + (Sipnetic's doc confirms the st field accepts embedded credentials in + that form) whenever coturn is configured, rather than left unset -- + leaving it out doesn't mean "no TURN", it means Sipnetic falls back to + its own default/built-in STUN server instead of the coturn instance + Asterisk itself is actually using, which is silently wrong rather than + absent. This is the same TURN_SERVER/TURN_USERNAME/TURN_PASSWORD + ea_device_details() already reads from the Asterisk .env (see + ea_connection_defaults()) -- whichever coturn Asterisk was configured + against (the shared instance on a droplet, or an embedded one), not a + second, separately-derived value. + A literal ';' in any field must be doubled per that same doc page -- generated passwords are alnum-only (_ea_generate_password) so this only matters for a hand-typed device name, but escaping costs nothing.""" @@ -3053,10 +3065,18 @@ def ea_device_sipnetic_string(extension): host = d["server"] or "" dt = "2" if d["transport"] == "TLS" else "0" - return "n=%s;u=%s;d=%s;p=%s;dt=%s;" % ( - esc_field(d["name"] or d["extension"]), esc_field(d["extension"]), - esc_field(host), esc_field(d["password"]), dt, - ) + fields = [ + "n=%s" % esc_field(d["name"] or d["extension"]), + "u=%s" % esc_field(d["extension"]), + "d=%s" % esc_field(host), + "p=%s" % esc_field(d["password"]), + "dt=%s" % dt, + ] + if d.get("turn_server") and d.get("turn_username") and d.get("turn_password"): + fields.append("st=%s" % esc_field( + "turn:%s:%s@%s" % (d["turn_username"], d["turn_password"], d["turn_server"]) + )) + return ";".join(fields) + ";" def _ea_edit_device_block(extension, mutate): @@ -3814,8 +3834,12 @@ INDEX_HTML = """ padding: var(--sp-4); } #qr-modal-overlay.show { display: flex; } + /* Sized to the QR frame's own 192px, not this -- the caption text below it + (TURN credentials warning included) wrapped down to a couple of + characters per line at that width. 320px gives it room to breathe while + staying well short of the surrounding card. */ .qr-modal { - position: relative; + position: relative; width: 320px; max-width: calc(100vw - 2 * var(--sp-4)); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--sp-4); box-shadow: 0 8px 24px rgba(0,0,0,0.45); @@ -4148,7 +4172,8 @@ INDEX_HTML = """
-

Scan with Sipnetic (Add Account → Scan QR Code)

+

Scan with Sipnetic (Add Account → Scan QR Code) to auto-fill this extension's SIP and TURN settings.

+

Contains the extension's password and TURN credentials in plain text — treat the image like the password itself.