From 635cc36dd5da4e2c6f077ab94cb00af58057387a Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Sun, 5 Jul 2026 19:12:53 +0200 Subject: [PATCH] fix(2fa): use shortener QR PNG URL and otpauth fallback AuthTwoFactorPage was setting img src to the short link URL (HTML), not the /api/v1/qr/*.png endpoint. Always expose a PNG via qr_url. Co-authored-by: Cursor --- src/AuthTwoFactorPage.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/AuthTwoFactorPage.php b/src/AuthTwoFactorPage.php index 05ac044..94bd70b 100644 --- a/src/AuthTwoFactorPage.php +++ b/src/AuthTwoFactorPage.php @@ -9,7 +9,7 @@ final class AuthTwoFactorPage { * Returns an array with: * long_url – approval URL (fallback if shortener unavailable) * short_url – shortened URL (s.f0xx.org) - * qr_url – URL of the QR PNG image (short_url + ?src=qr) + * qr_url – PNG image URL (url-shortener /api/v1/qr/… or otpauth QR fallback) * raw_token – the approval token to store in session for polling * * @return array{long_url:string,short_url?:string,qr_url?:string,raw_token:string}|null @@ -28,29 +28,35 @@ final class AuthTwoFactorPage { $approveUrl = 'https://apps.f0xx.org' . $project . '/two-factor/approve?token=' . urlencode($rawToken); + $payload = [ + 'long_url' => $approveUrl, + 'raw_token' => $rawToken, + 'qr_url' => AuthTotp::qrImageUrl($approveUrl), + ]; + if (!UrlShortenerDatabase::enabled() || !UrlShortenerDatabase::ping()) { - return ['long_url' => $approveUrl, 'raw_token' => $rawToken]; + return $payload; } $bearers = ShortLinksRepository::listBearers(); if ($bearers === []) { - return ['long_url' => $approveUrl, 'raw_token' => $rawToken]; + return $payload; } $bearerId = (int) ($bearers[0]['id'] ?? 0); if ($bearerId <= 0) { - return ['long_url' => $approveUrl, 'raw_token' => $rawToken]; + return $payload; } $mint = ShortLinksRepository::createLink($bearerId, $approveUrl, 900); if (empty($mint['ok'])) { - return ['long_url' => $approveUrl, 'raw_token' => $rawToken]; + return $payload; } $shortUrl = (string) ($mint['short_url'] ?? ''); - // QR image encodes the short URL with ?src=qr so mobile scans are tracked - $qrUrl = $shortUrl . (str_contains($shortUrl, '?') ? '&src=qr' : '?src=qr'); - return [ - 'long_url' => $approveUrl, - 'short_url' => $shortUrl, - 'qr_url' => $qrUrl, - 'raw_token' => $rawToken, - ]; + $qrUrl = (string) ($mint['qr_url'] ?? ''); + if ($qrUrl !== '') { + $payload['qr_url'] = $qrUrl; + } + if ($shortUrl !== '') { + $payload['short_url'] = $shortUrl; + } + return $payload; } }