|
1 | 1 | import { generateSecret, generateURI } from "otplib" |
| 2 | +import { renderSVG } from "uqr" |
2 | 3 | import { read2fa, create2fa } from "../../../../../../src/2fa.js" |
3 | 4 | import { readUser } from "../../../../../../src/users.js" |
4 | 5 |
|
@@ -121,21 +122,28 @@ export const onRequestPost: Handler = async (context) => { |
121 | 122 | ) |
122 | 123 | } |
123 | 124 |
|
124 | | - // Step 6: Generate QR Code Data (TOTP Auth URI) |
| 125 | + // Step 6: Generate the TOTP auth URI and render it to a QR code. |
| 126 | + // The URI embeds the TOTP secret, so the QR code is rendered here, in the |
| 127 | + // Worker, as an inline SVG — the secret is never sent to a third-party |
| 128 | + // image service. Inline SVG is page markup, not a fetched resource, so it |
| 129 | + // is also unaffected by the page's `default-src https:` CSP. |
125 | 130 | const otpauthUri = generateURI({ |
126 | 131 | issuer: APP_NAME, |
127 | 132 | label: userName, |
128 | 133 | secret: display_secret, |
129 | 134 | }) |
| 135 | + const qrCodeSvg = renderSVG(otpauthUri, { border: 2 }).replace( |
| 136 | + "<svg", |
| 137 | + '<svg class="tfa-qr-code" width="200" height="200" role="img" aria-label="Two-factor authentication QR code"' |
| 138 | + ) |
130 | 139 |
|
131 | 140 | // Step 7: Response - HTML for HTMX |
132 | | - // TODO: [Security] Consider using a more secure method for generating QR codes |
133 | 141 | const htmlResponse = ` |
134 | 142 | <div> |
135 | 143 | <h3>Setup Two-Factor Authentication</h3> |
136 | 144 | <p>Scan the QR code with your authenticator app or enter the setup code manually.</p> |
137 | 145 | <div class="tfa-qr-layout"> |
138 | | - <img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(otpauthUri)}" alt="QR Code" class="tfa-qr-code"/> |
| 146 | + ${qrCodeSvg} |
139 | 147 | <div> |
140 | 148 | <p><strong>Manual Setup Code:</strong></p> |
141 | 149 | <p class="tfa-secret-display">${display_secret}</p> |
|
0 commit comments