@lobehub/lobehub 2.0.0-next.354 → 2.0.0-next.356

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.
Files changed (176) hide show
  1. package/.env.desktop +0 -1
  2. package/.env.example +16 -20
  3. package/.env.example.development +1 -4
  4. package/.github/workflows/e2e.yml +10 -11
  5. package/CHANGELOG.md +60 -0
  6. package/Dockerfile +28 -4
  7. package/changelog/v1.json +18 -0
  8. package/docker-compose/local/docker-compose.yml +2 -2
  9. package/docker-compose/local/grafana/docker-compose.yml +2 -2
  10. package/docker-compose/local/logto/docker-compose.yml +2 -2
  11. package/docker-compose/local/zitadel/.env.example +2 -2
  12. package/docker-compose/local/zitadel/.env.zh-CN.example +2 -2
  13. package/docker-compose/production/grafana/docker-compose.yml +2 -2
  14. package/docker-compose/production/logto/.env.example +2 -2
  15. package/docker-compose/production/logto/.env.zh-CN.example +2 -2
  16. package/docker-compose/production/zitadel/.env.example +2 -2
  17. package/docker-compose/production/zitadel/.env.zh-CN.example +2 -2
  18. package/docs/development/basic/add-new-authentication-providers.mdx +144 -136
  19. package/docs/development/basic/add-new-authentication-providers.zh-CN.mdx +146 -136
  20. package/docs/self-hosting/advanced/auth/legacy.mdx +4 -0
  21. package/docs/self-hosting/advanced/auth/legacy.zh-CN.mdx +4 -0
  22. package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +326 -0
  23. package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +323 -0
  24. package/docs/self-hosting/advanced/auth.mdx +43 -16
  25. package/docs/self-hosting/advanced/auth.zh-CN.mdx +44 -16
  26. package/docs/self-hosting/advanced/redis/upstash.mdx +69 -0
  27. package/docs/self-hosting/advanced/redis/upstash.zh-CN.mdx +69 -0
  28. package/docs/self-hosting/advanced/redis.mdx +128 -0
  29. package/docs/self-hosting/advanced/redis.zh-CN.mdx +126 -0
  30. package/docs/self-hosting/environment-variables/auth.mdx +15 -1
  31. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +15 -1
  32. package/docs/self-hosting/environment-variables/basic.mdx +13 -0
  33. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +13 -0
  34. package/docs/self-hosting/environment-variables/redis.mdx +68 -0
  35. package/docs/self-hosting/environment-variables/redis.zh-CN.mdx +67 -0
  36. package/docs/self-hosting/migration/v2/breaking-changes.mdx +23 -23
  37. package/docs/self-hosting/migration/v2/breaking-changes.zh-CN.mdx +23 -23
  38. package/docs/self-hosting/server-database/docker-compose.mdx +4 -4
  39. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +4 -4
  40. package/e2e/CLAUDE.md +5 -6
  41. package/e2e/docs/local-setup.md +9 -12
  42. package/e2e/scripts/setup.ts +9 -15
  43. package/e2e/src/support/webServer.ts +6 -5
  44. package/locales/en-US/plugin.json +3 -0
  45. package/locales/zh-CN/plugin.json +3 -0
  46. package/package.json +4 -6
  47. package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
  48. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
  49. package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
  50. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
  51. package/packages/database/src/schemas/nextauth.ts +7 -2
  52. package/packages/utils/src/server/__tests__/auth.test.ts +1 -63
  53. package/packages/utils/src/server/auth.ts +8 -24
  54. package/scripts/_shared/checkDeprecatedAuth.js +99 -0
  55. package/scripts/clerk-to-betterauth/index.ts +8 -3
  56. package/scripts/nextauth-to-betterauth/_internal/config.ts +41 -0
  57. package/scripts/nextauth-to-betterauth/_internal/db.ts +32 -0
  58. package/scripts/nextauth-to-betterauth/_internal/env.ts +6 -0
  59. package/scripts/nextauth-to-betterauth/index.ts +226 -0
  60. package/scripts/nextauth-to-betterauth/verify.ts +188 -0
  61. package/scripts/prebuild.mts +66 -13
  62. package/scripts/serverLauncher/startServer.js +5 -5
  63. package/src/app/(backend)/api/auth/[...all]/route.ts +5 -23
  64. package/src/app/(backend)/api/webhooks/casdoor/route.ts +5 -5
  65. package/src/app/(backend)/api/webhooks/logto/route.ts +8 -8
  66. package/src/app/(backend)/middleware/auth/index.test.ts +8 -1
  67. package/src/app/(backend)/middleware/auth/index.ts +6 -15
  68. package/src/app/(backend)/middleware/auth/utils.test.ts +0 -32
  69. package/src/app/(backend)/middleware/auth/utils.ts +3 -8
  70. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +8 -1
  71. package/src/app/(backend)/webapi/create-image/comfyui/route.ts +0 -1
  72. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -1
  73. package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +1 -1
  74. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +4 -17
  75. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
  76. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +34 -21
  77. package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
  78. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
  79. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
  80. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
  81. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
  82. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
  83. package/src/app/[variants]/(main)/settings/profile/features/SSOProvidersList/index.tsx +12 -19
  84. package/src/app/[variants]/(main)/settings/profile/index.tsx +8 -14
  85. package/src/components/{NextAuth/AuthIcons.tsx → AuthIcons.tsx} +8 -10
  86. package/src/envs/auth.ts +12 -51
  87. package/src/envs/email.ts +3 -0
  88. package/src/envs/redis.ts +12 -54
  89. package/src/features/ChatInput/ChatInputProvider.tsx +22 -2
  90. package/src/features/ChatInput/InputEditor/index.tsx +14 -3
  91. package/src/features/ChatInput/store/initialState.ts +2 -0
  92. package/src/features/User/__tests__/PanelContent.test.tsx +0 -11
  93. package/src/features/User/__tests__/UserAvatar.test.tsx +1 -16
  94. package/src/layout/AuthProvider/index.tsx +1 -6
  95. package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -4
  96. package/src/libs/better-auth/define-config.ts +2 -0
  97. package/src/libs/better-auth/plugins/email-whitelist.test.ts +120 -0
  98. package/src/libs/better-auth/plugins/email-whitelist.ts +62 -0
  99. package/src/libs/next/config/define-config.ts +13 -1
  100. package/src/libs/next/proxy/define-config.ts +2 -75
  101. package/src/libs/oidc-provider/provider.test.ts +0 -4
  102. package/src/libs/redis/index.ts +0 -1
  103. package/src/libs/redis/manager.test.ts +9 -45
  104. package/src/libs/redis/manager.ts +2 -16
  105. package/src/libs/redis/redis.test.ts +2 -4
  106. package/src/libs/redis/redis.ts +2 -4
  107. package/src/libs/redis/types.ts +2 -24
  108. package/src/libs/redis/utils.test.ts +0 -10
  109. package/src/libs/redis/utils.ts +0 -19
  110. package/src/libs/trpc/lambda/context.test.ts +0 -13
  111. package/src/libs/trpc/lambda/context.ts +21 -59
  112. package/src/libs/trpc/middleware/userAuth.ts +1 -7
  113. package/src/libs/trusted-client/getSessionUser.ts +15 -35
  114. package/src/locales/default/plugin.ts +3 -0
  115. package/src/server/globalConfig/index.ts +1 -3
  116. package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
  117. package/src/server/routers/lambda/__tests__/user.test.ts +0 -48
  118. package/src/server/routers/lambda/user.ts +1 -12
  119. package/src/server/services/email/impls/nodemailer/index.ts +2 -2
  120. package/src/server/services/webhookUser/index.ts +88 -0
  121. package/src/services/chat/chat.test.ts +19 -19
  122. package/src/services/chat/index.ts +8 -3
  123. package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
  124. package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
  125. package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
  126. package/src/services/chat/mecha/contextEngineering.ts +12 -0
  127. package/src/services/chat/types.ts +7 -1
  128. package/src/services/user/index.test.ts +0 -14
  129. package/src/services/user/index.ts +0 -4
  130. package/src/store/chat/agents/createAgentExecutors.ts +15 -4
  131. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
  132. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
  133. package/src/store/user/slices/auth/action.test.ts +22 -126
  134. package/src/store/user/slices/auth/action.ts +32 -65
  135. package/src/store/user/slices/auth/initialState.ts +0 -3
  136. package/src/store/user/slices/auth/selectors.ts +0 -3
  137. package/tests/setup.ts +10 -0
  138. package/scripts/_shared/checkDeprecatedClerkEnv.js +0 -42
  139. package/src/app/(backend)/api/auth/adapter/route.ts +0 -137
  140. package/src/app/[variants]/(auth)/next-auth/error/AuthErrorPage.tsx +0 -40
  141. package/src/app/[variants]/(auth)/next-auth/error/page.tsx +0 -11
  142. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +0 -167
  143. package/src/app/[variants]/(auth)/next-auth/signin/page.tsx +0 -11
  144. package/src/app/[variants]/(auth)/reset-password/layout.tsx +0 -12
  145. package/src/app/[variants]/(auth)/signin/layout.tsx +0 -12
  146. package/src/app/[variants]/(auth)/verify-email/layout.tsx +0 -12
  147. package/src/envs/auth.test.ts +0 -47
  148. package/src/layout/AuthProvider/NextAuth/UserUpdater.tsx +0 -44
  149. package/src/layout/AuthProvider/NextAuth/index.tsx +0 -17
  150. package/src/libs/next-auth/adapter/index.ts +0 -177
  151. package/src/libs/next-auth/auth.config.ts +0 -64
  152. package/src/libs/next-auth/index.ts +0 -20
  153. package/src/libs/next-auth/sso-providers/auth0.ts +0 -24
  154. package/src/libs/next-auth/sso-providers/authelia.ts +0 -39
  155. package/src/libs/next-auth/sso-providers/authentik.ts +0 -25
  156. package/src/libs/next-auth/sso-providers/casdoor.ts +0 -50
  157. package/src/libs/next-auth/sso-providers/cloudflare-zero-trust.ts +0 -34
  158. package/src/libs/next-auth/sso-providers/cognito.ts +0 -8
  159. package/src/libs/next-auth/sso-providers/feishu.ts +0 -83
  160. package/src/libs/next-auth/sso-providers/generic-oidc.ts +0 -38
  161. package/src/libs/next-auth/sso-providers/github.ts +0 -23
  162. package/src/libs/next-auth/sso-providers/google.ts +0 -18
  163. package/src/libs/next-auth/sso-providers/index.ts +0 -35
  164. package/src/libs/next-auth/sso-providers/keycloak.ts +0 -22
  165. package/src/libs/next-auth/sso-providers/logto.ts +0 -48
  166. package/src/libs/next-auth/sso-providers/microsoft-entra-id-helper.ts +0 -29
  167. package/src/libs/next-auth/sso-providers/microsoft-entra-id.ts +0 -19
  168. package/src/libs/next-auth/sso-providers/okta.ts +0 -22
  169. package/src/libs/next-auth/sso-providers/sso.config.ts +0 -8
  170. package/src/libs/next-auth/sso-providers/wechat.ts +0 -36
  171. package/src/libs/next-auth/sso-providers/zitadel.ts +0 -21
  172. package/src/libs/redis/upstash.test.ts +0 -158
  173. package/src/libs/redis/upstash.ts +0 -136
  174. package/src/server/services/nextAuthUser/index.ts +0 -318
  175. package/src/server/services/nextAuthUser/utils.ts +0 -62
  176. package/src/types/next-auth.d.ts +0 -26
@@ -27,10 +27,6 @@ export class UserService {
27
27
  return lambdaClient.user.getUserSSOProviders.query();
28
28
  };
29
29
 
30
- unlinkSSOProvider = async (provider: string, providerAccountId: string) => {
31
- return lambdaClient.user.unlinkSSOProvider.mutate({ provider, providerAccountId });
32
- };
33
-
34
30
  makeUserOnboarded = async () => {
35
31
  return lambdaClient.user.makeUserOnboarded.mutate();
36
32
  };
@@ -22,10 +22,9 @@ import type { ChatToolPayload, ConversationContext, CreateMessageParams } from '
22
22
  import debug from 'debug';
23
23
  import pMap from 'p-map';
24
24
 
25
- import type { ResolvedAgentConfig } from '@/services/chat/mecha';
26
-
27
25
  import { LOADING_FLAT } from '@/const/message';
28
26
  import { aiAgentService } from '@/services/aiAgent';
27
+ import type { ResolvedAgentConfig } from '@/services/chat/mecha';
29
28
  import { agentByIdSelectors } from '@/store/agent/selectors';
30
29
  import { getAgentStoreState } from '@/store/agent/store';
31
30
  import type { ChatStore } from '@/store/chat/store';
@@ -96,7 +95,12 @@ export const createAgentExecutors = (context: {
96
95
  const llmPayload = (instruction as AgentInstructionCallLlm)
97
96
  .payload as GeneralAgentCallLLMInstructionPayload;
98
97
 
99
- log(`${stagePrefix} Starting session`);
98
+ log(
99
+ `${stagePrefix} Starting session. Input: state.messages=%d, llmPayload.messages=%d, messageKey=%s`,
100
+ state.messages.length,
101
+ llmPayload.messages.length,
102
+ context.messageKey,
103
+ );
100
104
 
101
105
  let assistantMessageId: string;
102
106
 
@@ -184,6 +188,12 @@ export const createAgentExecutors = (context: {
184
188
  // Get latest messages from store (already updated by internal_fetchAIChatMessage)
185
189
  const latestMessages = context.get().dbMessagesMap[context.messageKey] || [];
186
190
 
191
+ log(
192
+ `${stagePrefix} After fetch: dbMessagesMap[${context.messageKey}]=%d messages, available keys=%o`,
193
+ latestMessages.length,
194
+ Object.keys(context.get().dbMessagesMap),
195
+ );
196
+
187
197
  // Get updated assistant message to extract usage/cost information
188
198
  const assistantMessage = latestMessages.find((m) => m.id === assistantMessageId);
189
199
 
@@ -206,10 +216,11 @@ export const createAgentExecutors = (context: {
206
216
  }
207
217
 
208
218
  log(
209
- '[%s:%d] call_llm completed, finishType: %s',
219
+ '[%s:%d] call_llm completed, finishType: %s, outputMessages: %d',
210
220
  state.operationId,
211
221
  state.stepCount,
212
222
  finishType,
223
+ latestMessages.length,
213
224
  );
214
225
 
215
226
  // Accumulate usage and cost to state
@@ -259,6 +259,7 @@ export const conversationLifecycle: StateCreator<
259
259
  if (data?.topics) {
260
260
  const pageSize = systemStatusSelectors.topicPageSize(useGlobalStore.getState());
261
261
  get().internal_updateTopics(operationContext.agentId, {
262
+ groupId: operationContext.groupId,
262
263
  items: data.topics.items,
263
264
  pageSize,
264
265
  total: data.topics.total,
@@ -757,20 +757,24 @@ export const streamingExecutor: StateCreator<
757
757
  nextContext = { ...nextContext, stepContext };
758
758
 
759
759
  log(
760
- '[internal_execAgentRuntime][step-%d]: phase=%s, status=%s, stepContext=%O',
760
+ '[internal_execAgentRuntime][step-%d]: phase=%s, status=%s, state.messages=%d, dbMessagesMap[%s]=%d, stepContext=%O',
761
761
  stepCount,
762
762
  nextContext.phase,
763
763
  state.status,
764
+ state.messages.length,
765
+ messageKey,
766
+ currentDBMessages.length,
764
767
  stepContext,
765
768
  );
766
769
 
767
770
  const result = await runtime.step(state, nextContext);
768
771
 
769
772
  log(
770
- '[internal_execAgentRuntime] Step %d completed, events: %d, newStatus=%s',
773
+ '[internal_execAgentRuntime] Step %d completed, events: %d, newStatus=%s, newState.messages=%d',
771
774
  stepCount,
772
775
  result.events.length,
773
776
  result.newState.status,
777
+ result.newState.messages.length,
774
778
  );
775
779
 
776
780
  // After parallel tool batch completes, refresh messages to ensure all tool results are synced
@@ -15,29 +15,6 @@ vi.mock('@/libs/swr', async () => {
15
15
  };
16
16
  });
17
17
 
18
- // Use vi.hoisted to ensure variables exist before vi.mock factory executes
19
- const { enableNextAuth, enableBetterAuth } = vi.hoisted(() => ({
20
- enableNextAuth: { value: false },
21
- enableBetterAuth: { value: false },
22
- }));
23
-
24
- vi.mock('@/envs/auth', () => ({
25
- get enableNextAuth() {
26
- return enableNextAuth.value;
27
- },
28
- get enableBetterAuth() {
29
- return enableBetterAuth.value;
30
- },
31
- }));
32
-
33
- const mockUserService = vi.hoisted(() => ({
34
- getUserSSOProviders: vi.fn().mockResolvedValue([]),
35
- }));
36
-
37
- vi.mock('@/services/user', () => ({
38
- userService: mockUserService,
39
- }));
40
-
41
18
  const mockBetterAuthClient = vi.hoisted(() => ({
42
19
  listAccounts: vi.fn().mockResolvedValue({ data: [] }),
43
20
  accountInfo: vi.fn().mockResolvedValue({ data: { user: {} } }),
@@ -50,9 +27,6 @@ afterEach(() => {
50
27
  vi.restoreAllMocks();
51
28
  vi.clearAllMocks();
52
29
 
53
- enableNextAuth.value = false;
54
- enableBetterAuth.value = false;
55
-
56
30
  // Reset store state
57
31
  useUserStore.setState({
58
32
  isLoadedAuthProviders: false,
@@ -61,16 +35,6 @@ afterEach(() => {
61
35
  });
62
36
  });
63
37
 
64
- /**
65
- * Mock nextauth 库相关方法
66
- */
67
- vi.mock('next-auth/react', async () => {
68
- return {
69
- signIn: vi.fn(),
70
- signOut: vi.fn(),
71
- };
72
- });
73
-
74
38
  describe('createAuthSlice', () => {
75
39
  describe('refreshUserState', () => {
76
40
  it('should refresh user config', async () => {
@@ -85,64 +49,19 @@ describe('createAuthSlice', () => {
85
49
  });
86
50
 
87
51
  describe('logout', () => {
88
- it('should call next-auth signOut when NextAuth is enabled', async () => {
89
- enableNextAuth.value = true;
90
-
91
- const { result } = renderHook(() => useUserStore());
92
-
93
- await act(async () => {
94
- await result.current.logout();
95
- });
96
-
97
- const { signOut } = await import('next-auth/react');
98
-
99
- expect(signOut).toHaveBeenCalled();
100
- enableNextAuth.value = false;
101
- });
102
-
103
- it('should not call next-auth signOut when NextAuth is disabled', async () => {
52
+ it('should call better-auth signOut', async () => {
104
53
  const { result } = renderHook(() => useUserStore());
105
54
 
106
55
  await act(async () => {
107
56
  await result.current.logout();
108
57
  });
109
58
 
110
- const { signOut } = await import('next-auth/react');
111
-
112
- expect(signOut).not.toHaveBeenCalled();
59
+ expect(mockBetterAuthClient.signOut).toHaveBeenCalled();
113
60
  });
114
61
  });
115
62
 
116
63
  describe('openLogin', () => {
117
- it('should call next-auth signIn when NextAuth is enabled', async () => {
118
- enableNextAuth.value = true;
119
-
120
- const { result } = renderHook(() => useUserStore());
121
-
122
- await act(async () => {
123
- await result.current.openLogin();
124
- });
125
-
126
- const { signIn } = await import('next-auth/react');
127
-
128
- expect(signIn).toHaveBeenCalled();
129
- enableNextAuth.value = false;
130
- });
131
- it('should not call next-auth signIn when NextAuth is disabled', async () => {
132
- const { result } = renderHook(() => useUserStore());
133
-
134
- await act(async () => {
135
- await result.current.openLogin();
136
- });
137
-
138
- const { signIn } = await import('next-auth/react');
139
-
140
- expect(signIn).not.toHaveBeenCalled();
141
- });
142
-
143
- it('should redirect to signin page when BetterAuth is enabled', async () => {
144
- enableBetterAuth.value = true;
145
-
64
+ it('should redirect to signin page', async () => {
146
65
  const originalLocation = window.location;
147
66
  Object.defineProperty(window, 'location', {
148
67
  configurable: true,
@@ -171,18 +90,15 @@ describe('createAuthSlice', () => {
171
90
  });
172
91
  });
173
92
 
174
- it('should call signIn with single provider when only one OAuth provider available', async () => {
175
- enableNextAuth.value = true;
176
- useUserStore.setState({ oAuthSSOProviders: ['github'] });
177
-
93
+ it('should not redirect when already on signin page', async () => {
178
94
  const originalLocation = window.location;
179
95
  Object.defineProperty(window, 'location', {
180
96
  configurable: true,
181
97
  value: {
182
98
  ...originalLocation,
183
99
  href: '',
184
- pathname: '/chat',
185
- toString: () => 'http://localhost/chat',
100
+ pathname: '/signin',
101
+ toString: () => 'http://localhost/signin',
186
102
  },
187
103
  writable: true,
188
104
  });
@@ -193,9 +109,7 @@ describe('createAuthSlice', () => {
193
109
  await result.current.openLogin();
194
110
  });
195
111
 
196
- const { signIn } = await import('next-auth/react');
197
-
198
- expect(signIn).toHaveBeenCalledWith('github');
112
+ expect(window.location.href).toBe('');
199
113
 
200
114
  Object.defineProperty(window, 'location', {
201
115
  configurable: true,
@@ -215,29 +129,10 @@ describe('createAuthSlice', () => {
215
129
  await result.current.fetchAuthProviders();
216
130
  });
217
131
 
218
- expect(mockUserService.getUserSSOProviders).not.toHaveBeenCalled();
219
- });
220
-
221
- it('should fetch providers from NextAuth when BetterAuth is disabled', async () => {
222
- enableBetterAuth.value = false;
223
- const mockProviders = [
224
- { provider: 'github', email: 'test@example.com', providerAccountId: '123' },
225
- ];
226
- mockUserService.getUserSSOProviders.mockResolvedValueOnce(mockProviders);
227
-
228
- const { result } = renderHook(() => useUserStore());
229
-
230
- await act(async () => {
231
- await result.current.fetchAuthProviders();
232
- });
233
-
234
- expect(mockUserService.getUserSSOProviders).toHaveBeenCalled();
235
- expect(result.current.isLoadedAuthProviders).toBe(true);
236
- expect(result.current.authProviders).toEqual(mockProviders);
132
+ expect(mockBetterAuthClient.listAccounts).not.toHaveBeenCalled();
237
133
  });
238
134
 
239
- it('should fetch providers from BetterAuth when enabled', async () => {
240
- enableBetterAuth.value = true;
135
+ it('should fetch providers from BetterAuth', async () => {
241
136
  mockBetterAuthClient.listAccounts.mockResolvedValueOnce({
242
137
  data: [
243
138
  { providerId: 'github', accountId: 'gh-123' },
@@ -260,8 +155,7 @@ describe('createAuthSlice', () => {
260
155
  });
261
156
 
262
157
  it('should handle fetch error gracefully', async () => {
263
- enableBetterAuth.value = false;
264
- mockUserService.getUserSSOProviders.mockRejectedValueOnce(new Error('Network error'));
158
+ mockBetterAuthClient.listAccounts.mockRejectedValueOnce(new Error('Network error'));
265
159
 
266
160
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
267
161
 
@@ -277,12 +171,13 @@ describe('createAuthSlice', () => {
277
171
  });
278
172
 
279
173
  describe('refreshAuthProviders', () => {
280
- it('should refresh providers from NextAuth', async () => {
281
- enableBetterAuth.value = false;
282
- const mockProviders = [
283
- { provider: 'google', email: 'user@gmail.com', providerAccountId: 'g-1' },
284
- ];
285
- mockUserService.getUserSSOProviders.mockResolvedValueOnce(mockProviders);
174
+ it('should refresh providers from BetterAuth', async () => {
175
+ mockBetterAuthClient.listAccounts.mockResolvedValueOnce({
176
+ data: [{ providerId: 'google', accountId: 'g-1' }],
177
+ });
178
+ mockBetterAuthClient.accountInfo.mockResolvedValueOnce({
179
+ data: { user: { email: 'user@gmail.com' } },
180
+ });
286
181
 
287
182
  const { result } = renderHook(() => useUserStore());
288
183
 
@@ -290,13 +185,14 @@ describe('createAuthSlice', () => {
290
185
  await result.current.refreshAuthProviders();
291
186
  });
292
187
 
293
- expect(mockUserService.getUserSSOProviders).toHaveBeenCalled();
294
- expect(result.current.authProviders).toEqual(mockProviders);
188
+ expect(mockBetterAuthClient.listAccounts).toHaveBeenCalled();
189
+ expect(result.current.authProviders).toEqual([
190
+ { provider: 'google', email: 'user@gmail.com', providerAccountId: 'g-1' },
191
+ ]);
295
192
  });
296
193
 
297
194
  it('should handle refresh error gracefully', async () => {
298
- enableBetterAuth.value = false;
299
- mockUserService.getUserSSOProviders.mockRejectedValueOnce(new Error('Refresh failed'));
195
+ mockBetterAuthClient.listAccounts.mockRejectedValueOnce(new Error('Refresh failed'));
300
196
 
301
197
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
302
198
 
@@ -1,9 +1,6 @@
1
1
  import { type SSOProvider } from '@lobechat/types';
2
2
  import { type StateCreator } from 'zustand/vanilla';
3
3
 
4
- import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
5
- import { userService } from '@/services/user';
6
-
7
4
  import type { UserStore } from '../../store';
8
5
 
9
6
  interface AuthProvidersData {
@@ -31,32 +28,26 @@ export interface UserAuthAction {
31
28
  }
32
29
 
33
30
  const fetchAuthProvidersData = async (): Promise<AuthProvidersData> => {
34
- if (enableBetterAuth) {
35
- const { accountInfo, listAccounts } = await import('@/libs/better-auth/auth-client');
36
- const result = await listAccounts();
37
- const accounts = result.data || [];
38
- const hasPasswordAccount = accounts.some((account) => account.providerId === 'credential');
39
- const providers = await Promise.all(
40
- accounts
41
- .filter((account) => account.providerId !== 'credential')
42
- .map(async (account) => {
43
- // In theory, the id_token could be decrypted from the accounts table, but I found that better-auth on GitHub does not save the id_token
44
- const info = await accountInfo({
45
- query: { accountId: account.accountId },
46
- });
47
- return {
48
- email: info.data?.user?.email ?? undefined,
49
- provider: account.providerId,
50
- providerAccountId: account.accountId,
51
- };
52
- }),
53
- );
54
- return { hasPasswordAccount, providers };
55
- }
56
-
57
- // Fallback for NextAuth
58
- const providers = await userService.getUserSSOProviders();
59
- return { hasPasswordAccount: false, providers };
31
+ const { accountInfo, listAccounts } = await import('@/libs/better-auth/auth-client');
32
+ const result = await listAccounts();
33
+ const accounts = result.data || [];
34
+ const hasPasswordAccount = accounts.some((account) => account.providerId === 'credential');
35
+ const providers = await Promise.all(
36
+ accounts
37
+ .filter((account) => account.providerId !== 'credential')
38
+ .map(async (account) => {
39
+ // In theory, the id_token could be decrypted from the accounts table, but I found that better-auth on GitHub does not save the id_token
40
+ const info = await accountInfo({
41
+ query: { accountId: account.accountId },
42
+ });
43
+ return {
44
+ email: info.data?.user?.email ?? undefined,
45
+ provider: account.providerId,
46
+ providerAccountId: account.accountId,
47
+ };
48
+ }),
49
+ );
50
+ return { hasPasswordAccount, providers };
60
51
  };
61
52
 
62
53
  export const createAuthSlice: StateCreator<
@@ -78,50 +69,26 @@ export const createAuthSlice: StateCreator<
78
69
  }
79
70
  },
80
71
  logout: async () => {
81
- if (enableBetterAuth) {
82
- const { signOut } = await import('@/libs/better-auth/auth-client');
83
- await signOut({
84
- fetchOptions: {
85
- onSuccess: () => {
86
- // Use window.location.href to trigger a full page reload
87
- // This ensures all client-side state (React, Zustand, cache) is cleared
88
- window.location.href = '/signin';
89
- },
72
+ const { signOut } = await import('@/libs/better-auth/auth-client');
73
+ await signOut({
74
+ fetchOptions: {
75
+ onSuccess: () => {
76
+ // Use window.location.href to trigger a full page reload
77
+ // This ensures all client-side state (React, Zustand, cache) is cleared
78
+ window.location.href = '/signin';
90
79
  },
91
- });
92
-
93
- return;
94
- }
95
-
96
- if (enableNextAuth) {
97
- const { signOut } = await import('next-auth/react');
98
- signOut();
99
- }
80
+ },
81
+ });
100
82
  },
101
83
  openLogin: async () => {
102
- // Skip if already on a Better Auth login page (/signin, /signup)
84
+ // Skip if already on a login page (/signin, /signup)
103
85
  const pathname = location.pathname;
104
86
  if (pathname.startsWith('/signin') || pathname.startsWith('/signup')) {
105
87
  return;
106
88
  }
107
89
 
108
- if (enableBetterAuth) {
109
- const currentUrl = location.toString();
110
- window.location.href = `/signin?callbackUrl=${encodeURIComponent(currentUrl)}`;
111
-
112
- return;
113
- }
114
-
115
- if (enableNextAuth) {
116
- const { signIn } = await import('next-auth/react');
117
- // Check if only one provider is available
118
- const providers = get()?.oAuthSSOProviders;
119
- if (providers && providers.length === 1) {
120
- signIn(providers[0]);
121
- return;
122
- }
123
- signIn();
124
- }
90
+ const currentUrl = location.toString();
91
+ window.location.href = `/signin?callbackUrl=${encodeURIComponent(currentUrl)}`;
125
92
  },
126
93
  refreshAuthProviders: async () => {
127
94
  try {
@@ -1,4 +1,3 @@
1
- import { type Session, type User } from '@auth/core/types';
2
1
  import { type SSOProvider } from '@lobechat/types';
3
2
 
4
3
  import { type LobeUser } from '@/types/user';
@@ -13,8 +12,6 @@ export interface UserAuthState {
13
12
  isLoadedAuthProviders?: boolean;
14
13
 
15
14
  isSignedIn?: boolean;
16
- nextSession?: Session;
17
- nextUser?: User;
18
15
  oAuthSSOProviders?: string[];
19
16
  user?: LobeUser;
20
17
  }
@@ -1,7 +1,6 @@
1
1
  import { type LobeUser, type SSOProvider } from '@lobechat/types';
2
2
  import { t } from 'i18next';
3
3
 
4
- import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
5
4
  import type { UserStore } from '@/store/user';
6
5
 
7
6
  const nickName = (s: UserStore) => {
@@ -37,6 +36,4 @@ export const authSelectors = {
37
36
  isLoadedAuthProviders: (s: UserStore) => s.isLoadedAuthProviders ?? false,
38
37
  isLogin: (s: UserStore) => s.isSignedIn,
39
38
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
40
- isLoginWithBetterAuth: (s: UserStore): boolean => (s.isSignedIn && enableBetterAuth) || false,
41
- isLoginWithNextAuth: (s: UserStore): boolean => (s.isSignedIn && !!enableNextAuth) || false,
42
39
  };
package/tests/setup.ts CHANGED
@@ -24,6 +24,16 @@ vi.mock('@lobehub/analytics/react', () => ({
24
24
  }),
25
25
  }));
26
26
 
27
+ // Global mock for @/auth to avoid better-auth validator module issue in tests
28
+ // The validator package has ESM resolution issues in Vitest environment
29
+ vi.mock('@/auth', () => ({
30
+ auth: {
31
+ api: {
32
+ getSession: vi.fn().mockResolvedValue(null),
33
+ },
34
+ },
35
+ }));
36
+
27
37
  // node runtime
28
38
  if (typeof window === 'undefined') {
29
39
  // test with polyfill crypto
@@ -1,42 +0,0 @@
1
- /**
2
- * Shared utility to check for deprecated Clerk environment variables.
3
- * Used by both prebuild.mts (build time) and startServer.js (Docker runtime).
4
- *
5
- * IMPORTANT: Keep this file as CommonJS (.js) for compatibility with startServer.js
6
- */
7
-
8
- const CLERK_MIGRATION_DOC_URL =
9
- 'https://lobehub.com/docs/self-hosting/advanced/auth/clerk-to-betterauth';
10
-
11
- const CLERK_ENV_VARS = [
12
- 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
13
- 'CLERK_SECRET_KEY',
14
- 'CLERK_WEBHOOK_SECRET',
15
- ];
16
-
17
- /**
18
- * Check for deprecated Clerk environment variables and exit if found
19
- * @param {object} options
20
- * @param {string} [options.action='redeploy'] - Action hint in error message ('redeploy' or 'restart')
21
- */
22
- function checkDeprecatedClerkEnv(options = {}) {
23
- const { action = 'redeploy' } = options;
24
- const foundClerkEnvVars = CLERK_ENV_VARS.filter((envVar) => process.env[envVar]);
25
-
26
- if (foundClerkEnvVars.length > 0) {
27
- console.error('\n' + '═'.repeat(70));
28
- console.error('❌ ERROR: Clerk authentication is no longer supported!');
29
- console.error('═'.repeat(70));
30
- console.error('\nDetected deprecated Clerk environment variables:');
31
- for (const envVar of foundClerkEnvVars) {
32
- console.error(` • ${envVar}`);
33
- }
34
- console.error('\nClerk has been removed from LobeChat. Please migrate to Better Auth.');
35
- console.error(`\n📖 Migration guide: ${CLERK_MIGRATION_DOC_URL}`);
36
- console.error(`\nAfter migration, remove the Clerk environment variables and ${action}.`);
37
- console.error('═'.repeat(70) + '\n');
38
- process.exit(1);
39
- }
40
- }
41
-
42
- module.exports = { checkDeprecatedClerkEnv };