@choiceform/shared-auth 0.1.18 → 0.1.20
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.
- package/README.md +370 -139
- package/dist/core.d.ts +109 -66
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/init.d.ts +109 -66
- package/dist/init.d.ts.map +1 -1
- package/dist/lib/auth-client.d.ts +108 -66
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/referral-service.d.ts +74 -0
- package/dist/services/referral-service.d.ts.map +1 -0
- package/dist/services/referral-service.js +67 -0
- package/dist/store/utils.d.ts +1 -1
- package/dist/store/utils.d.ts.map +1 -1
- package/dist/types/user.d.ts +4 -0
- package/dist/types/user.d.ts.map +1 -1
- package/dist/utils/user-mapper.d.ts.map +1 -1
- package/dist/utils/user-mapper.js +2 -0
- package/package.json +11 -12
- package/dist/components/auth-sync.d.ts +0 -27
- package/dist/components/auth-sync.d.ts.map +0 -1
- package/dist/components/auth-sync.js +0 -117
- package/dist/components/protected-route.d.ts +0 -18
- package/dist/components/protected-route.d.ts.map +0 -1
- package/dist/components/protected-route.js +0 -34
- package/dist/components/sign-in-page.d.ts +0 -21
- package/dist/components/sign-in-page.d.ts.map +0 -1
- package/dist/components/sign-in-page.js +0 -31
package/README.md
CHANGED
|
@@ -1,55 +1,140 @@
|
|
|
1
1
|
# @choiceform/shared-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A shared authentication library based on Better Auth + Legend State.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Architecture
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
-
│
|
|
10
|
-
│
|
|
9
|
+
│ createAuth() / initAuth() │
|
|
10
|
+
│ (core.ts) │
|
|
11
11
|
├─────────────────────────────────────────────────────────────┤
|
|
12
|
-
│
|
|
13
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
14
|
-
│ │ Store │ │ Service │ │ API │ │
|
|
15
|
-
│ │ Layer │ │ Layer │ │ Layer │ │
|
|
16
|
-
│ │ │ │ │ │ │ │
|
|
17
|
-
│ │authStore │ │authServ. │ │apiClient │ │useAuthSync
|
|
18
|
-
│ │storeAct. │ │callback │ │authApi │ │useProtected │
|
|
19
|
-
│ │computed │ │companion │ │orgApi │ │
|
|
20
|
-
│ └──────────┘ └──────────┘ └──────────┘
|
|
21
|
-
│
|
|
12
|
+
│ │
|
|
13
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
|
|
14
|
+
│ │ Store │ │ Service │ │ API │ │ Hooks │ │
|
|
15
|
+
│ │ Layer │ │ Layer │ │ Layer │ │ Layer │ │
|
|
16
|
+
│ │ │ │ │ │ │ │ │ │
|
|
17
|
+
│ │authStore │ │authServ. │ │apiClient │ │useAuthSync │ │
|
|
18
|
+
│ │storeAct. │ │callback │ │authApi │ │useProtected │ │
|
|
19
|
+
│ │computed │ │companion │ │orgApi │ │useEmailVer. │ │
|
|
20
|
+
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
|
|
21
|
+
│ │
|
|
22
22
|
└─────────────────────────────────────────────────────────────┘
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Four-Layer Architecture
|
|
26
26
|
|
|
27
|
-
|
|
|
28
|
-
|
|
29
|
-
| **Store Layer** |
|
|
30
|
-
| **Service Layer** |
|
|
31
|
-
| **API Layer** | HTTP
|
|
32
|
-
| **Hooks Layer** | React
|
|
27
|
+
| Layer | Responsibility | Files |
|
|
28
|
+
|-------|----------------|-------|
|
|
29
|
+
| **Store Layer** | Reactive state management | `store/state.ts`, `store/actions.ts`, `store/computed.ts`, `store/utils.ts` |
|
|
30
|
+
| **Service Layer** | Business logic | `services/auth-service.ts`, `services/callback-service.ts`, `services/companion-team.ts` |
|
|
31
|
+
| **API Layer** | HTTP requests | `api/client.ts`, `api/auth-api.ts`, `api/organization-api.ts`, `api/team-api.ts` |
|
|
32
|
+
| **Hooks Layer** | React-specific | `hooks/use-auth-sync.ts`, `hooks/use-protected-route.ts`, `hooks/use-email-verification.ts` |
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @choiceform/shared-auth
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Peer Dependencies
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"@legendapp/state": "v3.0.0-beta.30",
|
|
45
|
+
"better-auth": "^1.4.4",
|
|
46
|
+
"react": ">=18.0.0",
|
|
47
|
+
"react-dom": ">=18.0.0"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
### Option 1: initAuth (Recommended)
|
|
54
|
+
|
|
55
|
+
Quick initialization with default configuration, automatically includes `magicLinkClient` and `organizationClient` plugins:
|
|
35
56
|
|
|
36
57
|
```typescript
|
|
37
58
|
import { initAuth } from "@choiceform/shared-auth"
|
|
38
59
|
|
|
39
|
-
// 初始化
|
|
40
60
|
const auth = initAuth({
|
|
41
61
|
baseURL: "https://api.example.com",
|
|
62
|
+
// Optional configuration
|
|
63
|
+
tokenStorageKey: "auth-token", // localStorage key, defaults to "auth-token"
|
|
64
|
+
skipTokenCleanupOnError: false, // Set to true for development
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export { auth }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Option 2: createAuth (Custom Configuration)
|
|
71
|
+
|
|
72
|
+
Use when you need custom Better Auth plugins:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { createAuth } from "@choiceform/shared-auth"
|
|
76
|
+
import { magicLinkClient, organizationClient } from "better-auth/client/plugins"
|
|
77
|
+
|
|
78
|
+
const auth = createAuth({
|
|
79
|
+
baseURL: "https://api.example.com",
|
|
80
|
+
plugins: [
|
|
81
|
+
magicLinkClient(),
|
|
82
|
+
organizationClient({ teams: { enabled: true } }),
|
|
83
|
+
// Other plugins...
|
|
84
|
+
],
|
|
85
|
+
tokenStorageKey: "auth-token",
|
|
42
86
|
})
|
|
43
87
|
|
|
44
|
-
// 导出供全局使用
|
|
45
88
|
export { auth }
|
|
46
89
|
```
|
|
47
90
|
|
|
48
|
-
##
|
|
91
|
+
## AuthInstance API
|
|
92
|
+
|
|
93
|
+
The instance returned by `createAuth()` / `initAuth()` includes:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const auth = initAuth({ baseURL: "..." })
|
|
97
|
+
|
|
98
|
+
// API Layer
|
|
99
|
+
auth.apiClient // HTTP client
|
|
100
|
+
auth.authApi // Auth API
|
|
101
|
+
auth.organizationApi // Organization API
|
|
102
|
+
auth.teamApi // Team API
|
|
103
|
+
|
|
104
|
+
// Store Layer
|
|
105
|
+
auth.authStore // Legend State Observable
|
|
106
|
+
auth.authComputed // Computed properties
|
|
107
|
+
auth.tokenStorage // Token storage
|
|
108
|
+
auth.storeActions // State actions
|
|
109
|
+
|
|
110
|
+
// Service Layer
|
|
111
|
+
auth.authService // Auth business logic
|
|
112
|
+
|
|
113
|
+
// Active State Management
|
|
114
|
+
auth.setActiveOrganization(request)
|
|
115
|
+
auth.setActiveTeam(request)
|
|
116
|
+
auth.setActiveOrganizationAndTeam(orgId, teamId)
|
|
117
|
+
|
|
118
|
+
// Shortcut Methods
|
|
119
|
+
auth.getCurrentUser() // Get current user
|
|
120
|
+
auth.getCurrentUserId() // Get current user ID
|
|
121
|
+
auth.isAuthenticated() // Check if authenticated
|
|
122
|
+
auth.isLoading() // Check if loading
|
|
123
|
+
auth.isLoaded() // Check if loaded
|
|
124
|
+
auth.getAuthToken() // Get token
|
|
125
|
+
auth.getAuthHeaders() // Get auth headers
|
|
126
|
+
auth.waitForAuth() // Wait for auth to complete
|
|
127
|
+
auth.userManager // User manager
|
|
128
|
+
|
|
129
|
+
// Better Auth Client (Advanced)
|
|
130
|
+
auth.authClient // Raw Better Auth client
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Hooks (Recommended)
|
|
49
134
|
|
|
50
135
|
### useAuthSync
|
|
51
136
|
|
|
52
|
-
|
|
137
|
+
Sync authentication state, replaces manual Better Auth session sync:
|
|
53
138
|
|
|
54
139
|
```typescript
|
|
55
140
|
import { useAuthSync } from "@choiceform/shared-auth"
|
|
@@ -71,7 +156,7 @@ function App() {
|
|
|
71
156
|
|
|
72
157
|
### useProtectedRoute
|
|
73
158
|
|
|
74
|
-
|
|
159
|
+
Route protection, checks authentication status and email verification:
|
|
75
160
|
|
|
76
161
|
```typescript
|
|
77
162
|
import { useProtectedRoute } from "@choiceform/shared-auth"
|
|
@@ -103,7 +188,7 @@ function ProtectedRoute({ children }) {
|
|
|
103
188
|
|
|
104
189
|
### useEmailVerification
|
|
105
190
|
|
|
106
|
-
|
|
191
|
+
Email verification flow:
|
|
107
192
|
|
|
108
193
|
```typescript
|
|
109
194
|
import { useEmailVerification } from "@choiceform/shared-auth"
|
|
@@ -122,21 +207,21 @@ function VerifyEmailPage({ email, lang }) {
|
|
|
122
207
|
email,
|
|
123
208
|
lang,
|
|
124
209
|
onRedirect: (path) => navigate(path),
|
|
125
|
-
onSendSuccess: (email) => toast.success(
|
|
126
|
-
onSendError: () => toast.error('
|
|
210
|
+
onSendSuccess: (email) => toast.success(`Verification email sent to ${email}`),
|
|
211
|
+
onSendError: () => toast.error('Failed to send'),
|
|
127
212
|
onAlreadyVerified: () => navigate('/community')
|
|
128
213
|
})
|
|
129
214
|
|
|
130
215
|
return (
|
|
131
216
|
<div>
|
|
132
217
|
{isAlreadyVerified ? (
|
|
133
|
-
<p
|
|
218
|
+
<p>Email verified, redirecting...</p>
|
|
134
219
|
) : (
|
|
135
220
|
<>
|
|
136
221
|
<button onClick={resendVerification} disabled={isLoading || isCountingDown}>
|
|
137
|
-
{isCountingDown ?
|
|
222
|
+
{isCountingDown ? `Retry in ${countdown}s` : 'Resend'}
|
|
138
223
|
</button>
|
|
139
|
-
<button onClick={changeEmail}
|
|
224
|
+
<button onClick={changeEmail}>Use different email</button>
|
|
140
225
|
</>
|
|
141
226
|
)}
|
|
142
227
|
</div>
|
|
@@ -144,107 +229,95 @@ function VerifyEmailPage({ email, lang }) {
|
|
|
144
229
|
}
|
|
145
230
|
```
|
|
146
231
|
|
|
147
|
-
|
|
232
|
+
### useAuthInit
|
|
148
233
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
处理各种认证回调(OAuth、邮箱验证、删除用户等):
|
|
234
|
+
Initialize authentication state:
|
|
152
235
|
|
|
153
236
|
```typescript
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
const callbackService = createCallbackService(auth, {
|
|
157
|
-
lang: 'us',
|
|
158
|
-
defaultRedirect: '/',
|
|
159
|
-
signInPath: '/sign-in',
|
|
160
|
-
linkExpiredPath: '/auth/link-expired',
|
|
161
|
-
deleteSuccessPath: '/auth/delete-success',
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// 处理 OAuth 回调
|
|
165
|
-
const result = await callbackService.handleOAuthCallback(token, isNewUser)
|
|
237
|
+
import { useAuthInit, initializeAuth } from "@choiceform/shared-auth"
|
|
166
238
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
239
|
+
// Hook approach
|
|
240
|
+
function App() {
|
|
241
|
+
useAuthInit(auth)
|
|
242
|
+
return <YourApp />
|
|
243
|
+
}
|
|
172
244
|
|
|
173
|
-
//
|
|
174
|
-
|
|
245
|
+
// Or manual call
|
|
246
|
+
await initializeAuth(auth)
|
|
175
247
|
```
|
|
176
248
|
|
|
249
|
+
## Service Layer
|
|
250
|
+
|
|
177
251
|
### authService
|
|
178
252
|
|
|
179
|
-
|
|
253
|
+
Auth business logic wrapper:
|
|
180
254
|
|
|
181
255
|
```typescript
|
|
182
|
-
// OAuth
|
|
256
|
+
// OAuth sign in
|
|
183
257
|
await auth.authService.signInWithOAuth("github", callbackURL, newUserCallbackURL, errorCallbackURL)
|
|
184
258
|
|
|
185
|
-
// Magic Link
|
|
259
|
+
// Magic Link sign in
|
|
186
260
|
await auth.authService.signInWithMagicLink(email, callbackURL, name, newUserCallbackURL)
|
|
187
261
|
|
|
188
|
-
//
|
|
262
|
+
// Email/password sign in
|
|
189
263
|
const result = await auth.authService.signInWithEmail(email, password)
|
|
190
264
|
|
|
191
|
-
//
|
|
265
|
+
// Email/password sign up
|
|
192
266
|
const result = await auth.authService.signUpWithEmail(email, password, name, callbackURL)
|
|
193
267
|
|
|
194
|
-
//
|
|
268
|
+
// Sign out
|
|
195
269
|
await auth.authService.signOut("/sign-in")
|
|
196
270
|
|
|
197
|
-
//
|
|
271
|
+
// Delete account
|
|
198
272
|
await auth.authService.deleteUser(callbackURL, password)
|
|
199
273
|
|
|
200
|
-
//
|
|
274
|
+
// Fetch session with token
|
|
201
275
|
await auth.authService.fetchAndSetSession(token)
|
|
202
276
|
```
|
|
203
277
|
|
|
204
|
-
|
|
278
|
+
### callbackService
|
|
279
|
+
|
|
280
|
+
Handle various auth callbacks (OAuth, email verification, user deletion, etc.):
|
|
205
281
|
|
|
206
282
|
```typescript
|
|
207
|
-
import {
|
|
208
|
-
// 验证
|
|
209
|
-
isValidEmail,
|
|
210
|
-
|
|
211
|
-
// 错误解析
|
|
212
|
-
parseAuthError,
|
|
213
|
-
isTokenExpiredError,
|
|
214
|
-
AUTH_ERROR_CODES,
|
|
215
|
-
|
|
216
|
-
// URL 工具
|
|
217
|
-
getNameFromEmail,
|
|
218
|
-
buildAuthUrl,
|
|
219
|
-
buildAuthPath,
|
|
220
|
-
clearAuthParams,
|
|
221
|
-
|
|
222
|
-
// 用户映射
|
|
223
|
-
extractSessionUser,
|
|
224
|
-
} from "@choiceform/shared-auth"
|
|
283
|
+
import { createCallbackService } from "@choiceform/shared-auth"
|
|
225
284
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
285
|
+
const callbackService = createCallbackService(auth, {
|
|
286
|
+
lang: 'us',
|
|
287
|
+
defaultRedirect: '/',
|
|
288
|
+
signInPath: '/sign-in',
|
|
289
|
+
linkExpiredPath: '/auth/link-expired',
|
|
290
|
+
deleteSuccessPath: '/auth/delete-success',
|
|
291
|
+
})
|
|
230
292
|
|
|
231
|
-
//
|
|
232
|
-
const
|
|
293
|
+
// Handle OAuth callback
|
|
294
|
+
const result = await callbackService.handleOAuthCallback(token, isNewUser)
|
|
233
295
|
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
// 重新登录
|
|
237
|
-
}
|
|
296
|
+
// Handle email verification callback
|
|
297
|
+
const result = await callbackService.handleEmailVerificationCallback(token)
|
|
238
298
|
|
|
239
|
-
//
|
|
240
|
-
const
|
|
299
|
+
// Handle delete user callback
|
|
300
|
+
const result = await callbackService.handleDeleteUserCallback(token, userEmail)
|
|
301
|
+
|
|
302
|
+
// Unified handler
|
|
303
|
+
const result = await callbackService.handleCallback(type, token, { isNewUser, userEmail, invitationId })
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### companionTeam
|
|
307
|
+
|
|
308
|
+
Companion team setup:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { setupCompanionTeam } from "@choiceform/shared-auth"
|
|
312
|
+
|
|
313
|
+
await setupCompanionTeam(auth, options)
|
|
241
314
|
```
|
|
242
315
|
|
|
243
316
|
## Store Layer
|
|
244
317
|
|
|
245
318
|
### authStore (Legend State Observable)
|
|
246
319
|
|
|
247
|
-
|
|
320
|
+
Reactive state, can be used in React components:
|
|
248
321
|
|
|
249
322
|
```typescript
|
|
250
323
|
import { use$ } from "@legendapp/state/react"
|
|
@@ -252,9 +325,11 @@ import { use$ } from "@legendapp/state/react"
|
|
|
252
325
|
function MyComponent() {
|
|
253
326
|
const user = use$(auth.authStore.user)
|
|
254
327
|
const isAuthenticated = use$(auth.authStore.isAuthenticated)
|
|
328
|
+
const loading = use$(auth.authStore.loading)
|
|
255
329
|
const error = use$(auth.authStore.error)
|
|
256
|
-
|
|
257
|
-
|
|
330
|
+
const isLoaded = use$(auth.authStore.isLoaded)
|
|
331
|
+
|
|
332
|
+
// Computed state
|
|
258
333
|
const isReady = use$(auth.authComputed.isReady)
|
|
259
334
|
const activeOrganizationId = use$(auth.authComputed.activeOrganizationId)
|
|
260
335
|
}
|
|
@@ -262,99 +337,255 @@ function MyComponent() {
|
|
|
262
337
|
|
|
263
338
|
### storeActions
|
|
264
339
|
|
|
265
|
-
|
|
340
|
+
State management actions:
|
|
266
341
|
|
|
267
342
|
```typescript
|
|
268
|
-
//
|
|
343
|
+
// Read
|
|
269
344
|
auth.storeActions.getUser()
|
|
270
345
|
auth.storeActions.getUserId()
|
|
271
346
|
auth.storeActions.isAuthenticated()
|
|
272
347
|
auth.storeActions.isLoading()
|
|
273
348
|
auth.storeActions.isLoaded()
|
|
274
349
|
|
|
275
|
-
//
|
|
350
|
+
// Update
|
|
276
351
|
auth.storeActions.setUser(user)
|
|
277
352
|
auth.storeActions.updateUser({ name: "New Name" })
|
|
278
353
|
auth.storeActions.setLoading(true)
|
|
279
354
|
auth.storeActions.setError("Error message")
|
|
280
355
|
auth.storeActions.clearAuth()
|
|
356
|
+
auth.storeActions.handleUnauthorized()
|
|
281
357
|
|
|
282
|
-
// Active
|
|
358
|
+
// Active state
|
|
283
359
|
auth.storeActions.setActiveOrganizationId(orgId)
|
|
284
360
|
auth.storeActions.setActiveTeamId(teamId)
|
|
285
361
|
```
|
|
286
362
|
|
|
287
|
-
|
|
363
|
+
### Store Utilities
|
|
364
|
+
|
|
365
|
+
Standalone utility functions for non-component scenarios:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import {
|
|
369
|
+
getCurrentUser,
|
|
370
|
+
getCurrentUserId,
|
|
371
|
+
isAuthenticated,
|
|
372
|
+
isLoading,
|
|
373
|
+
isLoaded,
|
|
374
|
+
waitForAuth,
|
|
375
|
+
getAuthToken,
|
|
376
|
+
getAuthTokenSync,
|
|
377
|
+
getAuthHeaders,
|
|
378
|
+
getAuthHeadersSync,
|
|
379
|
+
handle401Response,
|
|
380
|
+
createUserManager,
|
|
381
|
+
} from "@choiceform/shared-auth"
|
|
382
|
+
|
|
383
|
+
// Wait for auth to complete
|
|
384
|
+
await waitForAuth(auth.authStore)
|
|
385
|
+
|
|
386
|
+
// Get auth headers (sync)
|
|
387
|
+
const headers = getAuthHeadersSync(auth.tokenStorage)
|
|
388
|
+
|
|
389
|
+
// Handle 401 response
|
|
390
|
+
handle401Response(response, auth.storeActions)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Utilities
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import {
|
|
397
|
+
// Environment
|
|
398
|
+
getEnvVar,
|
|
399
|
+
getAuthBaseUrl,
|
|
400
|
+
|
|
401
|
+
// Validation
|
|
402
|
+
isValidEmail,
|
|
403
|
+
|
|
404
|
+
// Error parsing
|
|
405
|
+
parseAuthError,
|
|
406
|
+
isTokenExpiredError,
|
|
407
|
+
AUTH_ERROR_CODES,
|
|
408
|
+
|
|
409
|
+
// URL utilities
|
|
410
|
+
getNameFromEmail,
|
|
411
|
+
buildAuthUrl,
|
|
412
|
+
buildAuthPath,
|
|
413
|
+
clearAuthParams,
|
|
414
|
+
|
|
415
|
+
// User mapping
|
|
416
|
+
extractSessionUser,
|
|
417
|
+
} from "@choiceform/shared-auth"
|
|
418
|
+
|
|
419
|
+
// Validate email
|
|
420
|
+
if (isValidEmail(email)) {
|
|
421
|
+
// ...
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Parse error
|
|
425
|
+
const { code, message, isKnownError } = parseAuthError(error)
|
|
426
|
+
|
|
427
|
+
// Check if token expired
|
|
428
|
+
if (isTokenExpiredError(error)) {
|
|
429
|
+
// Re-authenticate
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Build URL
|
|
433
|
+
const url = buildAuthPath('/verify-email', { lang: 'us', email })
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## API Layer
|
|
437
|
+
|
|
438
|
+
Low-level API access:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import {
|
|
442
|
+
createApiClient,
|
|
443
|
+
createAuthApi,
|
|
444
|
+
createOrganizationApi,
|
|
445
|
+
createTeamApi,
|
|
446
|
+
parseErrorResponse,
|
|
447
|
+
} from "@choiceform/shared-auth"
|
|
448
|
+
|
|
449
|
+
// Use APIs directly
|
|
450
|
+
const response = await auth.authApi.getSession()
|
|
451
|
+
const orgs = await auth.organizationApi.listOrganizations()
|
|
452
|
+
const teams = await auth.teamApi.listTeams()
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Directory Structure
|
|
288
456
|
|
|
289
457
|
```
|
|
290
458
|
src/
|
|
291
459
|
├── api/ # API Layer
|
|
292
|
-
│ ├── client.ts # HTTP
|
|
293
|
-
│ ├── auth-api.ts #
|
|
294
|
-
│ ├── organization-api.ts
|
|
295
|
-
│
|
|
460
|
+
│ ├── client.ts # HTTP client
|
|
461
|
+
│ ├── auth-api.ts # Auth API
|
|
462
|
+
│ ├── organization-api.ts # Organization API
|
|
463
|
+
│ ├── team-api.ts # Team API
|
|
464
|
+
│ └── index.ts
|
|
296
465
|
├── services/ # Service Layer
|
|
297
|
-
│ ├── auth-service.ts #
|
|
298
|
-
│ ├── callback-service.ts #
|
|
299
|
-
│
|
|
466
|
+
│ ├── auth-service.ts # Auth business logic
|
|
467
|
+
│ ├── callback-service.ts # Callback handling
|
|
468
|
+
│ ├── companion-team.ts # Companion team setup
|
|
469
|
+
│ └── index.ts
|
|
300
470
|
├── store/ # Store Layer
|
|
301
|
-
│ ├── state.ts #
|
|
302
|
-
│ ├── actions.ts #
|
|
303
|
-
│ ├── computed.ts #
|
|
304
|
-
│
|
|
471
|
+
│ ├── state.ts # State definition
|
|
472
|
+
│ ├── actions.ts # State actions
|
|
473
|
+
│ ├── computed.ts # Computed properties
|
|
474
|
+
│ ├── utils.ts # Utility functions
|
|
475
|
+
│ └── index.ts
|
|
305
476
|
├── hooks/ # React Hooks
|
|
306
|
-
│ ├── use-auth-init.ts
|
|
307
|
-
│ ├── use-auth-sync.ts
|
|
477
|
+
│ ├── use-auth-init.ts # Auth initialization
|
|
478
|
+
│ ├── use-auth-sync.ts # State sync
|
|
308
479
|
│ ├── use-protected-route.ts
|
|
309
|
-
│
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
│ ├──
|
|
313
|
-
│
|
|
314
|
-
├──
|
|
315
|
-
├──
|
|
316
|
-
|
|
317
|
-
├──
|
|
318
|
-
├──
|
|
319
|
-
|
|
480
|
+
│ ├── use-email-verification.ts
|
|
481
|
+
│ └── index.ts
|
|
482
|
+
├── utils/ # Utilities
|
|
483
|
+
│ ├── auth-utils.ts # Auth utilities
|
|
484
|
+
│ ├── user-mapper.ts # User mapping
|
|
485
|
+
│ ├── date.ts # Date utilities
|
|
486
|
+
│ ├── env.ts # Environment variables
|
|
487
|
+
│ └── index.ts
|
|
488
|
+
├── types/ # Type definitions
|
|
489
|
+
│ ├── auth.ts
|
|
490
|
+
│ ├── callback.ts
|
|
491
|
+
│ ├── organization.ts
|
|
492
|
+
│ ├── team.ts
|
|
493
|
+
│ ├── user.ts
|
|
494
|
+
│ └── index.ts
|
|
495
|
+
├── lib/ # Better Auth client
|
|
496
|
+
│ └── auth-client.ts
|
|
497
|
+
├── core.ts # Core entry (createAuth)
|
|
498
|
+
├── init.ts # Quick init (initAuth)
|
|
499
|
+
├── config.ts # Default config
|
|
500
|
+
└── index.ts # Export entry
|
|
320
501
|
```
|
|
321
502
|
|
|
322
|
-
##
|
|
503
|
+
## Type Exports
|
|
323
504
|
|
|
324
505
|
```typescript
|
|
325
506
|
import type {
|
|
326
|
-
//
|
|
507
|
+
// Core
|
|
327
508
|
AuthInstance,
|
|
328
509
|
AuthState,
|
|
510
|
+
AuthConfig,
|
|
329
511
|
SessionUser,
|
|
330
|
-
|
|
331
|
-
|
|
512
|
+
SessionUserMetadata,
|
|
513
|
+
Session,
|
|
514
|
+
|
|
515
|
+
// Services
|
|
332
516
|
AuthService,
|
|
517
|
+
AuthServiceConfig,
|
|
333
518
|
CallbackService,
|
|
334
519
|
CallbackType,
|
|
335
520
|
CallbackResult,
|
|
336
|
-
|
|
521
|
+
CallbackConfig,
|
|
522
|
+
|
|
337
523
|
// Hooks
|
|
338
524
|
UseAuthSyncConfig,
|
|
525
|
+
UseAuthSyncResult,
|
|
339
526
|
UseProtectedRouteConfig,
|
|
340
527
|
UseProtectedRouteResult,
|
|
341
528
|
ProtectionStatus,
|
|
342
529
|
UseEmailVerificationConfig,
|
|
343
530
|
UseEmailVerificationResult,
|
|
344
|
-
|
|
345
|
-
//
|
|
531
|
+
|
|
532
|
+
// Organization
|
|
346
533
|
Organization,
|
|
534
|
+
OrganizationMetadata,
|
|
535
|
+
FullOrganization,
|
|
536
|
+
CreateOrganizationRequest,
|
|
537
|
+
UpdateOrganizationRequest,
|
|
347
538
|
Member,
|
|
348
539
|
MemberWithUser,
|
|
349
|
-
Invitation,
|
|
350
540
|
MemberRole,
|
|
351
|
-
|
|
352
|
-
|
|
541
|
+
Invitation,
|
|
542
|
+
InvitationDetail,
|
|
543
|
+
InvitationStatus,
|
|
544
|
+
|
|
545
|
+
// Team
|
|
353
546
|
Team,
|
|
547
|
+
TeamMetadata,
|
|
354
548
|
TeamMember,
|
|
355
|
-
|
|
356
|
-
|
|
549
|
+
CreateTeamRequest,
|
|
550
|
+
UpdateTeamRequest,
|
|
551
|
+
|
|
552
|
+
// API
|
|
553
|
+
ApiClient,
|
|
554
|
+
ApiClientConfig,
|
|
555
|
+
ApiResponse,
|
|
556
|
+
TokenStorage,
|
|
557
|
+
AuthApi,
|
|
558
|
+
OrganizationApi,
|
|
559
|
+
TeamApi,
|
|
560
|
+
|
|
561
|
+
// Store
|
|
562
|
+
StoreActions,
|
|
563
|
+
|
|
564
|
+
// Utilities
|
|
357
565
|
AuthErrorCode,
|
|
358
566
|
ParsedAuthError,
|
|
359
567
|
} from "@choiceform/shared-auth"
|
|
360
568
|
```
|
|
569
|
+
|
|
570
|
+
## Development
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
# Install dependencies
|
|
574
|
+
pnpm install
|
|
575
|
+
|
|
576
|
+
# Development mode
|
|
577
|
+
pnpm dev
|
|
578
|
+
|
|
579
|
+
# Build
|
|
580
|
+
pnpm build
|
|
581
|
+
|
|
582
|
+
# Test
|
|
583
|
+
pnpm test
|
|
584
|
+
|
|
585
|
+
# Watch tests
|
|
586
|
+
pnpm test:watch
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## License
|
|
590
|
+
|
|
591
|
+
MIT
|