feat(auth): structured error codes + conditional passkey UI

- Add AuthErrorCode union and typed twoFactorRedirect/retryAfter fields on AuthResult so the frontend can branch on stable codes instead of locale-dependent error strings.
- Extend signInWithPasskey with an optional { conditional } flag, threaded through to @simplewebauthn/browser via useBrowserAutofill, so hosts can opt into WebAuthn Conditional UI (passkey suggestions inline in the email autofill dropdown).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-08 12:40:51 +02:00
parent 3c91691d26
commit ff7dc5d875
4 changed files with 35 additions and 7 deletions

View file

@ -186,11 +186,11 @@ export function createManaAuthStore(config: ManaAuthStoreConfig = {}) {
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
async signInWithPasskey(options?: { conditional?: boolean }) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.signInWithPasskey();
const result = await authService.signInWithPasskey(options);
if (!result.success)
return { success: false, error: result.error || 'Passkey authentication failed' };
user = await authService.getUserFromToken();

View file

@ -35,13 +35,33 @@ export interface AuthServiceInterface {
forgotPassword(email: string): Promise<AuthResult>;
}
/**
* Structured error code for an auth operation. Frontend should branch on
* this rather than parsing the human-readable `error` string, which is
* locale-dependent.
*/
export type AuthErrorCode =
| 'INVALID_CREDENTIALS'
| 'EMAIL_NOT_VERIFIED'
| 'RATE_LIMITED'
| 'ACCOUNT_LOCKED'
| 'NETWORK_ERROR'
| 'UNKNOWN';
/**
* Result from auth operations
*/
export interface AuthResult {
success: boolean;
/** Human-readable, possibly localized error message */
error?: string;
/** Stable, locale-independent error code for branching */
errorCode?: AuthErrorCode;
needsVerification?: boolean;
/** Set when sign-in succeeded but a 2FA challenge must be completed */
twoFactorRedirect?: boolean;
/** Seconds until the user may retry, set on RATE_LIMITED / ACCOUNT_LOCKED */
retryAfter?: number;
}
/**