mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 19:41:25 +02:00
feat(auth): add WebAuthn/Passkey support across all apps
Implements passwordless authentication via passkeys using @simplewebauthn: Backend (mana-core-auth): - New passkeys table in auth schema (credentialId, publicKey, counter, etc.) - PasskeyService with registration/authentication flows and challenge storage - 7 new API endpoints (register, authenticate, list, delete, rename) - createSessionAndTokens helper for non-password auth flows - Security event types for passkey operations Client (shared-auth): - signInWithPasskey() and registerPasskey() with dynamic @simplewebauthn/browser imports - isPasskeyAvailable() browser capability check - Passkey management methods (list, delete, rename) UI (shared-auth-ui): - Passkey button on LoginPage with key icon, shown when browser supports WebAuthn - Divider between passkey and email/password form App integration: - All 19 web app auth stores have isPasskeyAvailable() and signInWithPasskey() - All 19 web app login pages pass passkeyAvailable and onSignInWithPasskey props - rpID=mana.how in production enables cross-app passkey usage (SSO-compatible) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1095202ad9
commit
3091da914e
52 changed files with 1849 additions and 4 deletions
|
|
@ -118,6 +118,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@
|
|||
primaryColor="#0ea5e9"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@
|
|||
primaryColor="#0ea5e9"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -105,6 +105,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
primaryColor="#2563eb"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@
|
|||
primaryColor="#f59e0b"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@
|
|||
primaryColor="#3b82f6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -106,6 +106,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@
|
|||
primaryColor="#0ea5e9"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -101,6 +101,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
primaryColor="#6366f1"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect="/dashboard"
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -93,6 +93,20 @@ export const authStore = {
|
|||
return true;
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const result = await authService.signInWithPasskey();
|
||||
if (result.success) {
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = toManaUser(userData);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
primaryColor="#8b5cf6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect="/decks"
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -107,6 +107,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
primaryColor="#f97316"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -116,6 +116,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@
|
|||
primaryColor="#22C55E"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -109,6 +109,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@
|
|||
primaryColor="#8b5cf6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -116,6 +116,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@
|
|||
primaryColor="#3b82f6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect="/app/gallery"
|
||||
registerPath="/auth/signup"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@
|
|||
primaryColor="#22c55e"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -84,6 +84,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) throw new Error('Auth not initialized');
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
primaryColor="#06b6d4"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,43 @@ export const auth = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@
|
|||
primaryColor="#f97316"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={auth.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => auth.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -113,6 +113,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@
|
|||
primaryColor="#8b5cf6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -117,6 +117,35 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@
|
|||
primaryColor="#10b981"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -114,6 +114,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -129,6 +129,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@
|
|||
primaryColor="#8b5cf6"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,43 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if passkeys are available in this browser
|
||||
*/
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with a passkey
|
||||
*/
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available on server' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@
|
|||
primaryColor="#f59e0b"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
{goto}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue