@choiceform/shared-auth 0.1.6 → 0.1.8
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 +27 -0
- package/dist/components/auth-sync.d.ts.map +1 -1
- package/dist/components/auth-sync.js +4 -6
- package/dist/components/sign-in-page.d.ts +5 -34
- package/dist/components/sign-in-page.d.ts.map +1 -1
- package/dist/components/sign-in-page.js +4 -4
- package/dist/hooks/use-auth-init.d.ts.map +1 -1
- package/dist/hooks/use-auth-init.js +4 -1
- package/dist/store/actions.js +1 -1
- package/dist/store/state.d.ts.map +1 -1
- package/dist/store/state.js +1 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ VITE_AUTH_API_URL=http://localhost:4320
|
|
|
36
36
|
### 伴生团队功能
|
|
37
37
|
|
|
38
38
|
`AuthSync` 组件会在用户登录成功后自动:
|
|
39
|
+
|
|
39
40
|
1. 从 OneAuth API 获取用户的组织信息
|
|
40
41
|
2. 设置活动组织
|
|
41
42
|
3. 设置活动团队(如果存在)
|
|
@@ -170,6 +171,7 @@ function LoginPage() {
|
|
|
170
171
|
Better Auth 认证状态同步组件。
|
|
171
172
|
|
|
172
173
|
**功能:**
|
|
174
|
+
|
|
173
175
|
- 同步 better-auth 的 session 状态到应用 store
|
|
174
176
|
- 在用户登录成功后自动设置伴生团队(活动组织和团队)
|
|
175
177
|
- 使用响应式状态监听,确保状态同步的实时性
|
|
@@ -475,6 +477,7 @@ A: 可以使用 `createAuth` 替代 `initAuth`,它提供更多配置选项,
|
|
|
475
477
|
### Q: 伴生团队功能是什么?
|
|
476
478
|
|
|
477
479
|
A: 在用户登录成功后,`AuthSync` 组件会自动:
|
|
480
|
+
|
|
478
481
|
- 从 OneAuth API (`/v1/organizations/me`) 获取用户的组织信息
|
|
479
482
|
- 调用 better-auth API 设置活动组织 (`/v1/auth/organization/set-active`)
|
|
480
483
|
- 调用 better-auth API 设置活动团队 (`/v1/auth/organization/set-active-team`)
|
|
@@ -485,8 +488,32 @@ A: 在用户登录成功后,`AuthSync` 组件会自动:
|
|
|
485
488
|
|
|
486
489
|
A: 不配置 `VITE_AUTH_API_URL` 环境变量即可。功能会静默跳过。
|
|
487
490
|
|
|
491
|
+
## Token 存储机制
|
|
492
|
+
|
|
493
|
+
### Token 编码
|
|
494
|
+
|
|
495
|
+
为了确保 token 在 localStorage 中安全存储,系统会在存储时自动使用 `encodeURIComponent` 进行编码:
|
|
496
|
+
|
|
497
|
+
- **存储时**:token 会自动编码后存储到 localStorage
|
|
498
|
+
- **读取时**:token 从 localStorage 读取后直接使用(编码后的值)
|
|
499
|
+
- **发送请求时**:在构建 Authorization header 时会再次使用 `encodeURIComponent` 编码
|
|
500
|
+
|
|
501
|
+
这种双重编码机制确保了:
|
|
502
|
+
|
|
503
|
+
1. localStorage 中存储的是编码后的安全值
|
|
504
|
+
2. API 请求时 token 会被正确编码传输
|
|
505
|
+
|
|
506
|
+
### Token 存储位置
|
|
507
|
+
|
|
508
|
+
Token 默认存储在 localStorage 中,key 为 `auth-token`(可通过 `tokenStorageKey` 配置项自定义)。
|
|
509
|
+
|
|
488
510
|
## 更新日志
|
|
489
511
|
|
|
512
|
+
### v0.1.6
|
|
513
|
+
|
|
514
|
+
- 🔧 改进:Token 存储时使用 `encodeURIComponent` 编码,确保 localStorage 中安全存储
|
|
515
|
+
- 📝 改进:完善 Token 存储机制文档说明
|
|
516
|
+
|
|
490
517
|
### v0.1.2
|
|
491
518
|
|
|
492
519
|
- ✨ 新增:登录时自动获取并设置伴生团队功能
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAoS3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA0FxD"}
|
|
@@ -114,8 +114,6 @@ async function setupCompanionTeam(auth, token, refetchSession) {
|
|
|
114
114
|
}
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
// 4. Token 编码(与 fetchSessionWithToken 保持一致)
|
|
118
|
-
const encodedToken = encodeURIComponent(oneAuthToken);
|
|
119
117
|
// 5. 获取组织信息
|
|
120
118
|
const myOrganizationUrl = `${oneAuthBaseUrl}/v1/organizations/me`;
|
|
121
119
|
if (isDev) {
|
|
@@ -123,7 +121,7 @@ async function setupCompanionTeam(auth, token, refetchSession) {
|
|
|
123
121
|
}
|
|
124
122
|
const orgResponse = await fetch(myOrganizationUrl, {
|
|
125
123
|
headers: {
|
|
126
|
-
Authorization: `Bearer ${
|
|
124
|
+
Authorization: `Bearer ${oneAuthToken}`,
|
|
127
125
|
"Content-Type": "application/json",
|
|
128
126
|
},
|
|
129
127
|
});
|
|
@@ -158,7 +156,7 @@ async function setupCompanionTeam(auth, token, refetchSession) {
|
|
|
158
156
|
const setActiveOrgResponse = await fetch(setActiveOrgUrl, {
|
|
159
157
|
method: "POST",
|
|
160
158
|
headers: {
|
|
161
|
-
Authorization: `Bearer ${
|
|
159
|
+
Authorization: `Bearer ${oneAuthToken}`,
|
|
162
160
|
"Content-Type": "application/json",
|
|
163
161
|
},
|
|
164
162
|
body: JSON.stringify({ organizationId: organization.id }),
|
|
@@ -178,7 +176,7 @@ async function setupCompanionTeam(auth, token, refetchSession) {
|
|
|
178
176
|
const setActiveTeamResponse = await fetch(setActiveTeamUrl, {
|
|
179
177
|
method: "POST",
|
|
180
178
|
headers: {
|
|
181
|
-
Authorization: `Bearer ${
|
|
179
|
+
Authorization: `Bearer ${oneAuthToken}`,
|
|
182
180
|
"Content-Type": "application/json",
|
|
183
181
|
},
|
|
184
182
|
body: JSON.stringify({ teamId: firstTeam.id }),
|
|
@@ -199,7 +197,7 @@ async function setupCompanionTeam(auth, token, refetchSession) {
|
|
|
199
197
|
const getSessionUrl = `${authBaseURL}/v1/auth/get-session`;
|
|
200
198
|
const sessionResponse = await fetch(getSessionUrl, {
|
|
201
199
|
headers: {
|
|
202
|
-
Authorization: `Bearer ${
|
|
200
|
+
Authorization: `Bearer ${oneAuthToken}`,
|
|
203
201
|
"Content-Type": "application/json",
|
|
204
202
|
},
|
|
205
203
|
});
|
|
@@ -1,49 +1,20 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { AuthInstance } from "../core";
|
|
3
3
|
interface SignInPageProps {
|
|
4
|
+
afterElement?: React.ReactNode;
|
|
4
5
|
auth: AuthInstance;
|
|
5
|
-
|
|
6
|
-
* 自定义样式类名
|
|
7
|
-
*/
|
|
6
|
+
beforeElement?: React.ReactNode;
|
|
8
7
|
className?: string;
|
|
9
|
-
/**
|
|
10
|
-
* 描述
|
|
11
|
-
*/
|
|
12
8
|
description?: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
logo?: React.ReactNode;
|
|
17
|
-
/**
|
|
18
|
-
* OAuth 提供商
|
|
19
|
-
* @default 'github'
|
|
20
|
-
*/
|
|
9
|
+
footerText?: React.ReactNode;
|
|
10
|
+
githubButton?: (isSigningIn: boolean) => React.ReactNode;
|
|
21
11
|
provider?: string;
|
|
22
|
-
/**
|
|
23
|
-
* 登录成功后的重定向路径
|
|
24
|
-
* @default '/explore'
|
|
25
|
-
*/
|
|
26
12
|
redirectAfterLogin?: string;
|
|
27
|
-
/**
|
|
28
|
-
* 登录按钮图标
|
|
29
|
-
*/
|
|
30
|
-
signInButtonIcon?: React.ReactNode;
|
|
31
|
-
/**
|
|
32
|
-
* 登录按钮文本
|
|
33
|
-
*/
|
|
34
|
-
signInButtonText?: string;
|
|
35
|
-
/**
|
|
36
|
-
* 登录中按钮文本
|
|
37
|
-
*/
|
|
38
|
-
signingInButtonText?: string;
|
|
39
|
-
/**
|
|
40
|
-
* 标题
|
|
41
|
-
*/
|
|
42
13
|
title?: string;
|
|
43
14
|
}
|
|
44
15
|
/**
|
|
45
16
|
* 登录页面组件
|
|
46
17
|
*/
|
|
47
|
-
export declare function SignInPage({ auth, redirectAfterLogin, provider,
|
|
18
|
+
export declare function SignInPage({ afterElement, auth, beforeElement, redirectAfterLogin, provider, title, description, githubButton, className, footerText, }: SignInPageProps): import("react/jsx-runtime").JSX.Element;
|
|
48
19
|
export {};
|
|
49
20
|
//# sourceMappingURL=sign-in-page.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign-in-page.d.ts","sourceRoot":"","sources":["../../src/components/sign-in-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sign-in-page.d.ts","sourceRoot":"","sources":["../../src/components/sign-in-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,eAAe;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAY,EACZ,IAAI,EACJ,aAAa,EACb,kBAA+B,EAC/B,QAAmB,EACnB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,GACX,EAAE,eAAe,2CAgDjB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@choiceform/design-system";
|
|
3
|
+
import { use$ } from "@legendapp/state/react";
|
|
2
4
|
import { useEffect, useState } from "react";
|
|
3
5
|
import { useNavigate } from "react-router";
|
|
4
|
-
import { use$ } from "@legendapp/state/react";
|
|
5
|
-
import { Button } from "@choiceform/design-system";
|
|
6
6
|
/**
|
|
7
7
|
* 登录页面组件
|
|
8
8
|
*/
|
|
9
|
-
export function SignInPage({ auth, redirectAfterLogin = "/explore", provider = "github",
|
|
9
|
+
export function SignInPage({ afterElement, auth, beforeElement, redirectAfterLogin = "/explore", provider = "github", title, description, githubButton, className, footerText, }) {
|
|
10
10
|
const navigate = useNavigate();
|
|
11
11
|
const { authStore, authActions } = auth;
|
|
12
12
|
const { isAuthenticated, error } = use$(authStore);
|
|
@@ -29,5 +29,5 @@ export function SignInPage({ auth, redirectAfterLogin = "/explore", provider = "
|
|
|
29
29
|
setIsSigningIn(false);
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
return (
|
|
32
|
+
return (_jsxs("div", { className: className, children: [beforeElement, title && (_jsxs("div", { className: "flex flex-col gap-2", children: [title && _jsx("p", { className: "text-heading-large", children: title }), description && (_jsx("p", { className: "text-secondary-foreground text-body-large", children: description }))] })), provider === "github" && (_jsx(Slot, { onClick: handleSignIn, children: githubButton?.(isSigningIn) })), error && _jsx("p", { className: "text-danger-foreground mt-2", children: error }), footerText, afterElement] }));
|
|
33
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-auth-init.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth-init.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"use-auth-init.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth-init.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,QAyC7C"}
|
|
@@ -9,8 +9,11 @@ export function useAuthInit(auth) {
|
|
|
9
9
|
const { authActions, tokenStorage } = auth;
|
|
10
10
|
// 检查 URL 中的 token
|
|
11
11
|
const urlParams = new URLSearchParams(window.location.search);
|
|
12
|
-
|
|
12
|
+
let tokenFromUrl = urlParams.get("token");
|
|
13
13
|
if (tokenFromUrl) {
|
|
14
|
+
if (decodeURIComponent(tokenFromUrl) === tokenFromUrl) {
|
|
15
|
+
tokenFromUrl = encodeURIComponent(tokenFromUrl);
|
|
16
|
+
}
|
|
14
17
|
// 立即清理 URL 中的 token 参数(避免暴露)
|
|
15
18
|
urlParams.delete("token");
|
|
16
19
|
const newUrl = urlParams.toString()
|
package/dist/store/actions.js
CHANGED
|
@@ -104,7 +104,7 @@ export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
|
104
104
|
const response = await fetch(endpoint, {
|
|
105
105
|
method: "GET",
|
|
106
106
|
headers: {
|
|
107
|
-
Authorization: `Bearer ${
|
|
107
|
+
Authorization: `Bearer ${token}`,
|
|
108
108
|
"Content-Type": "application/json",
|
|
109
109
|
},
|
|
110
110
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/store/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE;;;oBAyBnD,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/store/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE;;;oBAyBnD,MAAM,GAAG,IAAI;eAclB,MAAM,GAAG,IAAI;;;EAavB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,cAAc,CAAC,CAAA"}
|
package/dist/store/state.js
CHANGED
|
@@ -29,8 +29,7 @@ export function createAuthStore(config) {
|
|
|
29
29
|
save(token) {
|
|
30
30
|
try {
|
|
31
31
|
if (token) {
|
|
32
|
-
|
|
33
|
-
localStorage.setItem(tokenStorageKey, encodedToken);
|
|
32
|
+
localStorage.setItem(tokenStorageKey, token);
|
|
34
33
|
authStore.token.set(token);
|
|
35
34
|
}
|
|
36
35
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@choiceform/shared-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Shared authentication package for Choiceform projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"dev": "tsc --watch",
|
|
22
22
|
"clean": "rimraf dist",
|
|
23
|
-
"prepublishOnly": "pnpm run build"
|
|
23
|
+
"prepublishOnly": "pnpm run build",
|
|
24
|
+
"publish": "npm publish"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|