fix(auth): show username input on first login before passkey prompt

When no known accounts exist in localStorage, show a username/email
input field instead of immediately triggering the unscoped passkey
picker. User types their username, then gets a scoped passkey prompt
for only that account's credentials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-24 11:41:29 -07:00
parent 724c0e16ba
commit af446938be
1 changed files with 53 additions and 8 deletions

View File

@ -332,6 +332,34 @@ const styles = `
color: var(--eid-primary);
text-decoration: underline;
}
.username-form {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 200px;
}
.username-input {
padding: 10px 14px;
background: var(--eid-bg);
border: 1px solid var(--eid-text-secondary);
border-radius: var(--eid-radius);
color: var(--eid-text);
font-size: 0.95rem;
font-family: inherit;
outline: none;
transition: border-color 0.2s;
}
.username-input:focus {
border-color: var(--eid-primary);
}
.username-input::placeholder {
color: var(--eid-text-secondary);
opacity: 0.7;
}
`;
// ============================================================================
@ -456,13 +484,16 @@ export class EncryptIDLoginButton extends HTMLElement {
const accounts = getKnownAccounts();
// No known accounts → generic passkey button
// No known accounts → username input + sign-in button
if (accounts.length === 0) {
return `
<button class="login-btn ${sizeClass} ${variantClass}">
${PASSKEY_ICON}
<span>${this.label}</span>
</button>
<div class="username-form">
<input class="username-input" type="text" placeholder="Username or email" autocomplete="username webauthn" />
<button class="login-btn ${sizeClass} ${variantClass}" data-action="username-login">
${PASSKEY_ICON}
<span>${this.label}</span>
</button>
</div>
`;
}
@ -554,9 +585,23 @@ export class EncryptIDLoginButton extends HTMLElement {
});
});
} else {
// Login button with scoped username
const loginBtn = this.shadow.querySelector('.login-btn');
if (loginBtn) {
// Username input form (no known accounts)
const usernameInput = this.shadow.querySelector('.username-input') as HTMLInputElement;
const usernameLoginBtn = this.shadow.querySelector('[data-action="username-login"]');
if (usernameInput && usernameLoginBtn) {
const doLogin = () => {
const val = usernameInput.value.trim();
this.handleLogin(val || undefined);
};
usernameLoginBtn.addEventListener('click', doLogin);
usernameInput.addEventListener('keydown', (e) => {
if ((e as KeyboardEvent).key === 'Enter') doLogin();
});
}
// Login button with scoped username (1 known account)
const loginBtn = this.shadow.querySelector('.login-btn:not([data-action])');
if (loginBtn && !usernameInput) {
loginBtn.addEventListener('click', () => {
const username = (loginBtn as HTMLElement).dataset.username;
this.handleLogin(username);