adminui: show the enrolment link as a QR code

Enrolling a device that cannot reach the admin UI meant transcribing a
200-character link with a base64 pin in it — the step the link format
exists to avoid, and the one where a pin wrong by one character fails
later as an inscrutable TLS error.

Rendered as inline SVG rather than a PNG data: URI, because the page's
CSP is default-src 'none' and means it: a data: image would need img-src
opened, markup needs nothing. One path rather than a rect per module,
since a link this long encodes to about 60x60 and two thousand elements
is a lot of DOM for a picture of a square. It is generated from the same
validated value as the href, so a rejected link produces neither.

This relaxes the stdlib-only rule, deliberately and recorded in CLAUDE.md.
The rule bought one self-contained binary with no supply chain to audit,
which one small pure-Go package barely dents; F-Droid never applied to
the server, only the app ships there. A correct QR encoder is ~500 lines
of Reed-Solomon that nobody should be hand-writing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-02 08:48:02 +02:00
co-authored by Claude Opus 5
parent 5b02d40802
commit 40e76c52ca
6 changed files with 95 additions and 3 deletions
+7 -2
View File
@@ -167,6 +167,10 @@ const baseHTML = `<!doctype html>
.narrow{max-width:27rem}
.panel{background:var(--hull);border:1px solid var(--rule);border-radius:3px;
padding:.95rem 1rem;margin:.9rem 0;min-width:0}
/* White plate behind the code: a QR needs the light modules to actually be light, and this
page is dark. */
.qr{display:inline-block;background:#fff;padding:8px;border-radius:4px;margin:.2rem 0;line-height:0}
.qr svg{display:block;width:min(240px,60vw);height:auto}
.empty{border:1px dashed var(--rule);border-radius:3px;padding:1.4rem 1rem;
color:var(--dim);font-size:.9rem}
code,pre,.mono{font-family:var(--mono);font-size:.82rem}
@@ -328,8 +332,9 @@ const baseHTML = `<!doctype html>
app, so following the link hands it the token directly. Copying a 200-character string
between two devices is the step that goes wrong, and it does not have to happen at all. -->
{{with $.LinkHref}}<p><a class="btn" href="{{.}}">Open in the Echolot app</a></p>{{end}}
<p class="muted">Only works on the phone you are enrolling. From anywhere else, copy the link
into the app's enrolment field, or deliver it over adb.</p>
<p class="muted">Works on the phone you are enrolling. From another device, scan this:</p>
{{with $.LinkQR}}<div class="qr">{{.}}</div>{{end}}
<p class="muted">Or copy the link into the app's enrolment field, or deliver it over adb.</p>
<p><code>{{.}}</code></p>
<p class="muted mono">adb shell am start -a android.intent.action.VIEW -d "{{.}}"</p>
</div>