mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 20:39:39 +02:00
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:
parent
3c91691d26
commit
ff7dc5d875
4 changed files with 35 additions and 7 deletions
|
|
@ -436,9 +436,14 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
* Sign in with a passkey.
|
||||
*
|
||||
* Pass `{ conditional: true }` to use the WebAuthn Conditional UI flow,
|
||||
* where the browser surfaces passkeys directly inside the email autofill
|
||||
* dropdown instead of opening a modal. The host MUST verify
|
||||
* `PublicKeyCredential.isConditionalMediationAvailable()` first.
|
||||
*/
|
||||
async signInWithPasskey(): Promise<AuthResult> {
|
||||
async signInWithPasskey(options: { conditional?: boolean } = {}): Promise<AuthResult> {
|
||||
try {
|
||||
const { startAuthentication } = await import('@simplewebauthn/browser');
|
||||
const storage = getStorageAdapter();
|
||||
|
|
@ -454,10 +459,13 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
return { success: false, error: err.message || 'Failed to get authentication options' };
|
||||
}
|
||||
|
||||
const { options, challengeId } = await optionsRes.json();
|
||||
const { options: webauthnOptions, challengeId } = await optionsRes.json();
|
||||
|
||||
// Step 2: Authenticate via browser WebAuthn API
|
||||
const credential = await startAuthentication({ optionsJSON: options });
|
||||
const credential = await startAuthentication({
|
||||
optionsJSON: webauthnOptions,
|
||||
useBrowserAutofill: options.conditional === true,
|
||||
});
|
||||
|
||||
// Step 3: Send credential to server for verification
|
||||
const verifyRes = await fetch(`${baseUrl}${endpoints.passkeyAuthVerify}`, {
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ export interface AuthServiceInterface {
|
|||
// Passkeys
|
||||
isPasskeyAvailable(): boolean;
|
||||
registerPasskey(friendlyName?: string): Promise<AuthResult>;
|
||||
signInWithPasskey(): Promise<AuthResult>;
|
||||
signInWithPasskey(options?: { conditional?: boolean }): Promise<AuthResult>;
|
||||
listPasskeys(): Promise<any[]>;
|
||||
deletePasskey(passkeyId: string): Promise<AuthResult>;
|
||||
renamePasskey(passkeyId: string, friendlyName: string): Promise<AuthResult>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue