@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 CHANGED
@@ -1,55 +1,140 @@
1
1
  # @choiceform/shared-auth
2
2
 
3
- 基于 Better Auth + Legend State 的共享认证库。
3
+ A shared authentication library based on Better Auth + Legend State.
4
4
 
5
- ## 架构设计
5
+ ## Architecture
6
6
 
7
7
  ```
8
8
  ┌─────────────────────────────────────────────────────────────┐
9
- createAuth()
10
- (core.ts)
9
+ createAuth() / initAuth()
10
+ (core.ts)
11
11
  ├─────────────────────────────────────────────────────────────┤
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 │ │useEmailVerif
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** | 响应式状态管理 | `store/state.ts`, `store/actions.ts`, `store/computed.ts` |
30
- | **Service Layer** | 业务逻辑 | `services/auth-service.ts`, `services/callback-service.ts`, `services/companion-team.ts` |
31
- | **API Layer** | HTTP 请求 | `api/client.ts`, `api/auth-api.ts`, `api/organization-api.ts`, `api/team-api.ts` |
32
- | **Hooks Layer** | React 专用 | `hooks/use-auth-sync.ts`, `hooks/use-protected-route.ts`, `hooks/use-email-verification.ts` |
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
- ## Hooks(推荐使用)
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
- 认证状态同步,替代手动同步 Better Auth session
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(`验证邮件已发送到 ${email}`),
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>邮箱已验证,正在跳转...</p>
218
+ <p>Email verified, redirecting...</p>
134
219
  ) : (
135
220
  <>
136
221
  <button onClick={resendVerification} disabled={isLoading || isCountingDown}>
137
- {isCountingDown ? `${countdown}s 后重试` : '重新发送'}
222
+ {isCountingDown ? `Retry in ${countdown}s` : 'Resend'}
138
223
  </button>
139
- <button onClick={changeEmail}>使用其他邮箱</button>
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
- ### callbackService
150
-
151
- 处理各种认证回调(OAuth、邮箱验证、删除用户等):
234
+ Initialize authentication state:
152
235
 
153
236
  ```typescript
154
- import { createCallbackService } from "@choiceform/shared-auth"
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
- const result = await callbackService.handleEmailVerificationCallback(token)
169
-
170
- // 处理删除用户回调
171
- const result = await callbackService.handleDeleteUserCallback(token, userEmail)
239
+ // Hook approach
240
+ function App() {
241
+ useAuthInit(auth)
242
+ return <YourApp />
243
+ }
172
244
 
173
- // 统一处理入口
174
- const result = await callbackService.handleCallback(type, token, { isNewUser, userEmail, invitationId })
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
- // 使用 Token 获取 Session
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
- if (isValidEmail(email)) {
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 { code, message, isKnownError } = parseAuthError(error)
293
+ // Handle OAuth callback
294
+ const result = await callbackService.handleOAuthCallback(token, isNewUser)
233
295
 
234
- // 检查 token 是否过期
235
- if (isTokenExpiredError(error)) {
236
- // 重新登录
237
- }
296
+ // Handle email verification callback
297
+ const result = await callbackService.handleEmailVerificationCallback(token)
238
298
 
239
- // 构建 URL
240
- const url = buildAuthPath('/verify-email', { lang: 'us', email })
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
- 响应式状态,可在 React 组件中使用:
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
- // Computed 状态
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 # 认证 API
294
- │ ├── organization-api.ts
295
- └── team-api.ts
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
- └── companion-team.ts # 伴生团队设置
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
- └── utils.ts # 工具函数
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
- └── use-email-verification.ts
310
- ├── utils/ # 工具函数
311
- ├── auth-utils.ts # 认证工具
312
- │ ├── user-mapper.ts
313
- └── env.ts
314
- ├── types/ # 类型定义
315
- ├── lib/ # Better Auth 客户端
316
- ├── core.ts # 核心入口
317
- ├── init.ts # 快速初始化
318
- ├── config.ts # 默认配置
319
- └── index.ts # 导出入口
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