Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions e2e/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ export async function onboard(page, who) {
await expect(onboarding.or(ready)).toBeVisible({ timeout: 90_000 });

if (await onboarding.isVisible()) {
await page.getByTestId('onboarding-user-id').fill(`${who}@example.com`);
await page.getByTestId('onboarding-display-name').fill(who);
await page.getByTestId('onboarding-user-id').fill(who);
await page.getByTestId('onboarding-submit').click();
}

Expand Down
11 changes: 6 additions & 5 deletions e2e/m0-shell.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ test.describe('app shell', () => {
await expect(alice.getByTestId('onboarding-create-warning')).toBeVisible({ timeout: 90_000 });
await expect(alice.getByTestId('recover-identity')).toBeVisible();

// The other half of the same hazard, and the one nobody could guess from the
// screen: the first field becomes the WebAuthn handle, so two people picking
// the same name on one front-desk device means the second replaces the
// first. The hint carries that — tied to the input rather than floating near
// it, because a warning a screen reader never reaches is not a warning.
// The name field used to carry a second hazard: it became the WebAuthn
// handle, so two people picking the same name on one front-desk device meant
// the second replaced the first. The handle is random now, and the hint says
// the opposite — that the name is only a label and may be shared. Either
// way it has to reach a screen reader, so it stays tied to the input rather
// than floating near it.
const hint = alice.getByTestId('onboarding-user-id-hint');

await expect(hint).toBeVisible();
Expand Down
3 changes: 1 addition & 2 deletions e2e/m1-program.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ async function onboard(page) {
await page.goto('/studio/');

await expect(page.getByTestId('onboarding')).toBeVisible(READY);
await page.getByTestId('onboarding-user-id').fill('alice@example.com');
await page.getByTestId('onboarding-display-name').fill('Alice');
await page.getByTestId('onboarding-user-id').fill('Alice');
await page.getByTestId('onboarding-submit').click();

await expect(page.getByTestId('studio-ready')).toBeVisible(READY);
Expand Down
52 changes: 22 additions & 30 deletions src/lib/components/StudioGate.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

let { children } = $props();

let userId = $state('');
let displayName = $state('');
// WebAuthn takes an account name and a display name. Both are labels for the
// credential picker, neither identifies the passkey, so the screen asks once
// and gives the same answer to both.
let name = $state('');

onMount(async () => {
try {
Expand All @@ -33,7 +35,7 @@
async function create(/** @type {SubmitEvent} */ event) {
event.preventDefault();
try {
await createIdentityAndBoot({ userId, displayName });
await createIdentityAndBoot({ userId: name, displayName: name });
} catch {
// surfaced through bootStore
}
Expand Down Expand Up @@ -69,22 +71,20 @@

<form class="mt-4 grid max-w-md gap-3" onsubmit={create}>
<!--
The first field is not a label, whatever it looks like: the provider
builds the WebAuthn user handle out of it, and an authenticator
replaces a credential when the handle matches. Two people creating a
passkey on one front-desk device under the same name means the second
destroys the first, silently.

So the hint says so. It is the only technical sentence left on this
screen and it stays until the handle is random rather than typed —
Le-Space/orbitdb-identity-provider-webauthn-did#45, after which this
field is genuinely just a name and the two can become one.
One field now. It used to be two because the first one was not a
label at all: the provider built the WebAuthn user handle out of it,
and an authenticator replaces a credential whose handle matches, so
two people creating a passkey on one front-desk device under the same
name meant the second destroyed the first. The handle is random since
Le-Space/orbitdb-identity-provider-webauthn-did#45, which leaves a
name that is only a name — and one name is all this screen ever had
to ask for.
-->
<label class="grid gap-1 text-sm">
{m.onboarding_user_id()}
<input
data-testid="onboarding-user-id"
bind:value={userId}
bind:value={name}
required
autocomplete="username"
aria-describedby="user-id-hint"
Expand All @@ -95,20 +95,6 @@
</span>
</label>

<label class="grid gap-1 text-sm">
{m.onboarding_display_name()}
<input
data-testid="onboarding-display-name"
bind:value={displayName}
required
aria-describedby="display-name-hint"
class="rounded-control border p-2"
/>
<span id="display-name-hint" class="text-xs text-muted">
{m.onboarding_display_name_hint()}
</span>
</label>

<button
type="submit"
data-testid="onboarding-submit"
Expand All @@ -125,8 +111,14 @@
new device, and one whose storage was cleared — a new browser, a
wiped profile, a passkey synced from elsewhere. In the second, the
top button is the wrong one, and pressing it does not fail: it makes
a *second* passkey and therefore a second DID, under which the
studio does not know this device and its old passes are unreachable.
a *second* passkey and therefore a second account, which the studio
does not know and has to approve again.

Not a dead end any more, and the warning had to stop saying it was:
accounts are separated per DID rather than overwriting one another
(#82), so what sits under the first passkey stays there and "switch
account" in the footer leads back to it. What is still worth warning
about is the re-approval, not a loss.

A browser will not tell us which situation this is — it never reveals
whether a passkey exists without a gesture, on purpose. So the choice
Expand Down
6 changes: 2 additions & 4 deletions src/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,12 @@
"occupancy_unknown": "Belegung unbekannt",
"onboarding_busy": "Gerät wird eingerichtet …",
"onboarding_create": "Passkey anlegen",
"onboarding_create_warning": "Legt einen neuen Passkey an. Hattet ihr auf diesem Gerät schon einen — nach geleertem Speicher oder in einem anderen Browser —, nehmt den Knopf darunter. Sonst gilt dieses Gerät als ein neues, und was unter dem alten Passkey liegt, ist nicht mehr erreichbar.",
"onboarding_display_name": "Wie du anderen angezeigt wirst",
"onboarding_display_name_hint": "Darf derselbe Name sein.",
"onboarding_create_warning": "Legt einen neuen Passkey an. Hattet ihr auf diesem Gerät schon einen — nach geleertem Speicher oder in einem anderen Browser —, nehmt den Knopf darunter. Sonst sieht das Studio ein neues Gerät und muss es neu freigeben. Was unter dem alten Passkey liegt, bleibt dort und ist über „Konto wechseln“ am Seitenende wieder erreichbar.",
"onboarding_intro": "Kein Konto, kein Passwort. Dieses Gerät legt einen Schlüssel an, der es nie verlässt — daran erkennen dich dein Studio und deine Karten wieder.",
"onboarding_recover": "Mit vorhandenem Passkey fortfahren",
"onboarding_title": "Gerät einrichten",
"onboarding_user_id": "Dein Name oder Spitzname",
"onboarding_user_id_hint": "Keine E-Mail nötig — dein Instagram- oder Telegram-Name tut es genauso. Hat auf diesem Gerät schon jemand denselben Namen benutzt, nimm einen anderen: sonst wird sein Schlüssel ersetzt.",
"onboarding_user_id_hint": "Keine E-Mail nötig — dein Instagram- oder Telegram-Name tut es genauso. Er steht nur dran, wenn du einen Passkey auswählst; zwei Leute hier dürfen denselben nehmen.",
"package_add": "Karte anlegen",
"package_deactivate": "Nicht mehr verkaufen",
"package_id": "Kurzname (z. B. 10er)",
Expand Down
6 changes: 2 additions & 4 deletions src/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,12 @@
"occupancy_unknown": "Occupancy unknown",
"onboarding_busy": "Setting up the device …",
"onboarding_create": "Create passkey",
"onboarding_create_warning": "Creates a new passkey. If this device already had one — after cleared storage, or in a different browser — use the button below instead. Otherwise this counts as a new device, and whatever sits under the old passkey is out of reach.",
"onboarding_display_name": "How others see you",
"onboarding_display_name_hint": "May be the same name.",
"onboarding_create_warning": "Creates a new passkey. If this device already had one — after cleared storage, or in a different browser — use the button below instead. Otherwise the studio sees a new device and has to approve it again. Whatever sits under the old passkey stays there, and “Switch account” at the foot of the page leads back to it.",
"onboarding_intro": "No account, no password. This device creates a key that never leaves it — that is what your studio and your passes recognise you by.",
"onboarding_recover": "Continue with the existing passkey",
"onboarding_title": "Set up this device",
"onboarding_user_id": "Your name or nickname",
"onboarding_user_id_hint": "No e-mail needed — your Instagram or Telegram name does just as well. If somebody on this device has already used the same name, pick another: theirs would be replaced.",
"onboarding_user_id_hint": "No e-mail needed — your Instagram or Telegram name does just as well. It is only how you appear when choosing a passkey; two people here may use the same one.",
"package_add": "Add pass",
"package_deactivate": "Stop selling",
"package_id": "Short name (e.g. ten)",
Expand Down
Loading