Skip to content

2FA: draw the authenticator QR code in the browser - #258

Merged
windischb merged 1 commit into
developfrom
fix/totp-qr-local
Oct 10, 2026
Merged

windischb merged 1 commit into
developfrom
fix/totp-qr-local

Conversation

@windischb

Copy link
Copy Markdown
Contributor

The QR code in the authenticator setup was a broken image on every production image, so an authenticator app could only be set up by typing the key.

Both setup dialogs (profile → security, and the enforced setup at sign-in) loaded the code as an image from api.qrserver.com. The CSP only allows img-src 'self' data:, which refuses that image. Without the CSP the request would have carried the TOTP secret to that service.

What changes

  • New QrCode.vue: builds an inline SVG from the otpauth:// URI in the page — nothing leaves the origin. Dark on white in dark mode too, with the four-module quiet zone scanners expect.
  • New dependency uqr 0.1.3 (MIT, no dependencies of its own) for the QR encoding.
  • MfaSetupModal.vue and SecureSetupModal.vue use the component.
  • 50-2fa.spec.ts opens the profile dialog against the production image and asserts that the code is drawn, that no request leaves the origin and that the CSP is not violated. The spec only spoke to the API before, which is why this went unnoticed. It also signs in with the e-mail address now: a created user's user name is the address, no longer the acronym, so the spec was already red on develop.

Tests

  • pnpm type-check and pnpm build pass.
  • 50-2fa.spec.ts against a production image built from this branch: 3 of 3 green.
  • Red check: with the old dialog code in the same image the new test fails, and the browser reports the CSP violation for api.qrserver.com in both dialogs.
  • Screenshots of the profile dialog (light and dark) and of the sign-in dialog show the code; an independent decoder reads exactly the URI the server returned from all three. The sign-in dialog was only exercised in that one-off run (with the login answer rewritten to require setup) — the permanent test covers the profile dialog.
  • Not scanned with a real phone.

The rest of the Playwright suite is red on develop for unrelated reasons and no workflow runs it; that is not part of this change.

🤖 Generated with Claude Code

Both setup dialogs (profile and the enforced setup at sign-in) loaded the QR
code as an image from api.qrserver.com. The CSP only allows
`img-src 'self' data:`, so a production image showed a broken picture and the
authenticator app could only be set up by typing the key. Without the CSP the
request would have carried the TOTP secret to that service.

The code is now an inline SVG built from the otpauth URI with `uqr` (no
dependencies of its own): nothing leaves the page. It stays dark on white in
dark mode and has the four-module quiet zone scanners expect.

The 2FA e2e spec only spoke to the API, which is why this went unnoticed. It
now opens the dialog against the production image and asserts that the code is
drawn, that no request leaves the origin and that the CSP is not violated. The
spec also signs in with the e-mail address: a created user's user name is the
address, no longer the acronym, so the spec was red on develop before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@windischb
windischb merged commit 70f5bfa into develop Oct 10, 2026
8 checks passed
@windischb
windischb deleted the fix/totp-qr-local branch October 10, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant