@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.
- package/.env.desktop +0 -1
- package/.env.example +16 -20
- package/.env.example.development +1 -4
- package/.github/workflows/e2e.yml +10 -11
- package/CHANGELOG.md +60 -0
- package/Dockerfile +28 -4
- package/changelog/v1.json +18 -0
- package/docker-compose/local/docker-compose.yml +2 -2
- package/docker-compose/local/grafana/docker-compose.yml +2 -2
- package/docker-compose/local/logto/docker-compose.yml +2 -2
- package/docker-compose/local/zitadel/.env.example +2 -2
- package/docker-compose/local/zitadel/.env.zh-CN.example +2 -2
- package/docker-compose/production/grafana/docker-compose.yml +2 -2
- package/docker-compose/production/logto/.env.example +2 -2
- package/docker-compose/production/logto/.env.zh-CN.example +2 -2
- package/docker-compose/production/zitadel/.env.example +2 -2
- package/docker-compose/production/zitadel/.env.zh-CN.example +2 -2
- package/docs/development/basic/add-new-authentication-providers.mdx +144 -136
- package/docs/development/basic/add-new-authentication-providers.zh-CN.mdx +146 -136
- package/docs/self-hosting/advanced/auth/legacy.mdx +4 -0
- package/docs/self-hosting/advanced/auth/legacy.zh-CN.mdx +4 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +326 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +323 -0
- package/docs/self-hosting/advanced/auth.mdx +43 -16
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +44 -16
- package/docs/self-hosting/advanced/redis/upstash.mdx +69 -0
- package/docs/self-hosting/advanced/redis/upstash.zh-CN.mdx +69 -0
- package/docs/self-hosting/advanced/redis.mdx +128 -0
- package/docs/self-hosting/advanced/redis.zh-CN.mdx +126 -0
- package/docs/self-hosting/environment-variables/auth.mdx +15 -1
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +15 -1
- package/docs/self-hosting/environment-variables/basic.mdx +13 -0
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +13 -0
- package/docs/self-hosting/environment-variables/redis.mdx +68 -0
- package/docs/self-hosting/environment-variables/redis.zh-CN.mdx +67 -0
- package/docs/self-hosting/migration/v2/breaking-changes.mdx +23 -23
- package/docs/self-hosting/migration/v2/breaking-changes.zh-CN.mdx +23 -23
- package/docs/self-hosting/server-database/docker-compose.mdx +4 -4
- package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +4 -4
- package/e2e/CLAUDE.md +5 -6
- package/e2e/docs/local-setup.md +9 -12
- package/e2e/scripts/setup.ts +9 -15
- package/e2e/src/support/webServer.ts +6 -5
- package/locales/en-US/plugin.json +3 -0
- package/locales/zh-CN/plugin.json +3 -0
- package/package.json +4 -6
- package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
- package/packages/database/src/schemas/nextauth.ts +7 -2
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -63
- package/packages/utils/src/server/auth.ts +8 -24
- package/scripts/_shared/checkDeprecatedAuth.js +99 -0
- package/scripts/clerk-to-betterauth/index.ts +8 -3
- package/scripts/nextauth-to-betterauth/_internal/config.ts +41 -0
- package/scripts/nextauth-to-betterauth/_internal/db.ts +32 -0
- package/scripts/nextauth-to-betterauth/_internal/env.ts +6 -0
- package/scripts/nextauth-to-betterauth/index.ts +226 -0
- package/scripts/nextauth-to-betterauth/verify.ts +188 -0
- package/scripts/prebuild.mts +66 -13
- package/scripts/serverLauncher/startServer.js +5 -5
- package/src/app/(backend)/api/auth/[...all]/route.ts +5 -23
- package/src/app/(backend)/api/webhooks/casdoor/route.ts +5 -5
- package/src/app/(backend)/api/webhooks/logto/route.ts +8 -8
- package/src/app/(backend)/middleware/auth/index.test.ts +8 -1
- package/src/app/(backend)/middleware/auth/index.ts +6 -15
- package/src/app/(backend)/middleware/auth/utils.test.ts +0 -32
- package/src/app/(backend)/middleware/auth/utils.ts +3 -8
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +8 -1
- package/src/app/(backend)/webapi/create-image/comfyui/route.ts +0 -1
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -1
- package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +1 -1
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +4 -17
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +34 -21
- package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
- package/src/app/[variants]/(main)/settings/profile/features/SSOProvidersList/index.tsx +12 -19
- package/src/app/[variants]/(main)/settings/profile/index.tsx +8 -14
- package/src/components/{NextAuth/AuthIcons.tsx → AuthIcons.tsx} +8 -10
- package/src/envs/auth.ts +12 -51
- package/src/envs/email.ts +3 -0
- package/src/envs/redis.ts +12 -54
- package/src/features/ChatInput/ChatInputProvider.tsx +22 -2
- package/src/features/ChatInput/InputEditor/index.tsx +14 -3
- package/src/features/ChatInput/store/initialState.ts +2 -0
- package/src/features/User/__tests__/PanelContent.test.tsx +0 -11
- package/src/features/User/__tests__/UserAvatar.test.tsx +1 -16
- package/src/layout/AuthProvider/index.tsx +1 -6
- package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -4
- package/src/libs/better-auth/define-config.ts +2 -0
- package/src/libs/better-auth/plugins/email-whitelist.test.ts +120 -0
- package/src/libs/better-auth/plugins/email-whitelist.ts +62 -0
- package/src/libs/next/config/define-config.ts +13 -1
- package/src/libs/next/proxy/define-config.ts +2 -75
- package/src/libs/oidc-provider/provider.test.ts +0 -4
- package/src/libs/redis/index.ts +0 -1
- package/src/libs/redis/manager.test.ts +9 -45
- package/src/libs/redis/manager.ts +2 -16
- package/src/libs/redis/redis.test.ts +2 -4
- package/src/libs/redis/redis.ts +2 -4
- package/src/libs/redis/types.ts +2 -24
- package/src/libs/redis/utils.test.ts +0 -10
- package/src/libs/redis/utils.ts +0 -19
- package/src/libs/trpc/lambda/context.test.ts +0 -13
- package/src/libs/trpc/lambda/context.ts +21 -59
- package/src/libs/trpc/middleware/userAuth.ts +1 -7
- package/src/libs/trusted-client/getSessionUser.ts +15 -35
- package/src/locales/default/plugin.ts +3 -0
- package/src/server/globalConfig/index.ts +1 -3
- package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
- package/src/server/routers/lambda/__tests__/user.test.ts +0 -48
- package/src/server/routers/lambda/user.ts +1 -12
- package/src/server/services/email/impls/nodemailer/index.ts +2 -2
- package/src/server/services/webhookUser/index.ts +88 -0
- package/src/services/chat/chat.test.ts +19 -19
- package/src/services/chat/index.ts +8 -3
- package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
- package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
- package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
- package/src/services/chat/mecha/contextEngineering.ts +12 -0
- package/src/services/chat/types.ts +7 -1
- package/src/services/user/index.test.ts +0 -14
- package/src/services/user/index.ts +0 -4
- package/src/store/chat/agents/createAgentExecutors.ts +15 -4
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
- package/src/store/user/slices/auth/action.test.ts +22 -126
- package/src/store/user/slices/auth/action.ts +32 -65
- package/src/store/user/slices/auth/initialState.ts +0 -3
- package/src/store/user/slices/auth/selectors.ts +0 -3
- package/tests/setup.ts +10 -0
- package/scripts/_shared/checkDeprecatedClerkEnv.js +0 -42
- package/src/app/(backend)/api/auth/adapter/route.ts +0 -137
- package/src/app/[variants]/(auth)/next-auth/error/AuthErrorPage.tsx +0 -40
- package/src/app/[variants]/(auth)/next-auth/error/page.tsx +0 -11
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +0 -167
- package/src/app/[variants]/(auth)/next-auth/signin/page.tsx +0 -11
- package/src/app/[variants]/(auth)/reset-password/layout.tsx +0 -12
- package/src/app/[variants]/(auth)/signin/layout.tsx +0 -12
- package/src/app/[variants]/(auth)/verify-email/layout.tsx +0 -12
- package/src/envs/auth.test.ts +0 -47
- package/src/layout/AuthProvider/NextAuth/UserUpdater.tsx +0 -44
- package/src/layout/AuthProvider/NextAuth/index.tsx +0 -17
- package/src/libs/next-auth/adapter/index.ts +0 -177
- package/src/libs/next-auth/auth.config.ts +0 -64
- package/src/libs/next-auth/index.ts +0 -20
- package/src/libs/next-auth/sso-providers/auth0.ts +0 -24
- package/src/libs/next-auth/sso-providers/authelia.ts +0 -39
- package/src/libs/next-auth/sso-providers/authentik.ts +0 -25
- package/src/libs/next-auth/sso-providers/casdoor.ts +0 -50
- package/src/libs/next-auth/sso-providers/cloudflare-zero-trust.ts +0 -34
- package/src/libs/next-auth/sso-providers/cognito.ts +0 -8
- package/src/libs/next-auth/sso-providers/feishu.ts +0 -83
- package/src/libs/next-auth/sso-providers/generic-oidc.ts +0 -38
- package/src/libs/next-auth/sso-providers/github.ts +0 -23
- package/src/libs/next-auth/sso-providers/google.ts +0 -18
- package/src/libs/next-auth/sso-providers/index.ts +0 -35
- package/src/libs/next-auth/sso-providers/keycloak.ts +0 -22
- package/src/libs/next-auth/sso-providers/logto.ts +0 -48
- package/src/libs/next-auth/sso-providers/microsoft-entra-id-helper.ts +0 -29
- package/src/libs/next-auth/sso-providers/microsoft-entra-id.ts +0 -19
- package/src/libs/next-auth/sso-providers/okta.ts +0 -22
- package/src/libs/next-auth/sso-providers/sso.config.ts +0 -8
- package/src/libs/next-auth/sso-providers/wechat.ts +0 -36
- package/src/libs/next-auth/sso-providers/zitadel.ts +0 -21
- package/src/libs/redis/upstash.test.ts +0 -158
- package/src/libs/redis/upstash.ts +0 -136
- package/src/server/services/nextAuthUser/index.ts +0 -318
- package/src/server/services/nextAuthUser/utils.ts +0 -62
- 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(
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
expect(signOut).not.toHaveBeenCalled();
|
|
59
|
+
expect(mockBetterAuthClient.signOut).toHaveBeenCalled();
|
|
113
60
|
});
|
|
114
61
|
});
|
|
115
62
|
|
|
116
63
|
describe('openLogin', () => {
|
|
117
|
-
it('should
|
|
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
|
|
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: '/
|
|
185
|
-
toString: () => 'http://localhost/
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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(
|
|
294
|
-
expect(result.current.authProviders).toEqual(
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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 };
|