@choiceform/shared-auth 0.1.17 → 0.1.19
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 +504 -121
- package/dist/__tests__/auth-utils.test.d.ts +5 -0
- package/dist/__tests__/auth-utils.test.d.ts.map +1 -0
- package/dist/__tests__/auth-utils.test.js +96 -0
- package/dist/__tests__/store.test.d.ts +5 -0
- package/dist/__tests__/store.test.d.ts.map +1 -0
- package/dist/__tests__/store.test.js +210 -0
- package/dist/__tests__/user-mapper.test.d.ts +5 -0
- package/dist/__tests__/user-mapper.test.d.ts.map +1 -0
- package/dist/__tests__/user-mapper.test.js +76 -0
- package/dist/api/auth-api.d.ts +93 -9
- package/dist/api/auth-api.d.ts.map +1 -1
- package/dist/api/auth-api.js +219 -80
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +10 -0
- package/dist/api/organization-api.d.ts +2 -7
- package/dist/api/organization-api.d.ts.map +1 -1
- package/dist/api/organization-api.js +2 -17
- package/dist/api/team-api.d.ts +1 -5
- package/dist/api/team-api.d.ts.map +1 -1
- package/dist/api/team-api.js +5 -11
- package/dist/config.js +1 -1
- package/dist/core.d.ts +257 -137
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +112 -28
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/use-auth-init.d.ts +4 -0
- package/dist/hooks/use-auth-init.d.ts.map +1 -1
- package/dist/hooks/use-auth-init.js +16 -21
- package/dist/hooks/use-auth-sync.d.ts +60 -0
- package/dist/hooks/use-auth-sync.d.ts.map +1 -0
- package/dist/hooks/use-auth-sync.js +116 -0
- package/dist/hooks/use-email-verification.d.ts +85 -0
- package/dist/hooks/use-email-verification.d.ts.map +1 -0
- package/dist/hooks/use-email-verification.js +145 -0
- package/dist/hooks/use-protected-route.d.ts +67 -0
- package/dist/hooks/use-protected-route.d.ts.map +1 -0
- package/dist/hooks/use-protected-route.js +102 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -13
- package/dist/init.d.ts +236 -136
- 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/lib/auth-client.js +75 -2
- package/dist/services/auth-service.d.ts +101 -0
- package/dist/services/auth-service.d.ts.map +1 -0
- package/dist/services/auth-service.js +356 -0
- package/dist/services/callback-service.d.ts +33 -0
- package/dist/services/callback-service.d.ts.map +1 -0
- package/dist/services/callback-service.js +473 -0
- package/dist/services/companion-team.d.ts.map +1 -1
- package/dist/services/companion-team.js +41 -39
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -0
- package/dist/services/referral-service.d.ts +54 -0
- package/dist/services/referral-service.d.ts.map +1 -0
- package/dist/services/referral-service.js +54 -0
- package/dist/store/actions.d.ts +54 -51
- package/dist/store/actions.d.ts.map +1 -1
- package/dist/store/actions.js +111 -243
- package/dist/store/computed.d.ts +72 -1
- package/dist/store/computed.d.ts.map +1 -1
- package/dist/store/computed.js +90 -3
- package/dist/store/index.d.ts +3 -3
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +2 -2
- package/dist/store/state.d.ts +10 -0
- package/dist/store/state.d.ts.map +1 -1
- package/dist/store/state.js +11 -1
- package/dist/store/utils.d.ts +3 -34
- package/dist/store/utils.d.ts.map +1 -1
- package/dist/store/utils.js +2 -22
- package/dist/types/auth.d.ts +106 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/callback.d.ts +35 -0
- package/dist/types/callback.d.ts.map +1 -0
- package/dist/types/callback.js +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/organization.d.ts +19 -3
- package/dist/types/organization.d.ts.map +1 -1
- package/dist/types/team.d.ts +6 -2
- package/dist/types/team.d.ts.map +1 -1
- package/dist/types/user.d.ts +11 -3
- package/dist/types/user.d.ts.map +1 -1
- package/dist/utils/auth-utils.d.ts +60 -0
- package/dist/utils/auth-utils.d.ts.map +1 -0
- package/dist/utils/auth-utils.js +146 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/user-mapper.d.ts.map +1 -1
- package/dist/utils/user-mapper.js +4 -1
- package/package.json +17 -10
package/README.md
CHANGED
|
@@ -1,207 +1,590 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ createAuth() / initAuth() │
|
|
10
|
+
│ (core.ts) │
|
|
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 │ │useEmailVer. │ │
|
|
20
|
+
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
|
|
21
|
+
│ │
|
|
22
|
+
└─────────────────────────────────────────────────────────────┘
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Four-Layer Architecture
|
|
26
|
+
|
|
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` |
|
|
13
33
|
|
|
14
|
-
##
|
|
34
|
+
## Installation
|
|
15
35
|
|
|
16
36
|
```bash
|
|
17
37
|
pnpm add @choiceform/shared-auth
|
|
18
38
|
```
|
|
19
39
|
|
|
20
|
-
|
|
40
|
+
### Peer Dependencies
|
|
21
41
|
|
|
22
|
-
```
|
|
23
|
-
|
|
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
|
+
}
|
|
24
49
|
```
|
|
25
50
|
|
|
26
|
-
##
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
### Option 1: initAuth (Recommended)
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
Quick initialization with default configuration, automatically includes `magicLinkClient` and `organizationClient` plugins:
|
|
29
56
|
|
|
30
57
|
```typescript
|
|
31
58
|
import { initAuth } from "@choiceform/shared-auth"
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
baseURL:
|
|
60
|
+
const auth = initAuth({
|
|
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
|
+
],
|
|
35
85
|
tokenStorageKey: "auth-token",
|
|
36
86
|
})
|
|
87
|
+
|
|
88
|
+
export { auth }
|
|
37
89
|
```
|
|
38
90
|
|
|
39
|
-
|
|
91
|
+
## AuthInstance API
|
|
92
|
+
|
|
93
|
+
The instance returned by `createAuth()` / `initAuth()` includes:
|
|
40
94
|
|
|
41
95
|
```typescript
|
|
42
|
-
|
|
43
|
-
|
|
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)
|
|
134
|
+
|
|
135
|
+
### useAuthSync
|
|
136
|
+
|
|
137
|
+
Sync authentication state, replaces manual Better Auth session sync:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { useAuthSync } from "@choiceform/shared-auth"
|
|
44
141
|
|
|
45
142
|
function App() {
|
|
46
|
-
|
|
143
|
+
useAuthSync(auth, {
|
|
144
|
+
skipCompanionTeamPaths: ['/auth/callback', '/auth/delete-success'],
|
|
145
|
+
onAuthChange: (isAuthenticated) => {
|
|
146
|
+
if (isAuthenticated) {
|
|
147
|
+
syncTheme()
|
|
148
|
+
syncLanguage()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return <YourApp />
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### useProtectedRoute
|
|
158
|
+
|
|
159
|
+
Route protection, checks authentication status and email verification:
|
|
47
160
|
|
|
48
|
-
|
|
49
|
-
|
|
161
|
+
```typescript
|
|
162
|
+
import { useProtectedRoute } from "@choiceform/shared-auth"
|
|
163
|
+
|
|
164
|
+
function ProtectedRoute({ children }) {
|
|
165
|
+
const navigate = useNavigate()
|
|
166
|
+
const location = useLocation()
|
|
167
|
+
|
|
168
|
+
const { status, shouldRender, redirectPath } = useProtectedRoute(auth, {
|
|
169
|
+
pathname: location.pathname,
|
|
170
|
+
publicRoutePrefixes: ['/resources', '/public'],
|
|
171
|
+
requireEmailVerified: true,
|
|
172
|
+
lang: 'us'
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (redirectPath) {
|
|
177
|
+
navigate(redirectPath, { replace: true })
|
|
178
|
+
}
|
|
179
|
+
}, [redirectPath])
|
|
180
|
+
|
|
181
|
+
if (!shouldRender) {
|
|
182
|
+
return <Loading />
|
|
50
183
|
}
|
|
51
184
|
|
|
52
|
-
return
|
|
185
|
+
return <>{children}</>
|
|
53
186
|
}
|
|
54
187
|
```
|
|
55
188
|
|
|
56
|
-
###
|
|
189
|
+
### useEmailVerification
|
|
190
|
+
|
|
191
|
+
Email verification flow:
|
|
57
192
|
|
|
58
193
|
```typescript
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
194
|
+
import { useEmailVerification } from "@choiceform/shared-auth"
|
|
195
|
+
|
|
196
|
+
function VerifyEmailPage({ email, lang }) {
|
|
197
|
+
const navigate = useNavigate()
|
|
198
|
+
|
|
199
|
+
const {
|
|
200
|
+
isLoading,
|
|
201
|
+
isAlreadyVerified,
|
|
202
|
+
isCountingDown,
|
|
203
|
+
countdown,
|
|
204
|
+
resendVerification,
|
|
205
|
+
changeEmail
|
|
206
|
+
} = useEmailVerification(auth, {
|
|
207
|
+
email,
|
|
208
|
+
lang,
|
|
209
|
+
onRedirect: (path) => navigate(path),
|
|
210
|
+
onSendSuccess: (email) => toast.success(`Verification email sent to ${email}`),
|
|
211
|
+
onSendError: () => toast.error('Failed to send'),
|
|
212
|
+
onAlreadyVerified: () => navigate('/community')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div>
|
|
217
|
+
{isAlreadyVerified ? (
|
|
218
|
+
<p>Email verified, redirecting...</p>
|
|
219
|
+
) : (
|
|
220
|
+
<>
|
|
221
|
+
<button onClick={resendVerification} disabled={isLoading || isCountingDown}>
|
|
222
|
+
{isCountingDown ? `Retry in ${countdown}s` : 'Resend'}
|
|
223
|
+
</button>
|
|
224
|
+
<button onClick={changeEmail}>Use different email</button>
|
|
225
|
+
</>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
68
228
|
)
|
|
69
229
|
}
|
|
230
|
+
```
|
|
70
231
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
232
|
+
### useAuthInit
|
|
233
|
+
|
|
234
|
+
Initialize authentication state:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { useAuthInit, initializeAuth } from "@choiceform/shared-auth"
|
|
238
|
+
|
|
239
|
+
// Hook approach
|
|
240
|
+
function App() {
|
|
241
|
+
useAuthInit(auth)
|
|
242
|
+
return <YourApp />
|
|
79
243
|
}
|
|
244
|
+
|
|
245
|
+
// Or manual call
|
|
246
|
+
await initializeAuth(auth)
|
|
80
247
|
```
|
|
81
248
|
|
|
82
|
-
|
|
249
|
+
## Service Layer
|
|
250
|
+
|
|
251
|
+
### authService
|
|
83
252
|
|
|
84
|
-
|
|
253
|
+
Auth business logic wrapper:
|
|
85
254
|
|
|
86
255
|
```typescript
|
|
87
|
-
|
|
256
|
+
// OAuth sign in
|
|
257
|
+
await auth.authService.signInWithOAuth("github", callbackURL, newUserCallbackURL, errorCallbackURL)
|
|
88
258
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
259
|
+
// Magic Link sign in
|
|
260
|
+
await auth.authService.signInWithMagicLink(email, callbackURL, name, newUserCallbackURL)
|
|
261
|
+
|
|
262
|
+
// Email/password sign in
|
|
263
|
+
const result = await auth.authService.signInWithEmail(email, password)
|
|
264
|
+
|
|
265
|
+
// Email/password sign up
|
|
266
|
+
const result = await auth.authService.signUpWithEmail(email, password, name, callbackURL)
|
|
267
|
+
|
|
268
|
+
// Sign out
|
|
269
|
+
await auth.authService.signOut("/sign-in")
|
|
270
|
+
|
|
271
|
+
// Delete account
|
|
272
|
+
await auth.authService.deleteUser(callbackURL, password)
|
|
273
|
+
|
|
274
|
+
// Fetch session with token
|
|
275
|
+
await auth.authService.fetchAndSetSession(token)
|
|
96
276
|
```
|
|
97
277
|
|
|
98
|
-
|
|
278
|
+
### callbackService
|
|
99
279
|
|
|
100
|
-
|
|
280
|
+
Handle various auth callbacks (OAuth, email verification, user deletion, etc.):
|
|
101
281
|
|
|
102
|
-
|
|
282
|
+
```typescript
|
|
283
|
+
import { createCallbackService } from "@choiceform/shared-auth"
|
|
284
|
+
|
|
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
|
+
})
|
|
103
292
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
| baseURL | string | OneAuth API 地址 |
|
|
107
|
-
| tokenStorageKey | string | localStorage key(默认 `auth-token`) |
|
|
108
|
-
| plugins | BetterAuthPlugin[] | Better Auth 插件 |
|
|
293
|
+
// Handle OAuth callback
|
|
294
|
+
const result = await callbackService.handleOAuthCallback(token, isNewUser)
|
|
109
295
|
|
|
110
|
-
|
|
296
|
+
// Handle email verification callback
|
|
297
|
+
const result = await callbackService.handleEmailVerificationCallback(token)
|
|
111
298
|
|
|
112
|
-
|
|
299
|
+
// Handle delete user callback
|
|
300
|
+
const result = await callbackService.handleDeleteUserCallback(token, userEmail)
|
|
113
301
|
|
|
114
|
-
|
|
302
|
+
// Unified handler
|
|
303
|
+
const result = await callbackService.handleCallback(type, token, { isNewUser, userEmail, invitationId })
|
|
304
|
+
```
|
|
115
305
|
|
|
116
|
-
|
|
117
|
-
|------|------|
|
|
118
|
-
| authStore | Legend State store |
|
|
119
|
-
| authActions | 认证操作(signIn, signOut 等) |
|
|
120
|
-
| authApi | 认证 API |
|
|
121
|
-
| organizationApi | 组织 API |
|
|
122
|
-
| teamApi | 团队 API |
|
|
123
|
-
| tokenStorage | Token 存储工具 |
|
|
306
|
+
### companionTeam
|
|
124
307
|
|
|
125
|
-
|
|
308
|
+
Companion team setup:
|
|
126
309
|
|
|
127
310
|
```typescript
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
311
|
+
import { setupCompanionTeam } from "@choiceform/shared-auth"
|
|
312
|
+
|
|
313
|
+
await setupCompanionTeam(auth, options)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Store Layer
|
|
131
317
|
|
|
132
|
-
|
|
133
|
-
const authenticated = auth.isAuthenticated()
|
|
134
|
-
const loading = auth.isLoading()
|
|
135
|
-
const loaded = auth.isLoaded()
|
|
318
|
+
### authStore (Legend State Observable)
|
|
136
319
|
|
|
137
|
-
|
|
138
|
-
await auth.waitForAuth()
|
|
320
|
+
Reactive state, can be used in React components:
|
|
139
321
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
322
|
+
```typescript
|
|
323
|
+
import { use$ } from "@legendapp/state/react"
|
|
324
|
+
|
|
325
|
+
function MyComponent() {
|
|
326
|
+
const user = use$(auth.authStore.user)
|
|
327
|
+
const isAuthenticated = use$(auth.authStore.isAuthenticated)
|
|
328
|
+
const loading = use$(auth.authStore.loading)
|
|
329
|
+
const error = use$(auth.authStore.error)
|
|
330
|
+
const isLoaded = use$(auth.authStore.isLoaded)
|
|
331
|
+
|
|
332
|
+
// Computed state
|
|
333
|
+
const isReady = use$(auth.authComputed.isReady)
|
|
334
|
+
const activeOrganizationId = use$(auth.authComputed.activeOrganizationId)
|
|
335
|
+
}
|
|
143
336
|
```
|
|
144
337
|
|
|
145
|
-
|
|
338
|
+
### storeActions
|
|
339
|
+
|
|
340
|
+
State management actions:
|
|
146
341
|
|
|
147
342
|
```typescript
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
343
|
+
// Read
|
|
344
|
+
auth.storeActions.getUser()
|
|
345
|
+
auth.storeActions.getUserId()
|
|
346
|
+
auth.storeActions.isAuthenticated()
|
|
347
|
+
auth.storeActions.isLoading()
|
|
348
|
+
auth.storeActions.isLoaded()
|
|
349
|
+
|
|
350
|
+
// Update
|
|
351
|
+
auth.storeActions.setUser(user)
|
|
352
|
+
auth.storeActions.updateUser({ name: "New Name" })
|
|
353
|
+
auth.storeActions.setLoading(true)
|
|
354
|
+
auth.storeActions.setError("Error message")
|
|
355
|
+
auth.storeActions.clearAuth()
|
|
356
|
+
auth.storeActions.handleUnauthorized()
|
|
357
|
+
|
|
358
|
+
// Active state
|
|
359
|
+
auth.storeActions.setActiveOrganizationId(orgId)
|
|
360
|
+
auth.storeActions.setActiveTeamId(teamId)
|
|
361
|
+
```
|
|
362
|
+
|
|
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,
|
|
155
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)
|
|
156
391
|
```
|
|
157
392
|
|
|
158
|
-
|
|
393
|
+
## Utilities
|
|
159
394
|
|
|
160
395
|
```typescript
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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)) {
|
|
171
421
|
// ...
|
|
172
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 })
|
|
173
434
|
```
|
|
174
435
|
|
|
175
|
-
##
|
|
436
|
+
## API Layer
|
|
437
|
+
|
|
438
|
+
Low-level API access:
|
|
176
439
|
|
|
177
440
|
```typescript
|
|
178
|
-
import {
|
|
179
|
-
|
|
441
|
+
import {
|
|
442
|
+
createApiClient,
|
|
443
|
+
createAuthApi,
|
|
444
|
+
createOrganizationApi,
|
|
445
|
+
createTeamApi,
|
|
446
|
+
parseErrorResponse,
|
|
447
|
+
} from "@choiceform/shared-auth"
|
|
180
448
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
```
|
|
184
454
|
|
|
185
|
-
|
|
186
|
-
if (!user) return <SignIn />
|
|
455
|
+
## Directory Structure
|
|
187
456
|
|
|
188
|
-
|
|
189
|
-
|
|
457
|
+
```
|
|
458
|
+
src/
|
|
459
|
+
├── api/ # API Layer
|
|
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
|
|
465
|
+
├── services/ # Service Layer
|
|
466
|
+
│ ├── auth-service.ts # Auth business logic
|
|
467
|
+
│ ├── callback-service.ts # Callback handling
|
|
468
|
+
│ ├── companion-team.ts # Companion team setup
|
|
469
|
+
│ └── index.ts
|
|
470
|
+
├── store/ # Store Layer
|
|
471
|
+
│ ├── state.ts # State definition
|
|
472
|
+
│ ├── actions.ts # State actions
|
|
473
|
+
│ ├── computed.ts # Computed properties
|
|
474
|
+
│ ├── utils.ts # Utility functions
|
|
475
|
+
│ └── index.ts
|
|
476
|
+
├── hooks/ # React Hooks
|
|
477
|
+
│ ├── use-auth-init.ts # Auth initialization
|
|
478
|
+
│ ├── use-auth-sync.ts # State sync
|
|
479
|
+
│ ├── use-protected-route.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
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Type Exports
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import type {
|
|
507
|
+
// Core
|
|
508
|
+
AuthInstance,
|
|
509
|
+
AuthState,
|
|
510
|
+
AuthConfig,
|
|
511
|
+
SessionUser,
|
|
512
|
+
SessionUserMetadata,
|
|
513
|
+
Session,
|
|
514
|
+
|
|
515
|
+
// Services
|
|
516
|
+
AuthService,
|
|
517
|
+
AuthServiceConfig,
|
|
518
|
+
CallbackService,
|
|
519
|
+
CallbackType,
|
|
520
|
+
CallbackResult,
|
|
521
|
+
CallbackConfig,
|
|
522
|
+
|
|
523
|
+
// Hooks
|
|
524
|
+
UseAuthSyncConfig,
|
|
525
|
+
UseAuthSyncResult,
|
|
526
|
+
UseProtectedRouteConfig,
|
|
527
|
+
UseProtectedRouteResult,
|
|
528
|
+
ProtectionStatus,
|
|
529
|
+
UseEmailVerificationConfig,
|
|
530
|
+
UseEmailVerificationResult,
|
|
531
|
+
|
|
532
|
+
// Organization
|
|
533
|
+
Organization,
|
|
534
|
+
OrganizationMetadata,
|
|
535
|
+
FullOrganization,
|
|
536
|
+
CreateOrganizationRequest,
|
|
537
|
+
UpdateOrganizationRequest,
|
|
538
|
+
Member,
|
|
539
|
+
MemberWithUser,
|
|
540
|
+
MemberRole,
|
|
541
|
+
Invitation,
|
|
542
|
+
InvitationDetail,
|
|
543
|
+
InvitationStatus,
|
|
544
|
+
|
|
545
|
+
// Team
|
|
546
|
+
Team,
|
|
547
|
+
TeamMetadata,
|
|
548
|
+
TeamMember,
|
|
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
|
|
565
|
+
AuthErrorCode,
|
|
566
|
+
ParsedAuthError,
|
|
567
|
+
} from "@choiceform/shared-auth"
|
|
190
568
|
```
|
|
191
569
|
|
|
192
|
-
##
|
|
570
|
+
## Development
|
|
193
571
|
|
|
194
|
-
|
|
572
|
+
```bash
|
|
573
|
+
# Install dependencies
|
|
574
|
+
pnpm install
|
|
575
|
+
|
|
576
|
+
# Development mode
|
|
577
|
+
pnpm dev
|
|
195
578
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- 新增 `onboard` API、Magic Link 支持
|
|
199
|
-
- 移除 UI 组件(`AuthSync`、`ProtectedRoute`、`SignInPage`),由业务端实现
|
|
200
|
-
- 代码清理和优化
|
|
579
|
+
# Build
|
|
580
|
+
pnpm build
|
|
201
581
|
|
|
202
|
-
|
|
582
|
+
# Test
|
|
583
|
+
pnpm test
|
|
203
584
|
|
|
204
|
-
|
|
585
|
+
# Watch tests
|
|
586
|
+
pnpm test:watch
|
|
587
|
+
```
|
|
205
588
|
|
|
206
589
|
## License
|
|
207
590
|
|