@choiceform/shared-auth 0.1.17 → 0.1.18
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 +286 -134
- 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/components/auth-sync.d.ts +27 -0
- package/dist/components/auth-sync.d.ts.map +1 -0
- package/dist/components/auth-sync.js +117 -0
- package/dist/components/protected-route.d.ts +18 -0
- package/dist/components/protected-route.d.ts.map +1 -0
- package/dist/components/protected-route.js +34 -0
- package/dist/components/sign-in-page.d.ts +21 -0
- package/dist/components/sign-in-page.d.ts.map +1 -0
- package/dist/components/sign-in-page.js +31 -0
- package/dist/config.js +1 -1
- package/dist/core.d.ts +148 -71
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +109 -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 +43 -13
- package/dist/init.d.ts +127 -70
- package/dist/init.d.ts.map +1 -1
- 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 +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -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 +7 -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 +2 -1
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,208 +1,360 @@
|
|
|
1
1
|
# @choiceform/shared-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
基于 Better Auth + Legend State 的共享认证库。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 架构设计
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ createAuth() │
|
|
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 │ │useEmailVerif │ │
|
|
20
|
+
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
|
21
|
+
│ │
|
|
22
|
+
└─────────────────────────────────────────────────────────────┘
|
|
18
23
|
```
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
### 四层架构
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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` |
|
|
25
33
|
|
|
26
34
|
## 快速开始
|
|
27
35
|
|
|
28
|
-
### 初始化
|
|
29
|
-
|
|
30
36
|
```typescript
|
|
31
37
|
import { initAuth } from "@choiceform/shared-auth"
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
// 初始化
|
|
40
|
+
const auth = initAuth({
|
|
41
|
+
baseURL: "https://api.example.com",
|
|
36
42
|
})
|
|
43
|
+
|
|
44
|
+
// 导出供全局使用
|
|
45
|
+
export { auth }
|
|
37
46
|
```
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
## Hooks(推荐使用)
|
|
49
|
+
|
|
50
|
+
### useAuthSync
|
|
51
|
+
|
|
52
|
+
认证状态同步,替代手动同步 Better Auth session:
|
|
40
53
|
|
|
41
54
|
```typescript
|
|
42
|
-
import {
|
|
43
|
-
import { auth } from "./lib/auth"
|
|
55
|
+
import { useAuthSync } from "@choiceform/shared-auth"
|
|
44
56
|
|
|
45
57
|
function App() {
|
|
46
|
-
|
|
58
|
+
useAuthSync(auth, {
|
|
59
|
+
skipCompanionTeamPaths: ['/auth/callback', '/auth/delete-success'],
|
|
60
|
+
onAuthChange: (isAuthenticated) => {
|
|
61
|
+
if (isAuthenticated) {
|
|
62
|
+
syncTheme()
|
|
63
|
+
syncLanguage()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return <YourApp />
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### useProtectedRoute
|
|
73
|
+
|
|
74
|
+
路由保护,检查认证状态和邮箱验证:
|
|
47
75
|
|
|
48
|
-
|
|
49
|
-
|
|
76
|
+
```typescript
|
|
77
|
+
import { useProtectedRoute } from "@choiceform/shared-auth"
|
|
78
|
+
|
|
79
|
+
function ProtectedRoute({ children }) {
|
|
80
|
+
const navigate = useNavigate()
|
|
81
|
+
const location = useLocation()
|
|
82
|
+
|
|
83
|
+
const { status, shouldRender, redirectPath } = useProtectedRoute(auth, {
|
|
84
|
+
pathname: location.pathname,
|
|
85
|
+
publicRoutePrefixes: ['/resources', '/public'],
|
|
86
|
+
requireEmailVerified: true,
|
|
87
|
+
lang: 'us'
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (redirectPath) {
|
|
92
|
+
navigate(redirectPath, { replace: true })
|
|
93
|
+
}
|
|
94
|
+
}, [redirectPath])
|
|
95
|
+
|
|
96
|
+
if (!shouldRender) {
|
|
97
|
+
return <Loading />
|
|
50
98
|
}
|
|
51
99
|
|
|
52
|
-
return
|
|
100
|
+
return <>{children}</>
|
|
53
101
|
}
|
|
54
102
|
```
|
|
55
103
|
|
|
56
|
-
###
|
|
104
|
+
### useEmailVerification
|
|
57
105
|
|
|
58
|
-
|
|
59
|
-
// OAuth 登录
|
|
60
|
-
const handleOAuthSignIn = async () => {
|
|
61
|
-
const callbackURL = new URL("/dashboard", window.location.origin)
|
|
62
|
-
const newUserCallbackURL = new URL("/dashboard?isNew=true", window.location.origin)
|
|
63
|
-
|
|
64
|
-
await auth.authActions.signIn(
|
|
65
|
-
"github",
|
|
66
|
-
callbackURL.toString(),
|
|
67
|
-
newUserCallbackURL.toString()
|
|
68
|
-
)
|
|
69
|
-
}
|
|
106
|
+
邮箱验证流程:
|
|
70
107
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
```typescript
|
|
109
|
+
import { useEmailVerification } from "@choiceform/shared-auth"
|
|
110
|
+
|
|
111
|
+
function VerifyEmailPage({ email, lang }) {
|
|
112
|
+
const navigate = useNavigate()
|
|
113
|
+
|
|
114
|
+
const {
|
|
115
|
+
isLoading,
|
|
116
|
+
isAlreadyVerified,
|
|
117
|
+
isCountingDown,
|
|
118
|
+
countdown,
|
|
119
|
+
resendVerification,
|
|
120
|
+
changeEmail
|
|
121
|
+
} = useEmailVerification(auth, {
|
|
76
122
|
email,
|
|
77
|
-
|
|
123
|
+
lang,
|
|
124
|
+
onRedirect: (path) => navigate(path),
|
|
125
|
+
onSendSuccess: (email) => toast.success(`验证邮件已发送到 ${email}`),
|
|
126
|
+
onSendError: () => toast.error('发送失败'),
|
|
127
|
+
onAlreadyVerified: () => navigate('/community')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div>
|
|
132
|
+
{isAlreadyVerified ? (
|
|
133
|
+
<p>邮箱已验证,正在跳转...</p>
|
|
134
|
+
) : (
|
|
135
|
+
<>
|
|
136
|
+
<button onClick={resendVerification} disabled={isLoading || isCountingDown}>
|
|
137
|
+
{isCountingDown ? `${countdown}s 后重试` : '重新发送'}
|
|
138
|
+
</button>
|
|
139
|
+
<button onClick={changeEmail}>使用其他邮箱</button>
|
|
140
|
+
</>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
78
143
|
)
|
|
79
144
|
}
|
|
80
145
|
```
|
|
81
146
|
|
|
82
|
-
|
|
147
|
+
## 服务层
|
|
83
148
|
|
|
84
|
-
|
|
149
|
+
### callbackService
|
|
150
|
+
|
|
151
|
+
处理各种认证回调(OAuth、邮箱验证、删除用户等):
|
|
85
152
|
|
|
86
153
|
```typescript
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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',
|
|
95
162
|
})
|
|
96
|
-
```
|
|
97
163
|
|
|
98
|
-
|
|
164
|
+
// 处理 OAuth 回调
|
|
165
|
+
const result = await callbackService.handleOAuthCallback(token, isNewUser)
|
|
99
166
|
|
|
100
|
-
|
|
167
|
+
// 处理邮箱验证回调
|
|
168
|
+
const result = await callbackService.handleEmailVerificationCallback(token)
|
|
101
169
|
|
|
102
|
-
|
|
170
|
+
// 处理删除用户回调
|
|
171
|
+
const result = await callbackService.handleDeleteUserCallback(token, userEmail)
|
|
103
172
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
| tokenStorageKey | string | localStorage key(默认 `auth-token`) |
|
|
108
|
-
| plugins | BetterAuthPlugin[] | Better Auth 插件 |
|
|
173
|
+
// 统一处理入口
|
|
174
|
+
const result = await callbackService.handleCallback(type, token, { isNewUser, userEmail, invitationId })
|
|
175
|
+
```
|
|
109
176
|
|
|
110
|
-
###
|
|
177
|
+
### authService
|
|
111
178
|
|
|
112
|
-
|
|
179
|
+
业务逻辑封装:
|
|
113
180
|
|
|
114
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
// OAuth 登录
|
|
183
|
+
await auth.authService.signInWithOAuth("github", callbackURL, newUserCallbackURL, errorCallbackURL)
|
|
115
184
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
| authStore | Legend State store |
|
|
119
|
-
| authActions | 认证操作(signIn, signOut 等) |
|
|
120
|
-
| authApi | 认证 API |
|
|
121
|
-
| organizationApi | 组织 API |
|
|
122
|
-
| teamApi | 团队 API |
|
|
123
|
-
| tokenStorage | Token 存储工具 |
|
|
185
|
+
// Magic Link 登录
|
|
186
|
+
await auth.authService.signInWithMagicLink(email, callbackURL, name, newUserCallbackURL)
|
|
124
187
|
|
|
125
|
-
|
|
188
|
+
// 邮箱密码登录
|
|
189
|
+
const result = await auth.authService.signInWithEmail(email, password)
|
|
126
190
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const user = auth.getCurrentUser()
|
|
130
|
-
const userId = auth.getCurrentUserId()
|
|
191
|
+
// 邮箱密码注册
|
|
192
|
+
const result = await auth.authService.signUpWithEmail(email, password, name, callbackURL)
|
|
131
193
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
const loading = auth.isLoading()
|
|
135
|
-
const loaded = auth.isLoaded()
|
|
194
|
+
// 登出
|
|
195
|
+
await auth.authService.signOut("/sign-in")
|
|
136
196
|
|
|
137
|
-
//
|
|
138
|
-
await auth.
|
|
197
|
+
// 删除账户
|
|
198
|
+
await auth.authService.deleteUser(callbackURL, password)
|
|
139
199
|
|
|
140
|
-
// Token
|
|
141
|
-
|
|
142
|
-
const headers = auth.getAuthHeadersSync()
|
|
200
|
+
// 使用 Token 获取 Session
|
|
201
|
+
await auth.authService.fetchAndSetSession(token)
|
|
143
202
|
```
|
|
144
203
|
|
|
145
|
-
##
|
|
204
|
+
## 工具函数
|
|
146
205
|
|
|
147
206
|
```typescript
|
|
148
|
-
import
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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,
|
|
155
224
|
} from "@choiceform/shared-auth"
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### SessionUser
|
|
159
225
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
id: string
|
|
163
|
-
email: string
|
|
164
|
-
name: string
|
|
165
|
-
image?: string
|
|
166
|
-
metadata?: { color?: string }
|
|
167
|
-
inherentOrganizationId?: string // 伴生组织
|
|
168
|
-
inherentTeamId?: string // 伴生团队
|
|
169
|
-
activeOrganizationId?: string
|
|
170
|
-
activeTeamId?: string
|
|
226
|
+
// 验证邮箱
|
|
227
|
+
if (isValidEmail(email)) {
|
|
171
228
|
// ...
|
|
172
229
|
}
|
|
230
|
+
|
|
231
|
+
// 解析错误
|
|
232
|
+
const { code, message, isKnownError } = parseAuthError(error)
|
|
233
|
+
|
|
234
|
+
// 检查 token 是否过期
|
|
235
|
+
if (isTokenExpiredError(error)) {
|
|
236
|
+
// 重新登录
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 构建 URL
|
|
240
|
+
const url = buildAuthPath('/verify-email', { lang: 'us', email })
|
|
173
241
|
```
|
|
174
242
|
|
|
175
|
-
##
|
|
243
|
+
## Store Layer
|
|
244
|
+
|
|
245
|
+
### authStore (Legend State Observable)
|
|
246
|
+
|
|
247
|
+
响应式状态,可在 React 组件中使用:
|
|
176
248
|
|
|
177
249
|
```typescript
|
|
178
250
|
import { use$ } from "@legendapp/state/react"
|
|
179
|
-
import { auth } from "./lib/auth"
|
|
180
251
|
|
|
181
|
-
function
|
|
252
|
+
function MyComponent() {
|
|
182
253
|
const user = use$(auth.authStore.user)
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
254
|
+
const isAuthenticated = use$(auth.authStore.isAuthenticated)
|
|
255
|
+
const error = use$(auth.authStore.error)
|
|
256
|
+
|
|
257
|
+
// Computed 状态
|
|
258
|
+
const isReady = use$(auth.authComputed.isReady)
|
|
259
|
+
const activeOrganizationId = use$(auth.authComputed.activeOrganizationId)
|
|
189
260
|
}
|
|
190
261
|
```
|
|
191
262
|
|
|
192
|
-
|
|
263
|
+
### storeActions
|
|
193
264
|
|
|
194
|
-
|
|
265
|
+
状态管理:
|
|
195
266
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
267
|
+
```typescript
|
|
268
|
+
// 读取
|
|
269
|
+
auth.storeActions.getUser()
|
|
270
|
+
auth.storeActions.getUserId()
|
|
271
|
+
auth.storeActions.isAuthenticated()
|
|
272
|
+
auth.storeActions.isLoading()
|
|
273
|
+
auth.storeActions.isLoaded()
|
|
274
|
+
|
|
275
|
+
// 更新
|
|
276
|
+
auth.storeActions.setUser(user)
|
|
277
|
+
auth.storeActions.updateUser({ name: "New Name" })
|
|
278
|
+
auth.storeActions.setLoading(true)
|
|
279
|
+
auth.storeActions.setError("Error message")
|
|
280
|
+
auth.storeActions.clearAuth()
|
|
281
|
+
|
|
282
|
+
// Active 状态
|
|
283
|
+
auth.storeActions.setActiveOrganizationId(orgId)
|
|
284
|
+
auth.storeActions.setActiveTeamId(teamId)
|
|
285
|
+
```
|
|
201
286
|
|
|
202
|
-
|
|
287
|
+
## 目录结构
|
|
203
288
|
|
|
204
|
-
|
|
289
|
+
```
|
|
290
|
+
src/
|
|
291
|
+
├── api/ # API Layer
|
|
292
|
+
│ ├── client.ts # HTTP 客户端
|
|
293
|
+
│ ├── auth-api.ts # 认证 API
|
|
294
|
+
│ ├── organization-api.ts
|
|
295
|
+
│ └── team-api.ts
|
|
296
|
+
├── services/ # Service Layer
|
|
297
|
+
│ ├── auth-service.ts # 认证业务逻辑
|
|
298
|
+
│ ├── callback-service.ts # 回调处理
|
|
299
|
+
│ └── companion-team.ts # 伴生团队设置
|
|
300
|
+
├── store/ # Store Layer
|
|
301
|
+
│ ├── state.ts # 状态定义
|
|
302
|
+
│ ├── actions.ts # 状态操作
|
|
303
|
+
│ ├── computed.ts # 计算属性
|
|
304
|
+
│ └── utils.ts # 工具函数
|
|
305
|
+
├── hooks/ # React Hooks
|
|
306
|
+
│ ├── use-auth-init.ts
|
|
307
|
+
│ ├── use-auth-sync.ts
|
|
308
|
+
│ ├── 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 # 导出入口
|
|
320
|
+
```
|
|
205
321
|
|
|
206
|
-
##
|
|
322
|
+
## 类型导出
|
|
207
323
|
|
|
208
|
-
|
|
324
|
+
```typescript
|
|
325
|
+
import type {
|
|
326
|
+
// 核心
|
|
327
|
+
AuthInstance,
|
|
328
|
+
AuthState,
|
|
329
|
+
SessionUser,
|
|
330
|
+
|
|
331
|
+
// 服务
|
|
332
|
+
AuthService,
|
|
333
|
+
CallbackService,
|
|
334
|
+
CallbackType,
|
|
335
|
+
CallbackResult,
|
|
336
|
+
|
|
337
|
+
// Hooks
|
|
338
|
+
UseAuthSyncConfig,
|
|
339
|
+
UseProtectedRouteConfig,
|
|
340
|
+
UseProtectedRouteResult,
|
|
341
|
+
ProtectionStatus,
|
|
342
|
+
UseEmailVerificationConfig,
|
|
343
|
+
UseEmailVerificationResult,
|
|
344
|
+
|
|
345
|
+
// 组织
|
|
346
|
+
Organization,
|
|
347
|
+
Member,
|
|
348
|
+
MemberWithUser,
|
|
349
|
+
Invitation,
|
|
350
|
+
MemberRole,
|
|
351
|
+
|
|
352
|
+
// 团队
|
|
353
|
+
Team,
|
|
354
|
+
TeamMember,
|
|
355
|
+
|
|
356
|
+
// 工具
|
|
357
|
+
AuthErrorCode,
|
|
358
|
+
ParsedAuthError,
|
|
359
|
+
} from "@choiceform/shared-auth"
|
|
360
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-utils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具函数测试
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { isValidEmail, parseAuthError, isTokenExpiredError, getNameFromEmail, buildAuthPath, AUTH_ERROR_CODES, } from "../utils/auth-utils";
|
|
6
|
+
describe("isValidEmail", () => {
|
|
7
|
+
it("应该验证有效的邮箱", () => {
|
|
8
|
+
expect(isValidEmail("test@example.com")).toBe(true);
|
|
9
|
+
expect(isValidEmail("user.name@domain.co")).toBe(true);
|
|
10
|
+
expect(isValidEmail("user+tag@example.org")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it("应该拒绝无效的邮箱", () => {
|
|
13
|
+
expect(isValidEmail("")).toBe(false);
|
|
14
|
+
expect(isValidEmail("invalid")).toBe(false);
|
|
15
|
+
expect(isValidEmail("@example.com")).toBe(false);
|
|
16
|
+
expect(isValidEmail("test@")).toBe(false);
|
|
17
|
+
expect(isValidEmail("test@.com")).toBe(false);
|
|
18
|
+
expect(isValidEmail("test @example.com")).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("parseAuthError", () => {
|
|
22
|
+
it("应该解析 JSON 格式的错误", () => {
|
|
23
|
+
const error = JSON.stringify({ code: "PASSWORD_TOO_SHORT", message: "Password too short" });
|
|
24
|
+
const result = parseAuthError(error);
|
|
25
|
+
expect(result.code).toBe("PASSWORD_TOO_SHORT");
|
|
26
|
+
expect(result.isKnownError).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("应该处理包含已知错误代码的字符串", () => {
|
|
29
|
+
const result = parseAuthError("Error: INVALID_EMAIL_OR_PASSWORD");
|
|
30
|
+
expect(result.code).toBe("INVALID_EMAIL_OR_PASSWORD");
|
|
31
|
+
expect(result.isKnownError).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it("应该处理未知错误", () => {
|
|
34
|
+
const result = parseAuthError("Some unknown error");
|
|
35
|
+
expect(result.code).toBeNull();
|
|
36
|
+
expect(result.isKnownError).toBe(false);
|
|
37
|
+
expect(result.message).toBe("Some unknown error");
|
|
38
|
+
});
|
|
39
|
+
it("应该处理 null 输入", () => {
|
|
40
|
+
const result = parseAuthError(null);
|
|
41
|
+
expect(result.code).toBeNull();
|
|
42
|
+
expect(result.message).toBe("");
|
|
43
|
+
expect(result.isKnownError).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it("应该识别所有已知的错误代码", () => {
|
|
46
|
+
for (const code of AUTH_ERROR_CODES) {
|
|
47
|
+
const error = JSON.stringify({ code, message: "test" });
|
|
48
|
+
const result = parseAuthError(error);
|
|
49
|
+
expect(result.code).toBe(code);
|
|
50
|
+
expect(result.isKnownError).toBe(true);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("isTokenExpiredError", () => {
|
|
55
|
+
it("应该识别 INVALID_TOKEN 错误", () => {
|
|
56
|
+
expect(isTokenExpiredError(JSON.stringify({ code: "INVALID_TOKEN" }))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it("应该识别 SESSION_EXPIRED 错误", () => {
|
|
59
|
+
expect(isTokenExpiredError(JSON.stringify({ code: "SESSION_EXPIRED" }))).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
it("应该识别包含 expired 的消息", () => {
|
|
62
|
+
expect(isTokenExpiredError("Token has expired")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it("应该拒绝非过期错误", () => {
|
|
65
|
+
expect(isTokenExpiredError("Password too short")).toBe(false);
|
|
66
|
+
expect(isTokenExpiredError(null)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it("应该接受 ParsedAuthError 对象", () => {
|
|
69
|
+
expect(isTokenExpiredError({ code: "INVALID_TOKEN", message: "", isKnownError: true })).toBe(true);
|
|
70
|
+
expect(isTokenExpiredError({ code: null, message: "expired", isKnownError: false })).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("getNameFromEmail", () => {
|
|
74
|
+
it("应该从邮箱中提取名称", () => {
|
|
75
|
+
expect(getNameFromEmail("john.doe@example.com")).toBe("john.doe");
|
|
76
|
+
expect(getNameFromEmail("user@domain.com")).toBe("user");
|
|
77
|
+
expect(getNameFromEmail("test+tag@example.org")).toBe("test+tag");
|
|
78
|
+
});
|
|
79
|
+
it("应该处理空字符串", () => {
|
|
80
|
+
expect(getNameFromEmail("")).toBe("");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("buildAuthPath", () => {
|
|
84
|
+
it("应该构建带参数的路径", () => {
|
|
85
|
+
const path = buildAuthPath("/sign-in", { lang: "us", email: "test@example.com" });
|
|
86
|
+
expect(path).toBe("/sign-in?lang=us&email=test%40example.com");
|
|
87
|
+
});
|
|
88
|
+
it("应该忽略 null 和 undefined 参数", () => {
|
|
89
|
+
const path = buildAuthPath("/sign-in", { lang: "us", email: null, name: undefined });
|
|
90
|
+
expect(path).toBe("/sign-in?lang=us");
|
|
91
|
+
});
|
|
92
|
+
it("应该返回无参数的路径", () => {
|
|
93
|
+
const path = buildAuthPath("/sign-in", {});
|
|
94
|
+
expect(path).toBe("/sign-in");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|