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); color: var(--eid-primary);
text-decoration: underline; 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(); const accounts = getKnownAccounts();
// No known accounts → generic passkey button // No known accounts → username input + sign-in button
if (accounts.length === 0) { if (accounts.length === 0) {
return ` return `
<button class="login-btn ${sizeClass} ${variantClass}"> <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} ${PASSKEY_ICON}
<span>${this.label}</span> <span>${this.label}</span>
</button> </button>
</div>
`; `;
} }
@ -554,9 +585,23 @@ export class EncryptIDLoginButton extends HTMLElement {
}); });
}); });
} else { } else {
// Login button with scoped username // Username input form (no known accounts)
const loginBtn = this.shadow.querySelector('.login-btn'); const usernameInput = this.shadow.querySelector('.username-input') as HTMLInputElement;
if (loginBtn) { 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', () => { loginBtn.addEventListener('click', () => {
const username = (loginBtn as HTMLElement).dataset.username; const username = (loginBtn as HTMLElement).dataset.username;
this.handleLogin(username); this.handleLogin(username);