-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix: rate-limit known-device probe and TOTP QR #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -625,6 +625,8 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| <span id="copyTotpBtnText">Copy</span> | ||||||||
| </button> | ||||||||
| </div> | ||||||||
| <p class="hint" id="t_s5_uri_hint" style="margin-top:10px;"></p> | ||||||||
| <div class="server" id="totpUri"></div> | ||||||||
| <div class="totp-preview" id="totpPreview"> | ||||||||
| <div class="totp-code" id="totpCodeDisplay">------</div> | ||||||||
| <div class="totp-expire" id="totpExpireText"></div> | ||||||||
|
|
@@ -678,6 +680,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| </div> | ||||||||
| </div> | ||||||||
|
|
||||||||
| <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js" referrerpolicy="no-referrer"></script> | ||||||||
|
||||||||
| <script> | ||||||||
| const JWT_STATE = ${jwtStateJson}; | ||||||||
|
|
||||||||
|
|
@@ -755,6 +758,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| s5Enable1: '打开 Cloudflare 控制台 -> Workers 和 Pages -> NodeWarden -> 设置 -> 变量和机密。', | ||||||||
| s5Enable2: '新增 Secret:TOTP_SECRET,值填写下方生成的 Base32 密钥。', | ||||||||
| s5QrTitle: '扫描二维码', | ||||||||
| s5UriHint: '如需手动导入,请复制以下 otpauth 链接:', | ||||||||
| copyCode: '复制验证码', | ||||||||
| totpExpire: '秒后过期', | ||||||||
| s6Title: '最终页面', | ||||||||
|
|
@@ -841,6 +845,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| s5Enable1: 'Open Cloudflare Dashboard -> Workers & Pages -> your service -> Settings -> Variables and Secrets.', | ||||||||
| s5Enable2: 'Add Secret: TOTP_SECRET, using the generated Base32 seed below.', | ||||||||
| s5QrTitle: 'Scan QR code', | ||||||||
| s5UriHint: 'For manual import, copy this otpauth URI:', | ||||||||
| copyCode: 'Copy code', | ||||||||
| totpExpire: 's left', | ||||||||
| s6Title: 'Final step', | ||||||||
|
|
@@ -951,6 +956,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| setText('t_s5_enable_1', t('s5Enable1')); | ||||||||
| setText('t_s5_enable_2', t('s5Enable2')); | ||||||||
| setText('t_s5_qr_title', t('s5QrTitle')); | ||||||||
| setText('t_s5_uri_hint', t('s5UriHint')); | ||||||||
| setText('refreshTotpBtnText', t('refresh')); | ||||||||
| setText('copyTotpBtnText', t('copy')); | ||||||||
| setText('copyTotpCodeBtnText', t('copyCode')); | ||||||||
|
|
@@ -1039,16 +1045,50 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string | |||||||
| + '&algorithm=SHA1&digits=6&period=30'; | ||||||||
| } | ||||||||
|
|
||||||||
| function renderTotpQrPlaceholder() { | ||||||||
| const qr = document.getElementById('totpQr'); | ||||||||
| if (!qr) return; | ||||||||
| const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="170" height="170" viewBox="0 0 170 170" role="img" aria-label="Local-only TOTP setup">' | ||||||||
| + '<rect width="170" height="170" fill="#ffffff"/>' | ||||||||
| + '<rect x="10" y="10" width="150" height="150" rx="10" fill="#f8fafc" stroke="#d5dae1"/>' | ||||||||
| + '<path d="M85 46a16 16 0 0 0-16 16v8h32v-8a16 16 0 0 0-16-16Zm-9 24v-8a9 9 0 1 1 18 0v8h-18Zm-7 8h32v30H69V78Zm16 6a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z" fill="#111418"/>' | ||||||||
| + '<text x="85" y="130" text-anchor="middle" font-size="10" font-family="Arial, sans-serif" fill="#344054">LOCAL ONLY</text>' | ||||||||
| + '<text x="85" y="143" text-anchor="middle" font-size="9" font-family="Arial, sans-serif" fill="#667085">Use seed / otpauth URI</text>' | ||||||||
| + '</svg>'; | ||||||||
| qr.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg); | ||||||||
| } | ||||||||
|
|
||||||||
| function renderTotpQrFromUri(uri) { | ||||||||
| const qr = document.getElementById('totpQr'); | ||||||||
| if (!qr) return; | ||||||||
|
|
||||||||
| try { | ||||||||
| if (typeof qrcode === 'function') { | ||||||||
|
||||||||
| if (typeof qrcode === 'function') { | |
| if (typeof qrcode !== 'undefined' && qrcode) { |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The empty catch block silently swallows all errors that occur during QR code generation. While the code does fall back to the placeholder, logging the error would help with debugging issues in production. Consider adding a console.error or console.warn statement to log the exception.
| } catch { | |
| } catch (err) { | |
| console.error('Failed to render TOTP QR code from URI.', err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loading external scripts from a CDN without Subresource Integrity (SRI) creates a security risk. If the CDN is compromised or serves malicious content, it could execute arbitrary code in users' browsers during the setup process. Consider either: (1) adding an integrity attribute with the SHA hash of the expected file, or (2) bundling the qrcode-generator library locally instead of loading it from a CDN.