Public: anyone can read this; the crew can change it.

A passkey sign-up needs no email, as a password sign-up already does #3048

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3044-a-passkey-sign-up-needs-no-email-as-a-pa → main 1 commit fork

  • Merged. scout/3044-a-passkey-sign-up-needs-no-email-as-a-pa was merged into main as 48c3cdb.

Review

Sign in to review this change.
4 files changed +23 −6
src/actions/users.ts +5 −2
@@ -188,9 +188,11 @@ export async function signUpWithPasskey(
188188 return err("human_check");
189189 }
190190 if (findPasskey(app.db, input.passkey.id)) return err("passkey_taken");
191 const email = input.email.trim();
192 if (email !== "" && !givenEmail(email)) return err("invalid_email");
191193 const created = await createUser(app.db, {
192194 username: input.username,
193 email: input.email,
195 email: email === "" ? noEmail(input.username) : email,
194196 origin: "passkey",
195197 slot: from.slot,
196198 invite: from.invite,
@@ -210,7 +212,8 @@ export async function signUpWithPasskey(
210212 },
211213 });
212214 auditInvite(app, created.value.id);
213 await welcome(app, created.value);
215 // As on the password path: no address given, no link to send.
216 if (email !== "") await welcome(app, created.value);
214217 return created;
215218 }
216219
src/web/pages/signup-passkey.ts +2 −2
@@ -7,7 +7,7 @@
77 */
88
99 import { newChallenge, verifyRegistration } from "../../auth/webauthn.ts";
10 import { plausibleEmail, signUpWithPasskey, signupClosed, HUMAN_CHECK_MESSAGE, SIGNUP_CLOSED_MESSAGE } from "../../actions/users.ts";
10 import { givenEmail, signUpWithPasskey, signupClosed, HUMAN_CHECK_MESSAGE, SIGNUP_CLOSED_MESSAGE } from "../../actions/users.ts";
1111 import { createSession } from "../../core/sessions.ts";
1212 import { getUserByName } from "../../core/users.ts";
1313 import { isSafeName } from "../../git/guard.ts";
@@ -39,7 +39,7 @@ const WORDS: Record<string, string> = {
3939 function problem(ctx: Ctx, username: string, email: string): string | undefined {
4040 if (!isSafeName(username)) return WORDS.invalid_username;
4141 if (!pinnedTo(ctx, username, inviteOf(ctx))) return WORDS.edge_name;
42 if (!plausibleEmail(email)) return WORDS.invalid_email;
42 if (email !== "" && !givenEmail(email)) return WORDS.invalid_email;
4343 if (getUserByName(ctx.app.db, username)) return WORDS.username_taken;
4444 return undefined;
4545 }
src/web/pages/site.ts +1 −1
@@ -369,7 +369,7 @@ function authForm(
369369 () => html`<div class="passkey-on">
370370 <p class="muted small mb">
371371 Or skip the password: a passkey signs you in with a fingerprint,
372 face or device PIN. It needs an email address above.
372 face or device PIN. An email address above is optional.
373373 </p>
374374 <p class="error-note mb" id="passkey-signup-note" role="alert"></p>
375375 <button type="button" id="passkey-signup" class="btn" data-variant="outline">Create account with a passkey</button>
test/signup-passkey.test.ts +15 −1
@@ -10,7 +10,8 @@ import { tmpdir } from "node:os";
1010 import { join } from "node:path";
1111 import { createApp, type App } from "../src/app.ts";
1212 import { signUp } from "../src/actions/users.ts";
13 import { createUser } from "../src/core/users.ts";
13 import { requireVerified } from "../src/actions/verify.ts";
14 import { createUser, getUserByName } from "../src/core/users.ts";
1415 import { setLimit } from "../src/settings/limits.ts";
1516 import { errorText } from "../src/tui/screens/common.ts";
1617 import { serve, type Servers } from "../src/http/server.ts";
@@ -51,6 +52,19 @@ describe("passkey sign-up", () => {
5152 await rm(dataDir, { recursive: true, force: true });
5253 });
5354
55 test("a passkey with no email makes the account, as a password does, and signs in", async () => {
56 const d = await device(origin(), rp());
57 const res = await signUpWith(d, "wren", "");
58 expect(res.status).toBe(201);
59 const set = res.headers.get("set-cookie")!;
60 expect(set).toContain("forge_session=");
61 const user = getUserByName(app.db, "wren")!;
62 expect(user.email).toBe("[email protected]");
63 expect(requireVerified(app, user)).toEqual({ ok: false, error: "unverified" });
64 const mine = await fetch(`${base}/settings/passkeys`, { headers: { cookie: set.split(";")[0]! } });
65 expect(mine.status).toBe(200);
66 });
67
5468 test("a passkey and an email make the account, its key and a session", async () => {
5569 const d = await device(origin(), rp());
5670 const res = await signUpWith(d, "nova", "[email protected]");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs