@revealui/auth 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +58 -34
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/react/index.d.ts +4 -0
  4. package/dist/react/index.d.ts.map +1 -1
  5. package/dist/react/index.js +2 -0
  6. package/dist/react/useMFA.d.ts +83 -0
  7. package/dist/react/useMFA.d.ts.map +1 -0
  8. package/dist/react/useMFA.js +182 -0
  9. package/dist/react/usePasskey.d.ts +88 -0
  10. package/dist/react/usePasskey.d.ts.map +1 -0
  11. package/dist/react/usePasskey.js +203 -0
  12. package/dist/react/useSession.d.ts.map +1 -1
  13. package/dist/react/useSession.js +16 -5
  14. package/dist/react/useSignIn.d.ts +9 -3
  15. package/dist/react/useSignIn.d.ts.map +1 -1
  16. package/dist/react/useSignIn.js +32 -10
  17. package/dist/react/useSignOut.d.ts.map +1 -1
  18. package/dist/react/useSignUp.d.ts +1 -0
  19. package/dist/react/useSignUp.d.ts.map +1 -1
  20. package/dist/react/useSignUp.js +25 -9
  21. package/dist/server/auth.d.ts +2 -0
  22. package/dist/server/auth.d.ts.map +1 -1
  23. package/dist/server/auth.js +93 -5
  24. package/dist/server/brute-force.d.ts +10 -1
  25. package/dist/server/brute-force.d.ts.map +1 -1
  26. package/dist/server/brute-force.js +46 -23
  27. package/dist/server/errors.d.ts +4 -0
  28. package/dist/server/errors.d.ts.map +1 -1
  29. package/dist/server/errors.js +8 -0
  30. package/dist/server/index.d.ts +17 -6
  31. package/dist/server/index.d.ts.map +1 -1
  32. package/dist/server/index.js +12 -5
  33. package/dist/server/magic-link.d.ts +52 -0
  34. package/dist/server/magic-link.d.ts.map +1 -0
  35. package/dist/server/magic-link.js +111 -0
  36. package/dist/server/mfa.d.ts +87 -0
  37. package/dist/server/mfa.d.ts.map +1 -0
  38. package/dist/server/mfa.js +263 -0
  39. package/dist/server/oauth.d.ts +86 -0
  40. package/dist/server/oauth.d.ts.map +1 -0
  41. package/dist/server/oauth.js +355 -0
  42. package/dist/server/passkey.d.ts +132 -0
  43. package/dist/server/passkey.d.ts.map +1 -0
  44. package/dist/server/passkey.js +257 -0
  45. package/dist/server/password-reset.d.ts +32 -6
  46. package/dist/server/password-reset.d.ts.map +1 -1
  47. package/dist/server/password-reset.js +116 -47
  48. package/dist/server/password-validation.d.ts.map +1 -1
  49. package/dist/server/providers/github.d.ts +14 -0
  50. package/dist/server/providers/github.d.ts.map +1 -0
  51. package/dist/server/providers/github.js +89 -0
  52. package/dist/server/providers/google.d.ts +11 -0
  53. package/dist/server/providers/google.d.ts.map +1 -0
  54. package/dist/server/providers/google.js +69 -0
  55. package/dist/server/providers/vercel.d.ts +11 -0
  56. package/dist/server/providers/vercel.d.ts.map +1 -0
  57. package/dist/server/providers/vercel.js +63 -0
  58. package/dist/server/rate-limit.d.ts +10 -1
  59. package/dist/server/rate-limit.d.ts.map +1 -1
  60. package/dist/server/rate-limit.js +61 -43
  61. package/dist/server/session.d.ts +48 -1
  62. package/dist/server/session.d.ts.map +1 -1
  63. package/dist/server/session.js +126 -7
  64. package/dist/server/signed-cookie.d.ts +32 -0
  65. package/dist/server/signed-cookie.d.ts.map +1 -0
  66. package/dist/server/signed-cookie.js +67 -0
  67. package/dist/server/storage/database.d.ts +10 -1
  68. package/dist/server/storage/database.d.ts.map +1 -1
  69. package/dist/server/storage/database.js +43 -5
  70. package/dist/server/storage/in-memory.d.ts +4 -0
  71. package/dist/server/storage/in-memory.d.ts.map +1 -1
  72. package/dist/server/storage/in-memory.js +16 -6
  73. package/dist/server/storage/index.d.ts +11 -3
  74. package/dist/server/storage/index.d.ts.map +1 -1
  75. package/dist/server/storage/index.js +18 -4
  76. package/dist/server/storage/interface.d.ts +11 -1
  77. package/dist/server/storage/interface.d.ts.map +1 -1
  78. package/dist/server/storage/interface.js +1 -1
  79. package/dist/types.d.ts +23 -8
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js +2 -2
  82. package/dist/utils/database.d.ts.map +1 -1
  83. package/dist/utils/database.js +12 -2
  84. package/dist/utils/token.d.ts +9 -1
  85. package/dist/utils/token.d.ts.map +1 -1
  86. package/dist/utils/token.js +9 -1
  87. package/package.json +26 -8
package/README.md CHANGED
@@ -1,21 +1,17 @@
1
1
  # @revealui/auth
2
2
 
3
- **Status:** 🟡 Active Development | ⚠️ NOT Production Ready
4
-
5
- See [Project Status](../../docs/PROJECT_STATUS.md) for framework readiness.
6
-
7
- Authentication system for RevealUI - database-backed sessions with Better Auth patterns.
8
-
9
- > **⚠️ Security Note:** Auth implementation exists but requires independent security audit before production use.
3
+ Session-based authentication for RevealUI database-backed sessions, rate limiting, brute force protection, and password reset.
10
4
 
11
5
  ## Features
12
6
 
13
- - Database-backed sessions (PostgreSQL/NeonDB)
14
- - Secure token handling (SHA-256 hashing, HTTP-only cookies)
15
- - CSRF protection (SameSite cookies)
16
- - Type-safe (TypeScript)
17
- - Framework agnostic (Next.js, TanStack Start)
18
- - React hooks for client-side usage
7
+ - **Database Sessions** — PostgreSQL/NeonDB-backed sessions with SHA-256 token hashing
8
+ - **Secure Cookies** HTTP-only, SameSite, secure flag, cross-subdomain support
9
+ - **Rate Limiting** Configurable per-endpoint rate limits stored in database
10
+ - **Brute Force Protection** — Progressive lockout on failed sign-in attempts
11
+ - **Password Reset** Token-based password reset flow with email integration
12
+ - **Password Validation** Strength requirements and common password checks
13
+ - **React Hooks** — Client-side session management (`useSession`, `useSignIn`, `useSignOut`)
14
+ - **Framework Agnostic** — Works with Next.js, Hono, and other Node.js frameworks
19
15
 
20
16
  ## Installation
21
17
 
@@ -25,36 +21,34 @@ pnpm add @revealui/auth
25
21
 
26
22
  ## Usage
27
23
 
28
- ### Server-side (Next.js API Routes)
24
+ ### Server-Side
29
25
 
30
26
  ```typescript
31
- import { getSession } from '@revealui/auth/server'
32
- import { type NextRequest, NextResponse } from 'next/server'
27
+ import { getSession, signIn, signOut, createSession } from '@revealui/auth/server'
33
28
 
34
- export async function GET(request: NextRequest) {
35
- const session = await getSession(request.headers)
36
-
37
- if (!session) {
38
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
39
- }
29
+ // Validate session from request headers
30
+ const session = await getSession(request.headers)
40
31
 
41
- return NextResponse.json({ user: session.user })
42
- }
32
+ // Sign in with email/password
33
+ const result = await signIn({ email, password })
34
+
35
+ // Sign out (invalidate session)
36
+ await signOut(sessionToken)
43
37
  ```
44
38
 
45
- ### Client-side (React Hooks)
39
+ ### Client-Side (React)
46
40
 
47
41
  ```typescript
48
42
  'use client'
49
43
  import { useSession, useSignIn, useSignOut } from '@revealui/auth/react'
50
44
 
51
- function MyComponent() {
45
+ function AuthComponent() {
52
46
  const { data: session, isLoading } = useSession()
53
47
  const { signIn } = useSignIn()
54
48
  const { signOut } = useSignOut()
55
49
 
56
50
  if (isLoading) return <div>Loading...</div>
57
- if (!session) return <div>Not signed in</div>
51
+ if (!session) return <button onClick={() => signIn({ email, password })}>Sign In</button>
58
52
 
59
53
  return (
60
54
  <div>
@@ -65,13 +59,43 @@ function MyComponent() {
65
59
  }
66
60
  ```
67
61
 
68
- ## API Routes
62
+ ## Exports
63
+
64
+ | Subpath | Contents |
65
+ |---------|----------|
66
+ | `@revealui/auth/server` | Server-side auth (session CRUD, sign in/out, rate limiting, brute force) |
67
+ | `@revealui/auth/client` | Client-side utilities |
68
+ | `@revealui/auth/react` | React hooks (`useSession`, `useSignIn`, `useSignOut`) |
69
+
70
+ ## Security
71
+
72
+ - Passwords hashed with bcrypt
73
+ - Session tokens hashed with SHA-256 before storage
74
+ - HTTP-only cookies prevent XSS token theft
75
+ - SameSite cookie attribute prevents CSRF
76
+ - Rate limiting prevents abuse (configurable per endpoint)
77
+ - Brute force protection with progressive lockout
78
+ - Cookie domain supports cross-subdomain auth (e.g. `.revealui.com`)
79
+
80
+ ## Development
81
+
82
+ ```bash
83
+ # Build
84
+ pnpm build
85
+
86
+ # Type check
87
+ pnpm typecheck
88
+
89
+ # Run tests
90
+ pnpm test
91
+ ```
92
+
93
+ ## Related
69
94
 
70
- - `GET /api/auth/session` - Get current session
71
- - `POST /api/auth/sign-in` - Sign in with email/password
72
- - `POST /api/auth/sign-up` - Create new account
73
- - `POST /api/auth/sign-out` - Sign out
95
+ - [Core Package](../core/README.md) — CMS engine (uses auth for access control)
96
+ - [DB Package](../db/README.md) Database schema (sessions, users, rate_limits tables)
97
+ - [Auth Guide](../../docs/AUTH.md) Architecture, usage patterns, and security design
74
98
 
75
- ## Documentation
99
+ ## License
76
100
 
77
- See [Auth System Design](../../docs/reference/auth/AUTH_SYSTEM_DESIGN.md) for comprehensive documentation.
101
+ MIT
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAGjC,YAAY,EACV,WAAW,EACX,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,IAAI,GACL,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAEjC,cAAc,mBAAmB,CAAC;AAGlC,YAAY,EACV,WAAW,EACX,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,IAAI,GACL,MAAM,YAAY,CAAC"}
@@ -4,6 +4,10 @@
4
4
  * Client-side React hooks for authentication.
5
5
  * Inspired by Better Auth and TanStack Start patterns.
6
6
  */
7
+ export type { MFASetupData, UseMFASetupResult, UseMFAVerifyResult, } from './useMFA.js';
8
+ export { useMFASetup, useMFAVerify } from './useMFA.js';
9
+ export type { PasskeyRegisterOptions, PasskeyRegisterResult, UsePasskeyRegisterResult, UsePasskeySignInResult, } from './usePasskey.js';
10
+ export { usePasskeyRegister, usePasskeySignIn } from './usePasskey.js';
7
11
  export type { UseSessionResult } from './useSession.js';
8
12
  export { useSession } from './useSession.js';
9
13
  export type { SignInInput, UseSignInResult } from './useSignIn.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -4,6 +4,8 @@
4
4
  * Client-side React hooks for authentication.
5
5
  * Inspired by Better Auth and TanStack Start patterns.
6
6
  */
7
+ export { useMFASetup, useMFAVerify } from './useMFA.js';
8
+ export { usePasskeyRegister, usePasskeySignIn } from './usePasskey.js';
7
9
  export { useSession } from './useSession.js';
8
10
  export { useSignIn } from './useSignIn.js';
9
11
  export { useSignOut } from './useSignOut.js';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * MFA Hooks
3
+ *
4
+ * React hooks for Multi-Factor Authentication setup and verification.
5
+ */
6
+ /**
7
+ * MFA setup data returned when initiating TOTP setup.
8
+ */
9
+ export interface MFASetupData {
10
+ /** Base32-encoded TOTP secret */
11
+ secret: string;
12
+ /** otpauth:// URI for QR code generation */
13
+ uri: string;
14
+ /** One-time backup codes for account recovery */
15
+ backupCodes: string[];
16
+ }
17
+ export interface UseMFASetupResult {
18
+ /** Initiate MFA setup — returns secret, QR URI, and backup codes */
19
+ setup: () => Promise<MFASetupData | null>;
20
+ /** Verify a TOTP code to confirm setup */
21
+ verifySetup: (code: string) => Promise<boolean>;
22
+ isLoading: boolean;
23
+ error: string | null;
24
+ }
25
+ /**
26
+ * Hook for MFA setup on the security settings page.
27
+ *
28
+ * @returns Setup and verify functions, loading state, and error
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function SecuritySettings() {
33
+ * const { setup, verifySetup, isLoading, error } = useMFASetup();
34
+ *
35
+ * const handleEnable = async () => {
36
+ * const data = await setup();
37
+ * if (data) {
38
+ * // Show QR code using data.uri, display backup codes
39
+ * }
40
+ * };
41
+ *
42
+ * const handleVerify = async (code: string) => {
43
+ * const success = await verifySetup(code);
44
+ * if (success) {
45
+ * // MFA is now enabled
46
+ * }
47
+ * };
48
+ * }
49
+ * ```
50
+ */
51
+ export declare function useMFASetup(): UseMFASetupResult;
52
+ export interface UseMFAVerifyResult {
53
+ /** Verify a TOTP code during login */
54
+ verify: (code: string) => Promise<boolean>;
55
+ /** Verify a backup code during login */
56
+ verifyBackupCode: (code: string) => Promise<boolean>;
57
+ isLoading: boolean;
58
+ error: string | null;
59
+ }
60
+ /**
61
+ * Hook for MFA verification during the login flow.
62
+ *
63
+ * After sign-in returns `requiresMfa: true`, redirect to the MFA page
64
+ * and use this hook to complete authentication.
65
+ *
66
+ * @returns Verify functions, loading state, and error
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * function MFAPage() {
71
+ * const { verify, verifyBackupCode, isLoading, error } = useMFAVerify();
72
+ *
73
+ * const handleSubmit = async (code: string) => {
74
+ * const success = await verify(code);
75
+ * if (success) {
76
+ * router.push('/admin');
77
+ * }
78
+ * };
79
+ * }
80
+ * ```
81
+ */
82
+ export declare function useMFAVerify(): UseMFAVerifyResult;
83
+ //# sourceMappingURL=useMFA.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMFA.d.ts","sourceRoot":"","sources":["../../src/react/useMFA.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,KAAK,EAAE,MAAM,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC1C,0CAA0C;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,IAAI,iBAAiB,CAqE/C;AAED,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,wCAAwC;IACxC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,IAAI,kBAAkB,CAsEjD"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * MFA Hooks
3
+ *
4
+ * React hooks for Multi-Factor Authentication setup and verification.
5
+ */
6
+ 'use client';
7
+ import { useState } from 'react';
8
+ /**
9
+ * Hook for MFA setup on the security settings page.
10
+ *
11
+ * @returns Setup and verify functions, loading state, and error
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function SecuritySettings() {
16
+ * const { setup, verifySetup, isLoading, error } = useMFASetup();
17
+ *
18
+ * const handleEnable = async () => {
19
+ * const data = await setup();
20
+ * if (data) {
21
+ * // Show QR code using data.uri, display backup codes
22
+ * }
23
+ * };
24
+ *
25
+ * const handleVerify = async (code: string) => {
26
+ * const success = await verifySetup(code);
27
+ * if (success) {
28
+ * // MFA is now enabled
29
+ * }
30
+ * };
31
+ * }
32
+ * ```
33
+ */
34
+ export function useMFASetup() {
35
+ const [isLoading, setIsLoading] = useState(false);
36
+ const [error, setError] = useState(null);
37
+ const setup = async () => {
38
+ try {
39
+ setIsLoading(true);
40
+ setError(null);
41
+ const response = await fetch('/api/auth/mfa/setup', {
42
+ method: 'POST',
43
+ credentials: 'include',
44
+ });
45
+ const json = await response.json();
46
+ if (!response.ok) {
47
+ const errorData = json;
48
+ setError(errorData.error ?? 'Failed to set up MFA');
49
+ return null;
50
+ }
51
+ const data = json;
52
+ return data;
53
+ }
54
+ catch (err) {
55
+ const message = err instanceof Error ? err.message : String(err);
56
+ setError(message);
57
+ return null;
58
+ }
59
+ finally {
60
+ setIsLoading(false);
61
+ }
62
+ };
63
+ const verifySetup = async (code) => {
64
+ try {
65
+ setIsLoading(true);
66
+ setError(null);
67
+ const response = await fetch('/api/auth/mfa/verify-setup', {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ credentials: 'include',
71
+ body: JSON.stringify({ code }),
72
+ });
73
+ const json = await response.json();
74
+ if (!response.ok) {
75
+ const errorData = json;
76
+ setError(errorData.error ?? 'Failed to verify MFA code');
77
+ return false;
78
+ }
79
+ return true;
80
+ }
81
+ catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ setError(message);
84
+ return false;
85
+ }
86
+ finally {
87
+ setIsLoading(false);
88
+ }
89
+ };
90
+ return {
91
+ setup,
92
+ verifySetup,
93
+ isLoading,
94
+ error,
95
+ };
96
+ }
97
+ /**
98
+ * Hook for MFA verification during the login flow.
99
+ *
100
+ * After sign-in returns `requiresMfa: true`, redirect to the MFA page
101
+ * and use this hook to complete authentication.
102
+ *
103
+ * @returns Verify functions, loading state, and error
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * function MFAPage() {
108
+ * const { verify, verifyBackupCode, isLoading, error } = useMFAVerify();
109
+ *
110
+ * const handleSubmit = async (code: string) => {
111
+ * const success = await verify(code);
112
+ * if (success) {
113
+ * router.push('/admin');
114
+ * }
115
+ * };
116
+ * }
117
+ * ```
118
+ */
119
+ export function useMFAVerify() {
120
+ const [isLoading, setIsLoading] = useState(false);
121
+ const [error, setError] = useState(null);
122
+ const verify = async (code) => {
123
+ try {
124
+ setIsLoading(true);
125
+ setError(null);
126
+ const response = await fetch('/api/auth/mfa/verify', {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ credentials: 'include',
130
+ body: JSON.stringify({ code }),
131
+ });
132
+ const json = await response.json();
133
+ if (!response.ok) {
134
+ const errorData = json;
135
+ setError(errorData.error ?? 'Invalid verification code');
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+ catch (err) {
141
+ const message = err instanceof Error ? err.message : String(err);
142
+ setError(message);
143
+ return false;
144
+ }
145
+ finally {
146
+ setIsLoading(false);
147
+ }
148
+ };
149
+ const verifyBackupCode = async (code) => {
150
+ try {
151
+ setIsLoading(true);
152
+ setError(null);
153
+ const response = await fetch('/api/auth/mfa/backup', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ credentials: 'include',
157
+ body: JSON.stringify({ code }),
158
+ });
159
+ const json = await response.json();
160
+ if (!response.ok) {
161
+ const errorData = json;
162
+ setError(errorData.error ?? 'Invalid backup code');
163
+ return false;
164
+ }
165
+ return true;
166
+ }
167
+ catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ setError(message);
170
+ return false;
171
+ }
172
+ finally {
173
+ setIsLoading(false);
174
+ }
175
+ };
176
+ return {
177
+ verify,
178
+ verifyBackupCode,
179
+ isLoading,
180
+ error,
181
+ };
182
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Passkey Hooks
3
+ *
4
+ * React hooks for WebAuthn passkey registration and authentication.
5
+ * Uses dynamic imports for @simplewebauthn/browser to avoid SSR issues.
6
+ */
7
+ export interface PasskeyRegisterOptions {
8
+ /** Email for passkey registration (sign-up flow) */
9
+ email?: string;
10
+ /** Display name for the credential */
11
+ name?: string;
12
+ /** Human-readable device name (e.g., "MacBook Pro") */
13
+ deviceName?: string;
14
+ }
15
+ export interface PasskeyRegisterResult {
16
+ /** Backup codes returned during sign-up flow */
17
+ backupCodes?: string[];
18
+ }
19
+ export interface UsePasskeyRegisterResult {
20
+ /** Register a new passkey credential */
21
+ register: (options?: PasskeyRegisterOptions) => Promise<PasskeyRegisterResult | null>;
22
+ isLoading: boolean;
23
+ error: string | null;
24
+ /** Whether the browser supports WebAuthn */
25
+ supported: boolean;
26
+ }
27
+ /**
28
+ * Hook for passkey registration (sign-up and security settings).
29
+ *
30
+ * @returns Register function, loading state, error, and browser support flag
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * function AddPasskey() {
35
+ * const { register, isLoading, error, supported } = usePasskeyRegister();
36
+ *
37
+ * if (!supported) return <p>Passkeys are not supported in this browser.</p>;
38
+ *
39
+ * const handleAdd = async () => {
40
+ * const result = await register({ deviceName: 'My Laptop' });
41
+ * if (result) {
42
+ * // Passkey registered successfully
43
+ * }
44
+ * };
45
+ * }
46
+ * ```
47
+ */
48
+ export declare function usePasskeyRegister(): UsePasskeyRegisterResult;
49
+ export interface UsePasskeySignInResult {
50
+ /** Authenticate with a passkey */
51
+ signIn: () => Promise<boolean>;
52
+ isLoading: boolean;
53
+ error: string | null;
54
+ /** Whether the browser supports WebAuthn */
55
+ supported: boolean;
56
+ }
57
+ /**
58
+ * Hook for passkey authentication (login page).
59
+ *
60
+ * @returns Sign-in function, loading state, error, and browser support flag
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * function LoginPage() {
65
+ * const { signIn, isLoading, error, supported } = usePasskeySignIn();
66
+ *
67
+ * const handlePasskeyLogin = async () => {
68
+ * const success = await signIn();
69
+ * if (success) {
70
+ * router.push('/admin');
71
+ * }
72
+ * };
73
+ *
74
+ * return (
75
+ * <>
76
+ * {supported && (
77
+ * <button onClick={handlePasskeyLogin} disabled={isLoading}>
78
+ * Sign in with Passkey
79
+ * </button>
80
+ * )}
81
+ * {error && <p>{error}</p>}
82
+ * </>
83
+ * );
84
+ * }
85
+ * ```
86
+ */
87
+ export declare function usePasskeySignIn(): UsePasskeySignInResult;
88
+ //# sourceMappingURL=usePasskey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePasskey.d.ts","sourceRoot":"","sources":["../../src/react/usePasskey.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,sBAAsB;IACrC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtF,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4CAA4C;IAC5C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,IAAI,wBAAwB,CA0F7D;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4CAA4C;IAC5C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,IAAI,sBAAsB,CA8EzD"}