@choiceform/shared-auth 0.1.14 → 0.1.15
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 +106 -450
- package/dist/api/auth-api.d.ts +28 -0
- package/dist/api/auth-api.d.ts.map +1 -0
- package/dist/api/auth-api.js +133 -0
- package/dist/api/client.d.ts +34 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +104 -0
- package/dist/api/index.d.ts +12 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +7 -0
- package/dist/api/organization-api.d.ts +96 -0
- package/dist/api/organization-api.d.ts.map +1 -0
- package/dist/api/organization-api.js +228 -0
- package/dist/api/team-api.d.ts +57 -0
- package/dist/api/team-api.d.ts.map +1 -0
- package/dist/api/team-api.js +118 -0
- package/dist/config.d.ts +4 -57
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -6
- package/dist/core.d.ts +114 -72
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +35 -17
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -12
- package/dist/init.d.ts +133 -92
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +12 -14
- package/dist/lib/auth-client.d.ts +49 -54
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +10 -16
- package/dist/services/companion-team.d.ts +16 -0
- package/dist/services/companion-team.d.ts.map +1 -0
- package/dist/services/companion-team.js +73 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +4 -0
- package/dist/store/actions.d.ts +45 -33
- package/dist/store/actions.d.ts.map +1 -1
- package/dist/store/actions.js +135 -106
- package/dist/store/index.d.ts +8 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +7 -0
- package/dist/store/state.d.ts +10 -7
- package/dist/store/state.d.ts.map +1 -1
- package/dist/store/state.js +31 -23
- package/dist/store/utils.d.ts +22 -71
- package/dist/store/utils.d.ts.map +1 -1
- package/dist/store/utils.js +28 -146
- package/dist/types/auth.d.ts +107 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +4 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/organization.d.ts +111 -0
- package/dist/types/organization.d.ts.map +1 -0
- package/dist/types/organization.js +4 -0
- package/dist/types/team.d.ts +52 -0
- package/dist/types/team.d.ts.map +1 -0
- package/dist/types/team.js +4 -0
- package/dist/types/user.d.ts +44 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +4 -0
- package/dist/utils/date.d.ts +10 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/env.d.ts +20 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +23 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/user-mapper.d.ts +21 -0
- package/dist/utils/user-mapper.d.ts.map +1 -0
- package/dist/utils/user-mapper.js +55 -0
- package/package.json +3 -4
- package/dist/components/auth-sync.d.ts +0 -25
- package/dist/components/auth-sync.d.ts.map +0 -1
- package/dist/components/auth-sync.js +0 -346
- 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/dist/core/init-auth-sync.d.ts +0 -7
- package/dist/core/init-auth-sync.d.ts.map +0 -1
- package/dist/core/init-auth-sync.js +0 -34
- package/dist/types.d.ts +0 -87
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -4
package/README.md
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
# @choiceform/shared-auth
|
|
2
2
|
|
|
3
|
-
共享认证包 -
|
|
3
|
+
共享认证包 - 基于 Better Auth 的统一认证解决方案
|
|
4
4
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- ✅ 完整的 TypeScript 类型支持
|
|
14
|
-
- ✅ 预配置的 API 客户端(自动添加认证头)
|
|
15
|
-
- ✅ 用户管理器工具
|
|
16
|
-
- ✅ 响应式状态管理(基于 Legend State)
|
|
17
|
-
- ✅ 自动伴生团队设置(登录时自动获取并设置活动组织和团队)
|
|
7
|
+
- Better Auth 集成(OAuth、Magic Link)
|
|
8
|
+
- Legend State 状态管理
|
|
9
|
+
- 自动伴生团队设置
|
|
10
|
+
- Token 自动管理
|
|
11
|
+
- 完整 TypeScript 类型支持
|
|
12
|
+
- 预配置 API 客户端
|
|
18
13
|
|
|
19
14
|
## 安装
|
|
20
15
|
|
|
@@ -22,230 +17,119 @@
|
|
|
22
17
|
pnpm add @choiceform/shared-auth
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
在使用认证功能前,需要在项目根目录配置环境变量:
|
|
20
|
+
## 环境变量
|
|
28
21
|
|
|
29
22
|
```bash
|
|
30
|
-
|
|
31
|
-
VITE_AUTH_API_URL=http://localhost:4320
|
|
23
|
+
VITE_ONEAUTH_BASE_URL=https://oneauth.choiceform.io
|
|
32
24
|
```
|
|
33
25
|
|
|
34
|
-
**注意:** 如果你的项目仍在使用 `VITE_CORE_AI_API_URL`,代码会自动向后兼容,但建议迁移到新的 `VITE_AUTH_API_URL`。
|
|
35
|
-
|
|
36
|
-
### 伴生团队功能
|
|
37
|
-
|
|
38
|
-
`AuthSync` 组件会在用户登录成功后自动:
|
|
39
|
-
|
|
40
|
-
1. 从 OneAuth API 获取用户的组织信息
|
|
41
|
-
2. 设置活动组织
|
|
42
|
-
3. 设置活动团队(如果存在)
|
|
43
|
-
|
|
44
|
-
此功能需要 `VITE_AUTH_API_URL` 环境变量已配置。如果未配置,功能会静默跳过,不影响登录流程。
|
|
45
|
-
|
|
46
26
|
## 快速开始
|
|
47
27
|
|
|
48
|
-
###
|
|
28
|
+
### 初始化
|
|
49
29
|
|
|
50
30
|
```typescript
|
|
51
31
|
import { initAuth } from "@choiceform/shared-auth"
|
|
52
|
-
import { adminClient, organizationClient } from "better-auth/client/plugins"
|
|
53
32
|
|
|
54
33
|
export const auth = initAuth({
|
|
55
|
-
baseURL: import.meta.env.
|
|
34
|
+
baseURL: import.meta.env.VITE_ONEAUTH_BASE_URL,
|
|
56
35
|
tokenStorageKey: "auth-token",
|
|
57
|
-
// 可选:如果你想自定义插件
|
|
58
|
-
plugins: [adminClient(), organizationClient({ teams: { enabled: true } })],
|
|
59
36
|
})
|
|
60
|
-
|
|
61
|
-
// 注意:导航相关配置已移至组件层面:
|
|
62
|
-
// - 登录成功后的导航:在 SignInPage 组件中通过 onAuthSuccess 回调处理
|
|
63
|
-
// - 未授权时的导航:在 ProtectedRoute 组件中通过 onUnauthorized 回调处理
|
|
64
|
-
// - 登出后的导航:在调用 signOut 时传入 redirectTo 参数
|
|
65
37
|
```
|
|
66
38
|
|
|
67
|
-
###
|
|
39
|
+
### 在应用中使用
|
|
68
40
|
|
|
69
41
|
```typescript
|
|
70
|
-
import {
|
|
71
|
-
import { useNavigate } from "react-router"
|
|
42
|
+
import { use$ } from "@legendapp/state/react"
|
|
72
43
|
import { auth } from "./lib/auth"
|
|
73
44
|
|
|
74
45
|
function App() {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<Route path="/sign-in" element={<SignInPage />} />
|
|
83
|
-
<Route
|
|
84
|
-
path="/*"
|
|
85
|
-
element={
|
|
86
|
-
<ProtectedRoute
|
|
87
|
-
auth={auth}
|
|
88
|
-
onUnauthorized={() => navigate("/sign-in", { replace: true })}
|
|
89
|
-
>
|
|
90
|
-
<YourApp />
|
|
91
|
-
</ProtectedRoute>
|
|
92
|
-
}
|
|
93
|
-
/>
|
|
94
|
-
</Router>
|
|
95
|
-
</>
|
|
96
|
-
)
|
|
46
|
+
const { isAuthenticated, user } = use$(auth.authStore)
|
|
47
|
+
|
|
48
|
+
if (!isAuthenticated) {
|
|
49
|
+
return <SignInPage />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return <MainApp user={user} />
|
|
97
53
|
}
|
|
98
54
|
```
|
|
99
55
|
|
|
100
|
-
###
|
|
56
|
+
### 登录
|
|
101
57
|
|
|
102
58
|
```typescript
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
71
|
+
// Magic Link 登录
|
|
72
|
+
const handleMagicLink = async (email: string) => {
|
|
73
|
+
const callbackURL = new URL("/dashboard", window.location.origin)
|
|
109
74
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
title="Your App Title"
|
|
114
|
-
description="Your app description"
|
|
115
|
-
provider="github"
|
|
116
|
-
redirectUrl="/dashboard"
|
|
117
|
-
onAuthSuccess={() => navigate("/dashboard")}
|
|
118
|
-
githubButton={(isSigningIn) => (
|
|
119
|
-
<button disabled={isSigningIn}>
|
|
120
|
-
{isSigningIn ? "Redirecting to GitHub..." : "Sign in with GitHub"}
|
|
121
|
-
</button>
|
|
122
|
-
)}
|
|
123
|
-
/>
|
|
75
|
+
await auth.authActions.signInWithMagicLink(
|
|
76
|
+
email,
|
|
77
|
+
callbackURL.toString()
|
|
124
78
|
)
|
|
125
79
|
}
|
|
126
80
|
```
|
|
127
81
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
### `initAuth(config)`
|
|
131
|
-
|
|
132
|
-
快速初始化认证系统(使用默认配置)。
|
|
133
|
-
|
|
134
|
-
**参数:**
|
|
135
|
-
|
|
136
|
-
- `baseURL`: Better Auth API 基础 URL(必需)
|
|
137
|
-
- `tokenStorageKey?`: Token 存储的 localStorage key(默认: `'auth-token'`)
|
|
138
|
-
- `plugins?`: Better Auth 插件列表(默认包含 `organizationClient()`)
|
|
139
|
-
- ~~`defaultRedirectAfterLogin?`~~: (已废弃) 建议在 SignInPage 组件中通过 `redirectUrl` prop 传入
|
|
140
|
-
- ~~`signInPath?`~~: (已废弃) 建议在 ProtectedRoute 组件中通过 `onUnauthorized` 回调处理导航
|
|
141
|
-
|
|
142
|
-
**返回:** `AuthInstance`
|
|
143
|
-
|
|
144
|
-
### `createAuth(config)`
|
|
145
|
-
|
|
146
|
-
创建认证系统实例(更灵活的配置选项)。
|
|
147
|
-
|
|
148
|
-
**参数:**
|
|
149
|
-
|
|
150
|
-
- `baseURL`: Better Auth API 基础 URL(必需)
|
|
151
|
-
- `tokenStorageKey?`: Token 存储的 localStorage key(默认: `'auth-token'`)
|
|
152
|
-
- `plugins?`: Better Auth 插件列表
|
|
153
|
-
- `callbackURLBuilder?`: 自定义回调 URL 构建函数
|
|
154
|
-
- `getSessionEndpoint?`: 获取 session 的端点路径(默认: `'/v1/auth/get-session'`)
|
|
155
|
-
- ~~`defaultRedirectAfterLogin?`~~: (已废弃) 建议在 SignInPage 组件中通过 `redirectUrl` prop 传入
|
|
156
|
-
- ~~`signInPath?`~~: (已废弃) 建议在 ProtectedRoute 组件中通过 `onUnauthorized` 回调处理导航
|
|
157
|
-
|
|
158
|
-
**返回:** `AuthInstance`
|
|
159
|
-
|
|
160
|
-
### `AuthInstance`
|
|
161
|
-
|
|
162
|
-
认证实例包含以下属性和方法:
|
|
163
|
-
|
|
164
|
-
**核心属性:**
|
|
165
|
-
|
|
166
|
-
- `authStore`: Legend State store(响应式状态)
|
|
167
|
-
- `authActions`: 认证操作(signIn, signOut, initialize 等)
|
|
168
|
-
- `authComputed`: 计算属性(isInitializing 等)
|
|
169
|
-
- `authClient`: Better Auth 客户端
|
|
170
|
-
- `tokenStorage`: Token 存储工具
|
|
171
|
-
|
|
172
|
-
**工具方法(已绑定到实例):**
|
|
173
|
-
|
|
174
|
-
- `getCurrentUser()`: 获取当前用户
|
|
175
|
-
- `getCurrentUserId()`: 获取当前用户ID
|
|
176
|
-
- `getCurrentUserIdSafe()`: 安全获取当前用户ID
|
|
177
|
-
- `isAuthenticated()`: 检查是否已认证
|
|
178
|
-
- `isLoading()`: 检查是否正在加载
|
|
179
|
-
- `isLoaded()`: 检查是否已加载
|
|
180
|
-
- `waitForAuth()`: 等待认证完成
|
|
181
|
-
- `getAuthToken()`: 获取认证 Token
|
|
182
|
-
- `getAuthHeaders()`: 获取认证 Headers
|
|
183
|
-
- `apiClient`: 预配置的 API 客户端(包含 get/post/put/delete 方法)
|
|
184
|
-
- `userManager`: 用户管理器(包含 getUser/getUserId 方法)
|
|
185
|
-
|
|
186
|
-
### `AuthSync`
|
|
187
|
-
|
|
188
|
-
Better Auth 认证状态同步组件。
|
|
189
|
-
|
|
190
|
-
**功能:**
|
|
82
|
+
### 认证状态同步
|
|
191
83
|
|
|
192
|
-
|
|
193
|
-
- 在用户登录成功后自动设置伴生团队(活动组织和团队)
|
|
194
|
-
- 使用响应式状态监听,确保状态同步的实时性
|
|
84
|
+
新用户登录后,使用 `setupCompanionTeam` 设置伴生组织和团队:
|
|
195
85
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
86
|
+
```typescript
|
|
87
|
+
import { setupCompanionTeam } from "@choiceform/shared-auth"
|
|
88
|
+
|
|
89
|
+
// 在认证成功后调用
|
|
90
|
+
setupCompanionTeam(auth, token, {
|
|
91
|
+
isNewUser: searchParams.get("isNew") === "true",
|
|
92
|
+
onComplete: () => {
|
|
93
|
+
// 刷新 session
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
```
|
|
205
97
|
|
|
206
|
-
|
|
98
|
+
## API
|
|
207
99
|
|
|
208
|
-
|
|
209
|
-
- `children`: React.ReactNode
|
|
210
|
-
- `onUnauthorized`: () => void - 当用户未认证时的回调函数(用于导航到登录页)
|
|
211
|
-
- `loadingComponent?`: React.ComponentType<{ message?: string }> - 自定义加载组件
|
|
212
|
-
- `loadingMessage?`: string - 加载中消息(默认: `'Checking authentication...'`)
|
|
100
|
+
### `initAuth(config)`
|
|
213
101
|
|
|
214
|
-
|
|
102
|
+
快速初始化(使用默认配置)。
|
|
215
103
|
|
|
216
|
-
|
|
104
|
+
| 参数 | 类型 | 说明 |
|
|
105
|
+
|------|------|------|
|
|
106
|
+
| baseURL | string | OneAuth API 地址 |
|
|
107
|
+
| tokenStorageKey | string | localStorage key(默认 `auth-token`) |
|
|
108
|
+
| plugins | BetterAuthPlugin[] | Better Auth 插件 |
|
|
217
109
|
|
|
218
|
-
|
|
110
|
+
### `createAuth(config)`
|
|
219
111
|
|
|
220
|
-
|
|
221
|
-
- `className?`: string - 自定义样式类名
|
|
222
|
-
- `title?`: string - 标题
|
|
223
|
-
- `description?`: string - 描述
|
|
224
|
-
- `provider?`: string - OAuth 提供商(默认: `'github'`)
|
|
225
|
-
- `redirectUrl?`: string - OAuth 回调重定向地址(默认: `'/explore'`)
|
|
226
|
-
- `onAuthSuccess?`: () => void - 认证成功后的回调函数(用于导航)
|
|
227
|
-
- `githubButton?`: (isSigningIn: boolean) => React.ReactNode - 自定义 GitHub 登录按钮渲染函数
|
|
228
|
-
- `beforeElement?`: React.ReactNode - 在登录表单之前渲染的元素
|
|
229
|
-
- `afterElement?`: React.ReactNode - 在登录表单之后渲染的元素
|
|
230
|
-
- `footerText?`: React.ReactNode - 页脚文本
|
|
112
|
+
创建认证实例(完整配置)。
|
|
231
113
|
|
|
232
|
-
|
|
114
|
+
### AuthInstance
|
|
233
115
|
|
|
234
|
-
|
|
116
|
+
| 属性 | 说明 |
|
|
117
|
+
|------|------|
|
|
118
|
+
| authStore | Legend State store |
|
|
119
|
+
| authActions | 认证操作(signIn, signOut 等) |
|
|
120
|
+
| authApi | 认证 API |
|
|
121
|
+
| organizationApi | 组织 API |
|
|
122
|
+
| teamApi | 团队 API |
|
|
123
|
+
| tokenStorage | Token 存储工具 |
|
|
235
124
|
|
|
236
|
-
|
|
125
|
+
### 工具方法
|
|
237
126
|
|
|
238
127
|
```typescript
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// 获取当前用户
|
|
128
|
+
// 获取用户
|
|
242
129
|
const user = auth.getCurrentUser()
|
|
243
|
-
|
|
244
|
-
// 获取当前用户ID
|
|
245
130
|
const userId = auth.getCurrentUserId()
|
|
246
|
-
const userIdSafe = auth.getCurrentUserIdSafe()
|
|
247
131
|
|
|
248
|
-
//
|
|
132
|
+
// 认证状态
|
|
249
133
|
const authenticated = auth.isAuthenticated()
|
|
250
134
|
const loading = auth.isLoading()
|
|
251
135
|
const loaded = auth.isLoaded()
|
|
@@ -253,300 +137,72 @@ const loaded = auth.isLoaded()
|
|
|
253
137
|
// 等待认证完成
|
|
254
138
|
await auth.waitForAuth()
|
|
255
139
|
|
|
256
|
-
//
|
|
257
|
-
const token =
|
|
258
|
-
|
|
259
|
-
// 获取认证 Headers
|
|
260
|
-
const headers = await auth.getAuthHeaders()
|
|
261
|
-
|
|
262
|
-
// 使用预配置的 API 客户端
|
|
263
|
-
const response = await auth.apiClient.get("/api/users")
|
|
264
|
-
const data = await auth.apiClient.post("/api/data", { name: "test" })
|
|
265
|
-
|
|
266
|
-
// 使用用户管理器
|
|
267
|
-
const user = auth.userManager.getUser()
|
|
268
|
-
const userId = auth.userManager.getUserId()
|
|
140
|
+
// Token
|
|
141
|
+
const token = auth.getAuthTokenSync()
|
|
142
|
+
const headers = auth.getAuthHeadersSync()
|
|
269
143
|
```
|
|
270
144
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
如果需要独立的工具函数(需要手动传入参数),可以从包中导入:
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
import {
|
|
277
|
-
getCurrentUser,
|
|
278
|
-
getCurrentUserId,
|
|
279
|
-
isAuthenticated,
|
|
280
|
-
getAuthToken,
|
|
281
|
-
getAuthHeaders,
|
|
282
|
-
createApiClient,
|
|
283
|
-
createUserManager,
|
|
284
|
-
} from "@choiceform/shared-auth"
|
|
285
|
-
|
|
286
|
-
// 使用独立的工具函数(需要传入 authStore 等参数)
|
|
287
|
-
const user = getCurrentUser(auth.authStore)
|
|
288
|
-
const userId = getCurrentUserId(auth.authStore)
|
|
289
|
-
const authenticated = isAuthenticated(auth.authStore)
|
|
290
|
-
|
|
291
|
-
// 创建 API 客户端
|
|
292
|
-
const apiClient = createApiClient(
|
|
293
|
-
auth.authStore,
|
|
294
|
-
auth.tokenStorage,
|
|
295
|
-
auth.authActions,
|
|
296
|
-
auth.authClient
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
// 创建用户管理器
|
|
300
|
-
const userManager = createUserManager(auth.authStore)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## 类型定义
|
|
145
|
+
## 类型
|
|
304
146
|
|
|
305
147
|
```typescript
|
|
306
148
|
import type {
|
|
307
149
|
SessionUser,
|
|
308
|
-
Session,
|
|
309
150
|
AuthState,
|
|
310
151
|
AuthConfig,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
152
|
+
Organization,
|
|
153
|
+
Team,
|
|
154
|
+
Member,
|
|
314
155
|
} from "@choiceform/shared-auth"
|
|
315
156
|
```
|
|
316
157
|
|
|
317
|
-
###
|
|
318
|
-
|
|
319
|
-
- `SessionUser`: 用户信息类型
|
|
320
|
-
- `Session`: 会话信息类型
|
|
321
|
-
- `AuthState`: 认证状态类型(包含 user, isAuthenticated, loading 等)
|
|
322
|
-
- `AuthConfig`: 认证配置类型
|
|
323
|
-
- `AuthInstance`: 认证实例类型(`initAuth` 或 `createAuth` 的返回值)
|
|
324
|
-
- `AuthActions`: 认证操作类型
|
|
325
|
-
- `TokenStorage`: Token 存储工具类型
|
|
326
|
-
|
|
327
|
-
## 环境变量
|
|
328
|
-
|
|
329
|
-
### 必需的环境变量
|
|
330
|
-
|
|
331
|
-
- `VITE_AUTH_API_URL`: Better Auth API 基础 URL
|
|
332
|
-
|
|
333
|
-
### 示例配置
|
|
334
|
-
|
|
335
|
-
参考项目根目录的 `.env.example` 文件:
|
|
336
|
-
|
|
337
|
-
```bash
|
|
338
|
-
# .env
|
|
339
|
-
VITE_AUTH_API_URL=http://localhost:4320
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### 向后兼容
|
|
343
|
-
|
|
344
|
-
如果你的项目仍在使用 `VITE_CORE_AI_API_URL`,代码会自动向后兼容:
|
|
158
|
+
### SessionUser
|
|
345
159
|
|
|
346
160
|
```typescript
|
|
347
|
-
|
|
161
|
+
interface SessionUser {
|
|
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
|
|
171
|
+
// ...
|
|
172
|
+
}
|
|
348
173
|
```
|
|
349
174
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
## 响应式状态使用
|
|
353
|
-
|
|
354
|
-
由于认证状态使用 Legend State 管理,你可以在 React 组件中使用 `use$` hook 来响应式地访问状态:
|
|
175
|
+
## 响应式状态
|
|
355
176
|
|
|
356
177
|
```typescript
|
|
357
178
|
import { use$ } from "@legendapp/state/react"
|
|
358
179
|
import { auth } from "./lib/auth"
|
|
359
180
|
|
|
360
|
-
function
|
|
361
|
-
// 响应式获取用户信息
|
|
181
|
+
function Profile() {
|
|
362
182
|
const user = use$(auth.authStore.user)
|
|
363
|
-
const isAuthenticated = use$(auth.authStore.isAuthenticated)
|
|
364
183
|
const loading = use$(auth.authStore.loading)
|
|
365
184
|
|
|
366
|
-
if (loading)
|
|
367
|
-
|
|
368
|
-
}
|
|
185
|
+
if (loading) return <Loading />
|
|
186
|
+
if (!user) return <SignIn />
|
|
369
187
|
|
|
370
|
-
|
|
371
|
-
return <div>Please sign in</div>
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return (
|
|
375
|
-
<div>
|
|
376
|
-
<h1>Welcome, {user?.name}</h1>
|
|
377
|
-
<p>Email: {user?.email}</p>
|
|
378
|
-
</div>
|
|
379
|
-
)
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### 使用计算属性
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
import { use$ } from "@legendapp/state/react"
|
|
387
|
-
import { auth } from "./lib/auth"
|
|
388
|
-
|
|
389
|
-
function AuthStatus() {
|
|
390
|
-
// 使用计算属性检查初始化状态
|
|
391
|
-
const isInitializing = use$(auth.authComputed.isInitializing)
|
|
392
|
-
|
|
393
|
-
if (isInitializing) {
|
|
394
|
-
return <div>Initializing...</div>
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return <div>Ready</div>
|
|
188
|
+
return <div>Hello, {user.name}</div>
|
|
398
189
|
}
|
|
399
190
|
```
|
|
400
191
|
|
|
401
|
-
## 最佳实践
|
|
402
|
-
|
|
403
|
-
### 1. 在组件中使用响应式状态
|
|
404
|
-
|
|
405
|
-
优先使用 `use$` hook 来访问响应式状态,而不是直接调用 `getCurrentUser()` 等方法:
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
// ✅ 推荐:响应式更新
|
|
409
|
-
const user = use$(auth.authStore.user)
|
|
410
|
-
|
|
411
|
-
// ❌ 不推荐:非响应式
|
|
412
|
-
const user = auth.getCurrentUser()
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### 2. 使用 API 客户端进行 API 调用
|
|
416
|
-
|
|
417
|
-
使用预配置的 `apiClient` 可以自动处理认证头和处理 401 响应:
|
|
418
|
-
|
|
419
|
-
```typescript
|
|
420
|
-
// ✅ 推荐:使用 apiClient
|
|
421
|
-
const response = await auth.apiClient.get("/api/users")
|
|
422
|
-
const data = await response.json()
|
|
423
|
-
|
|
424
|
-
// ❌ 不推荐:手动添加认证头
|
|
425
|
-
const headers = await auth.getAuthHeaders()
|
|
426
|
-
const response = await fetch("/api/users", { headers })
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### 3. 等待认证完成
|
|
430
|
-
|
|
431
|
-
在进行需要认证的操作前,使用 `waitForAuth()` 确保认证已初始化:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// ✅ 推荐:等待认证完成
|
|
435
|
-
await auth.waitForAuth()
|
|
436
|
-
if (auth.isAuthenticated()) {
|
|
437
|
-
// 执行需要认证的操作
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// ❌ 不推荐:直接检查状态(可能尚未初始化)
|
|
441
|
-
if (auth.isAuthenticated()) {
|
|
442
|
-
// 可能不准确
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### 4. 错误处理
|
|
447
|
-
|
|
448
|
-
认证操作可能会失败,确保适当处理错误:
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
try {
|
|
452
|
-
await auth.authActions.signIn("github", redirectUrl)
|
|
453
|
-
} catch (error) {
|
|
454
|
-
console.error("Sign in failed:", error)
|
|
455
|
-
// 显示错误提示给用户
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### 5. 退出登录
|
|
460
|
-
|
|
461
|
-
退出登录时使用 `signOut` 方法,可以选择性地传入重定向地址:
|
|
462
|
-
|
|
463
|
-
```typescript
|
|
464
|
-
async function handleSignOut() {
|
|
465
|
-
try {
|
|
466
|
-
// 传入重定向地址,登出后会自动跳转
|
|
467
|
-
await auth.authActions.signOut("/sign-in")
|
|
468
|
-
|
|
469
|
-
// 或者不传入地址,由调用方手动处理后续导航
|
|
470
|
-
// await auth.authActions.signOut()
|
|
471
|
-
// navigate("/sign-in")
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error("Sign out failed:", error)
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
## 常见问题
|
|
479
|
-
|
|
480
|
-
### Q: 如何在非 React 环境中使用?
|
|
481
|
-
|
|
482
|
-
A: 可以使用独立的工具函数,它们不依赖 React:
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
import { getCurrentUser, isAuthenticated } from "@choiceform/shared-auth"
|
|
486
|
-
|
|
487
|
-
const user = getCurrentUser(auth.authStore)
|
|
488
|
-
const authenticated = isAuthenticated(auth.authStore)
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### Q: 如何处理 Token 过期?
|
|
492
|
-
|
|
493
|
-
A: `apiClient` 会自动处理 401 响应,当检测到未授权时会调用 `handleUnauthorized`,通常会清除认证状态并重定向到登录页面。
|
|
494
|
-
|
|
495
|
-
### Q: 如何自定义认证流程?
|
|
496
|
-
|
|
497
|
-
A: 可以使用 `createAuth` 替代 `initAuth`,它提供更多配置选项,包括自定义回调 URL 构建函数等。
|
|
498
|
-
|
|
499
|
-
### Q: 伴生团队功能是什么?
|
|
500
|
-
|
|
501
|
-
A: 在用户登录成功后,`AuthSync` 组件会自动:
|
|
502
|
-
|
|
503
|
-
- 从 OneAuth API (`/v1/organizations/me`) 获取用户的组织信息
|
|
504
|
-
- 调用 better-auth API 设置活动组织 (`/v1/auth/organization/set-active`)
|
|
505
|
-
- 调用 better-auth API 设置活动团队 (`/v1/auth/organization/set-active-team`)
|
|
506
|
-
|
|
507
|
-
这确保了用户在登录后拥有正确的权限上下文。如果此功能失败,不会影响登录流程,只会在控制台记录错误信息。
|
|
508
|
-
|
|
509
|
-
### Q: 如何禁用伴生团队功能?
|
|
510
|
-
|
|
511
|
-
A: 不配置 `VITE_AUTH_API_URL` 环境变量即可。功能会静默跳过。
|
|
512
|
-
|
|
513
|
-
## Token 存储机制
|
|
514
|
-
|
|
515
|
-
### Token 编码
|
|
516
|
-
|
|
517
|
-
为了确保 token 在 localStorage 中安全存储,系统会在存储时自动使用 `encodeURIComponent` 进行编码:
|
|
518
|
-
|
|
519
|
-
- **存储时**:token 会自动编码后存储到 localStorage
|
|
520
|
-
- **读取时**:token 从 localStorage 读取后直接使用(编码后的值)
|
|
521
|
-
- **发送请求时**:在构建 Authorization header 时会再次使用 `encodeURIComponent` 编码
|
|
522
|
-
|
|
523
|
-
这种双重编码机制确保了:
|
|
524
|
-
|
|
525
|
-
1. localStorage 中存储的是编码后的安全值
|
|
526
|
-
2. API 请求时 token 会被正确编码传输
|
|
527
|
-
|
|
528
|
-
### Token 存储位置
|
|
529
|
-
|
|
530
|
-
Token 默认存储在 localStorage 中,key 为 `auth-token`(可通过 `tokenStorageKey` 配置项自定义)。
|
|
531
|
-
|
|
532
192
|
## 更新日志
|
|
533
193
|
|
|
534
|
-
### v0.
|
|
535
|
-
|
|
536
|
-
- 🔧 改进:Token 存储时使用 `encodeURIComponent` 编码,确保 localStorage 中安全存储
|
|
537
|
-
- 📝 改进:完善 Token 存储机制文档说明
|
|
538
|
-
|
|
539
|
-
### v0.1.2
|
|
194
|
+
### v0.2.0
|
|
540
195
|
|
|
541
|
-
-
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
196
|
+
- 服务器地址更换为 `https://oneauth.choiceform.io`
|
|
197
|
+
- 伴生组织/团队改从 session 获取(`inherentOrganizationId`、`inherentTeamId`)
|
|
198
|
+
- 新增 `onboard` API、Magic Link 支持
|
|
199
|
+
- 移除 UI 组件(`AuthSync`、`ProtectedRoute`、`SignInPage`),由业务端实现
|
|
200
|
+
- 代码清理和优化
|
|
545
201
|
|
|
546
|
-
### v0.1.
|
|
202
|
+
### v0.1.x
|
|
547
203
|
|
|
548
204
|
- 初始版本
|
|
549
205
|
|
|
550
|
-
##
|
|
206
|
+
## License
|
|
551
207
|
|
|
552
208
|
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 认证 API
|
|
3
|
+
*/
|
|
4
|
+
import type { MagicLinkRequest, SessionUser, SetActiveOrganizationRequest, SetActiveTeamRequest, UpdateUserRequest } from "../types";
|
|
5
|
+
import { type ApiClient } from "./client";
|
|
6
|
+
export declare function createAuthApi(apiClient: ApiClient, baseURL: string): {
|
|
7
|
+
getSession(token?: string): Promise<SessionUser | null>;
|
|
8
|
+
getSessionWithToken(token: string): Promise<SessionUser | null>;
|
|
9
|
+
setActiveOrganization(params: SetActiveOrganizationRequest, token?: string): Promise<void>;
|
|
10
|
+
setActiveTeam(params: SetActiveTeamRequest, token?: string): Promise<void>;
|
|
11
|
+
onboard(token: string): Promise<void>;
|
|
12
|
+
updateUser(data: UpdateUserRequest): Promise<SessionUser | null>;
|
|
13
|
+
sendMagicLink(params: MagicLinkRequest): Promise<{
|
|
14
|
+
status: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* 为已登录用户绑定密码
|
|
18
|
+
* 用于通过 OAuth 或 Magic Link 登录的用户设置密码
|
|
19
|
+
*/
|
|
20
|
+
linkCredential(newPassword: string, token?: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* 检查邮箱是否已注册
|
|
23
|
+
* 注意:此 API 可能不存在,调用时需要处理错误
|
|
24
|
+
*/
|
|
25
|
+
checkEmailExists(email: string): Promise<boolean>;
|
|
26
|
+
};
|
|
27
|
+
export type AuthApi = ReturnType<typeof createAuthApi>;
|
|
28
|
+
//# sourceMappingURL=auth-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-api.d.ts","sourceRoot":"","sources":["../../src/api/auth-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,4BAA4B,EAC5B,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAE7D,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;uBAEtC,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;+BAe5B,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;kCAgBjC,4BAA4B,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;0BAepE,oBAAoB,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;mBAe3D,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;qBAcpB,iBAAiB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;0BAQ1C,gBAAgB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAc3E;;;OAGG;gCAC+B,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxE;;;OAGG;4BAC2B,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;EAqB1D;AAED,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}
|