46decf351fc0

offer the setup key and address with copy buttons, and no QR (v3.2.3)

time
2026-10-08 21:55 UTC · 4 days ago
machine
Junes-MacBook-Pro.local
agent
deepseek-flash
session
two-step sign-in
parent
0b513e91a395
3 files package.json+1 −1src/admin/account.ts+8 −5tests/account.test.ts+6 −0
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.2.2",3+	"version": "3.2.3",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

src/admin/account.ts+8 −5
@@ -82,11 +82,14 @@8282 	// typed it holds it, so a tampered field can only fail.8383 	const twoStep =8484 		offered !== ""85-			? html`<p>Add this key to an authenticator app by hand, or paste the address under it, then86-type the code the app shows. The key is shown this once; from then on sign-in asks for the password87-and a code.</p>88-<p class="issue-meta mono">key: ${offered}</p>89-<p class="issue-meta mono">${otpauthUri(offered, ctx.domain)}</p>85+			? html`<p>Add this key to an authenticator app by hand, or open the address below on a phone86+that has one, then type the code the app shows. The key is shown this once; from then on sign-in87+asks for the password and a code.</p>88+<p class="issue-meta mono">key: ${offered}89+<button type="button" class="copy-btn" data-copy="${offered}" hidden>copy key</button></p>90+<p class="issue-meta mono"><a href="${otpauthUri(offered, ctx.domain)}">open in an authenticator app</a>91+<button type="button" class="copy-btn" data-copy="${otpauthUri(offered, ctx.domain)}" hidden>copy the92+setup address</button></p>9093 <form class="issues-form" method="post" action="/admin/account">9194 ${formField(c)}<input type="hidden" name="action" value="totp-confirm">9295 <input type="hidden" name="secret" value="${offered}">

view file

tests/account.test.ts+6 −0
@@ -130,6 +130,12 @@130130 		const secret = /key: ([A-Z2-7]{32})/.exec(offered)?.[1] ?? "";131131 		expect(secret).toHaveLength(32);132132 		expect(offered).toContain("otpauth://totp/kepr:kepr.example");133+		// Both the key and the setup address can be copied, and the address opens an app directly.134+		expect(offered).toContain(`data-copy="${secret}"`);135+		expect(offered).toContain(`data-copy="otpauth://totp/kepr:kepr.example?secret=${secret}`);136+		expect(offered).toContain(`href="otpauth://totp/kepr:kepr.example?secret=${secret}`);137+		// The html the template writes escapes the ampersands the address carries.138+		expect(offered).toContain("&amp;issuer=kepr");133139 		expect(inst.config.totpSecret).toBeUndefined();134140 		const wrong = await operator.post("/admin/account", {135141 			action: "totp-confirm",

view file