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:
parent
724c0e16ba
commit
af446938be
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue