fix(qr-login): render QR code server-side using segno instead of CDN JS library

The QR code on /qr-login was not rendering because it depended on loading
qrcode@1.5.4 from the jsdelivr CDN, which may be blocked in some network
environments.

- Add segno>=1.6.0 (pure-Python QR library, no Pillow needed) to requirements.txt
- Generate QR code as a base64 SVG data URI server-side in the challenge endpoint
- Add qr_code_svg field to CreateChallengeResponse Pydantic model
- Replace canvas+CDN script in qr_login.html with an <img :src="qrCodeSvg">
- Remove the $nextTick/QRCode.toCanvas() client-side rendering block
- Extract QR rendering parameters (_QR_ERROR_LEVEL, _QR_SCALE) as module constants

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-18 08:34:22 +00:00
parent 6727253958
commit a8eb6504ac
3 changed files with 39 additions and 15 deletions
+10 -15
View File
@@ -32,7 +32,13 @@
id="qr-container"
aria-label="{{ _('qr_login.description') }}"
>
<canvas id="qr-canvas" width="256" height="256"></canvas>
<img
:src="qrCodeSvg"
width="256"
height="256"
alt="{{ _('qr_login.description') }}"
id="qr-image"
/>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">
{{ _("qr_login.description") }}
@@ -110,8 +116,7 @@
</section>
</div>
<!-- QR Code library (lightweight, no external deps) -->
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.4/build/qrcode.min.js"></script>
<!-- QR code is rendered server-side; no external QR library needed -->
<script>
function qrLoginPage() {
@@ -120,6 +125,7 @@ function qrLoginPage() {
challengeId: null,
challengeToken: '',
qrPayload: '',
qrCodeSvg: '',
expiresAt: null,
countdown: 0,
deviceName: '',
@@ -157,24 +163,13 @@ function qrLoginPage() {
this.challengeId = data.challenge_id;
this.challengeToken = data.challenge_token;
this.qrPayload = data.qr_payload;
this.qrCodeSvg = data.qr_code_svg;
this.expiresAt = new Date(data.expires_at);
this._ttlSeconds = data.ttl_seconds || 120;
this._receivedAt = Date.now();
this.status = 'pending';
this.deviceName = '';
// Render QR code
this.$nextTick(() => {
const canvas = document.getElementById('qr-canvas');
if (canvas && typeof QRCode !== 'undefined') {
QRCode.toCanvas(canvas, this.qrPayload, {
width: 256,
margin: 2,
color: { dark: '#000000', light: '#ffffff' },
});
}
});
// Start polling and countdown
this._startPolling();
this._startCountdown();