@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 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;AAuS3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA0FxD"}
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 ${encodedToken}`,
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 ${encodedToken}`,
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 ${encodedToken}`,
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 ${encodedToken}`,
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
- * 自定义 Logo 组件
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, logo, title, description, signInButtonText, signingInButtonText, signInButtonIcon, className, }: SignInPageProps): import("react/jsx-runtime").JSX.Element;
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":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,eAAe;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;OAEG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAClC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,kBAA+B,EAC/B,QAAmB,EACnB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,gBAAwC,EACxC,mBAAgD,EAChD,gBAAgB,EAChB,SAAS,GACV,EAAE,eAAe,2CAyDjB"}
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", logo, title, description, signInButtonText = "Sign in with GitHub", signingInButtonText = "Redirecting to GitHub...", signInButtonIcon, className, }) {
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 (_jsx("div", { className: className || "flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900", children: _jsxs("div", { className: "w-full max-w-md space-y-8", children: [logo && (_jsxs("div", { className: "mb-8", children: [_jsx("div", { className: "flex items-center gap-2", children: logo }), title && (_jsxs("div", { className: "p-4", children: [title && _jsx("p", { className: "text-heading-large mt-2", children: title }), description && (_jsx("p", { className: "text-secondary-foreground text-body-large mt-2", children: description }))] }))] })), _jsxs("div", { className: "flex flex-col items-start justify-start gap-2 p-4", children: [_jsxs(Button, { onClick: handleSignIn, disabled: isSigningIn, loading: isSigningIn, size: "large", children: [signInButtonIcon && _jsx("span", { className: "flex items-center", children: signInButtonIcon }), _jsx("span", { children: isSigningIn ? signingInButtonText : signInButtonText })] }), error && _jsx("p", { className: "text-danger-foreground", children: error })] })] }) }));
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,QAqC7C"}
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
- const tokenFromUrl = urlParams.get("token");
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()
@@ -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 ${encodeURIComponent(token)}`,
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;eAelB,MAAM,GAAG,IAAI;;;EAavB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,cAAc,CAAC,CAAA"}
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"}
@@ -29,8 +29,7 @@ export function createAuthStore(config) {
29
29
  save(token) {
30
30
  try {
31
31
  if (token) {
32
- const encodedToken = encodeURIComponent(token);
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.6",
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",