@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
@@ -11,7 +11,7 @@ import { Editor, useEditor } from '@lobehub/editor/react';
11
11
  import { Flexbox, Icon, Text } from '@lobehub/ui';
12
12
  import { Card } from 'antd';
13
13
  import { Clock } from 'lucide-react';
14
- import { memo, useCallback, useEffect, useRef } from 'react';
14
+ import { type RefObject, memo, useCallback, useEffect, useRef } from 'react';
15
15
  import { useTranslation } from 'react-i18next';
16
16
 
17
17
  interface CronJobContentEditorProps {
@@ -20,8 +20,12 @@ interface CronJobContentEditorProps {
20
20
  onChange: (value: string) => void;
21
21
  }
22
22
 
23
- const CronJobContentEditor = memo<CronJobContentEditorProps>(
24
- ({ enableRichRender, initialValue, onChange }) => {
23
+ interface CronJobContentEditorInnerProps extends CronJobContentEditorProps {
24
+ contentRef: RefObject<string>;
25
+ }
26
+
27
+ const CronJobContentEditorInner = memo<CronJobContentEditorInnerProps>(
28
+ ({ enableRichRender, initialValue, onChange, contentRef }) => {
25
29
  const { t } = useTranslation('setting');
26
30
  const editor = useEditor();
27
31
  const currentValueRef = useRef(initialValue);
@@ -31,23 +35,6 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
31
35
  currentValueRef.current = initialValue;
32
36
  }, [initialValue]);
33
37
 
34
- // Initialize editor content when editor is ready
35
- useEffect(() => {
36
- if (!editor) return;
37
- try {
38
- setTimeout(() => {
39
- if (initialValue) {
40
- editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
41
- }
42
- }, 100);
43
- } catch (error) {
44
- console.error('[CronJobContentEditor] Failed to initialize editor content:', error);
45
- setTimeout(() => {
46
- editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
47
- }, 100);
48
- }
49
- }, [editor, enableRichRender, initialValue]);
50
-
51
38
  // Handle content changes
52
39
  const handleContentChange = useCallback(
53
40
  (e: any) => {
@@ -57,13 +44,18 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
57
44
 
58
45
  const finalContent = nextContent || '';
59
46
 
47
+ // Save to parent ref for restoration
48
+ if (contentRef) {
49
+ (contentRef as { current: string }).current = finalContent;
50
+ }
51
+
60
52
  // Only call onChange if content actually changed
61
53
  if (finalContent !== currentValueRef.current) {
62
54
  currentValueRef.current = finalContent;
63
55
  onChange(finalContent);
64
56
  }
65
57
  },
66
- [enableRichRender, onChange],
58
+ [enableRichRender, onChange, contentRef],
67
59
  );
68
60
 
69
61
  return (
@@ -82,6 +74,14 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
82
74
  content={''}
83
75
  editor={editor}
84
76
  lineEmptyPlaceholder={t('agentCronJobs.form.content.placeholder')}
77
+ onInit={(editor) => {
78
+ // Restore content from parent ref when editor re-initializes
79
+ if (contentRef?.current) {
80
+ editor.setDocument(enableRichRender ? 'markdown' : 'text', contentRef.current);
81
+ } else if (initialValue) {
82
+ editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
83
+ }
84
+ }}
85
85
  onTextChange={handleContentChange}
86
86
  placeholder={t('agentCronJobs.form.content.placeholder')}
87
87
  plugins={
@@ -108,4 +108,17 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
108
108
  },
109
109
  );
110
110
 
111
+ const CronJobContentEditor = (props: CronJobContentEditorProps) => {
112
+ // Ref to persist content across re-mounts when enableRichRender changes
113
+ const contentRef = useRef<string>(props.initialValue);
114
+
115
+ return (
116
+ <CronJobContentEditorInner
117
+ contentRef={contentRef}
118
+ key={`editor-${props.enableRichRender}`}
119
+ {...props}
120
+ />
121
+ );
122
+ };
123
+
111
124
  export default CronJobContentEditor;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Flexbox } from '@lobehub/ui';
4
+ import debug from 'debug';
4
5
  import { Suspense, memo, useMemo } from 'react';
5
6
 
6
7
  import ChatMiniMap from '@/features/ChatMiniMap';
@@ -18,6 +19,8 @@ import ThreadHydration from './ThreadHydration';
18
19
  import { useActionsBarConfig } from './useActionsBarConfig';
19
20
  import { useAgentContext } from './useAgentContext';
20
21
 
22
+ const log = debug('lobe-render:agent:ConversationArea');
23
+
21
24
  /**
22
25
  * ConversationArea
23
26
  *
@@ -35,6 +38,7 @@ const Conversation = memo(() => {
35
38
  );
36
39
  const replaceMessages = useChatStore((s) => s.replaceMessages);
37
40
  const messages = useChatStore((s) => s.dbMessagesMap[chatKey]);
41
+ log('contextKey %s: %o', chatKey, messages);
38
42
 
39
43
  // Get operation state from ChatStore for reactive updates
40
44
  const operationState = useOperationState(context);
@@ -111,6 +111,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
111
111
  fill={fav ? cssVar.colorWarning : 'transparent'}
112
112
  icon={Star}
113
113
  onClick={(e) => {
114
+ e.preventDefault();
114
115
  e.stopPropagation();
115
116
  favoriteTopic(id, !fav);
116
117
  }}
@@ -106,7 +106,7 @@ const AgentItem = memo<AgentItemProps>(({ item, style, className }) => {
106
106
 
107
107
  return (
108
108
  <>
109
- <Link aria-label={id} to={agentUrl}>
109
+ <Link aria-label={displayTitle} to={agentUrl}>
110
110
  <NavItem
111
111
  actions={<Actions dropdownMenu={dropdownMenu} />}
112
112
  className={className}
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { DEFAULT_INBOX_AVATAR, SESSION_CHAT_URL } from '@lobechat/const';
4
4
  import { Avatar } from '@lobehub/ui';
5
- import { type CSSProperties, memo, useCallback } from 'react';
6
- import { useNavigate } from 'react-router-dom';
5
+ import { type CSSProperties, memo } from 'react';
6
+ import { Link } from 'react-router-dom';
7
7
 
8
8
  import NavItem from '@/features/NavPanel/components/NavItem';
9
9
  import { useAgentStore } from '@/store/agent';
@@ -17,38 +17,28 @@ interface InboxItemProps {
17
17
  }
18
18
 
19
19
  const InboxItem = memo<InboxItemProps>(({ className, style }) => {
20
- const navigate = useNavigate();
21
-
22
20
  const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
23
- const activeAgentId = useAgentStore((s) => s.activeAgentId);
24
- const isActive = !!inboxAgentId && activeAgentId === inboxAgentId;
25
21
 
26
- const isLoading = useChatStore(
27
- useCallback(
28
- (s) => (isActive ? operationSelectors.isAgentRuntimeRunning(s) : false),
29
- [isActive],
30
- ),
31
- );
22
+ const isLoading = useChatStore(operationSelectors.isAgentRuntimeRunning);
32
23
  const inboxAgentTitle = 'Lobe AI';
33
24
 
34
- const handleClick = useCallback(() => {
35
- if (inboxAgentId) {
36
- navigate(SESSION_CHAT_URL(inboxAgentId, false));
37
- }
38
- }, [inboxAgentId, navigate]);
39
-
40
25
  return (
41
- <NavItem
42
- active={isActive}
43
- className={className}
44
- icon={
45
- <Avatar avatar={DEFAULT_INBOX_AVATAR} emojiScaleWithBackground shape={'square'} size={24} />
46
- }
47
- loading={isLoading}
48
- onClick={handleClick}
49
- style={style}
50
- title={inboxAgentTitle}
51
- />
26
+ <Link aria-label={inboxAgentTitle} to={SESSION_CHAT_URL(inboxAgentId, false)}>
27
+ <NavItem
28
+ className={className}
29
+ icon={
30
+ <Avatar
31
+ avatar={DEFAULT_INBOX_AVATAR}
32
+ emojiScaleWithBackground
33
+ shape={'square'}
34
+ size={24}
35
+ />
36
+ }
37
+ loading={isLoading}
38
+ style={style}
39
+ title={inboxAgentTitle}
40
+ />
41
+ </Link>
52
42
  );
53
43
  });
54
44
 
@@ -4,6 +4,7 @@ import { MoreHorizontal } from 'lucide-react';
4
4
  import { type CSSProperties, memo, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
+ import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
7
8
  import NavItem from '@/features/NavPanel/components/NavItem';
8
9
  import { useGlobalStore } from '@/store/global';
9
10
  import { systemStatusSelectors } from '@/store/global/selectors';
@@ -11,7 +12,6 @@ import { useHomeStore } from '@/store/home';
11
12
  import { homeAgentListSelectors } from '@/store/home/selectors';
12
13
  import { SessionDefaultGroup } from '@/types/session';
13
14
 
14
- import EmptyNavItem from '../../../../../../../../features/NavPanel/components/EmptyNavItem';
15
15
  import { useCreateMenuItems } from '../../../hooks';
16
16
  import GroupItem from './AgentGroupItem';
17
17
  import AgentItem from './AgentItem';
@@ -5,8 +5,8 @@ import { type ReactNode, createContext, memo, useContext, useMemo, useState } fr
5
5
  import { ChatGroupWizard } from '@/components/ChatGroupWizard';
6
6
  import { MemberSelectionModal } from '@/components/MemberSelectionModal';
7
7
 
8
- import CreateGroupModal from '../../CreateGroupModal';
9
8
  import ConfigGroupModal from './Modals/ConfigGroupModal';
9
+ import CreateGroupModal from './Modals/CreateGroupModal';
10
10
 
11
11
  interface AgentModalContextValue {
12
12
  closeAllModals: () => void;
@@ -5,8 +5,7 @@ import { type CSSProperties, memo, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { modal, notification } from '@/components/AntdStaticMethods';
8
- import AuthIcons from '@/components/NextAuth/AuthIcons';
9
- import { userService } from '@/services/user';
8
+ import AuthIcons from '@/components/AuthIcons';
10
9
  import { useServerConfigStore } from '@/store/serverConfig';
11
10
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
12
11
  import { useUserStore } from '@/store/user';
@@ -17,7 +16,7 @@ const providerNameStyle: CSSProperties = {
17
16
  };
18
17
 
19
18
  export const SSOProvidersList = memo(() => {
20
- const isLoginWithBetterAuth = useUserStore(authSelectors.isLoginWithBetterAuth);
19
+ const isLogin = useUserStore(authSelectors.isLogin);
21
20
  const providers = useUserStore(authSelectors.authProviders);
22
21
  const hasPasswordAccount = useUserStore(authSelectors.hasPasswordAccount);
23
22
  const refreshAuthProviders = useUserStore((s) => s.refreshAuthProviders);
@@ -26,7 +25,7 @@ export const SSOProvidersList = memo(() => {
26
25
 
27
26
  // Allow unlink if user has multiple SSO providers OR has email/password login
28
27
  const allowUnlink = providers.length > 1 || hasPasswordAccount;
29
- const enableBetterAuthActions = !isDesktop && isLoginWithBetterAuth;
28
+ const enableAuthActions = !isDesktop && isLogin;
30
29
 
31
30
  // Get linked provider IDs for filtering
32
31
  const linkedProviderIds = useMemo(() => {
@@ -38,9 +37,9 @@ export const SSOProvidersList = memo(() => {
38
37
  return (oAuthSSOProviders || []).filter((provider) => !linkedProviderIds.has(provider));
39
38
  }, [oAuthSSOProviders, linkedProviderIds]);
40
39
 
41
- const handleUnlinkSSO = async (provider: string, providerAccountId: string) => {
40
+ const handleUnlinkSSO = async (provider: string) => {
42
41
  // Better-auth link/unlink operations are not available on desktop
43
- if (isDesktop && isLoginWithBetterAuth) return;
42
+ if (isDesktop) return;
44
43
 
45
44
  // Prevent unlink if this is the only login method
46
45
  if (!allowUnlink) {
@@ -55,14 +54,8 @@ export const SSOProvidersList = memo(() => {
55
54
  danger: true,
56
55
  },
57
56
  onOk: async () => {
58
- if (isLoginWithBetterAuth) {
59
- // Use better-auth native API
60
- const { unlinkAccount } = await import('@/libs/better-auth/auth-client');
61
- await unlinkAccount({ providerId: provider });
62
- } else {
63
- // Fallback for NextAuth
64
- await userService.unlinkSSOProvider(provider, providerAccountId);
65
- }
57
+ const { unlinkAccount } = await import('@/libs/better-auth/auth-client');
58
+ await unlinkAccount({ providerId: provider });
66
59
  refreshAuthProviders();
67
60
  },
68
61
  title: <span style={providerNameStyle}>{t('profile.sso.unlink.title', { provider })}</span>,
@@ -70,7 +63,7 @@ export const SSOProvidersList = memo(() => {
70
63
  };
71
64
 
72
65
  const handleLinkSSO = async (provider: string) => {
73
- if (enableBetterAuthActions) {
66
+ if (enableAuthActions) {
74
67
  // Use better-auth native linkSocial API
75
68
  const { linkSocial } = await import('@/libs/better-auth/auth-client');
76
69
  await linkSocial({
@@ -107,19 +100,19 @@ export const SSOProvidersList = memo(() => {
107
100
  </Text>
108
101
  )}
109
102
  </Flexbox>
110
- {!(isDesktop && isLoginWithBetterAuth) && (
103
+ {!isDesktop && (
111
104
  <ActionIcon
112
105
  disabled={!allowUnlink}
113
106
  icon={Unlink}
114
- onClick={() => handleUnlinkSSO(item.provider, item.providerAccountId)}
107
+ onClick={() => handleUnlinkSSO(item.provider)}
115
108
  size={'small'}
116
109
  />
117
110
  )}
118
111
  </Flexbox>
119
112
  ))}
120
113
 
121
- {/* Link Account Button - Only show for Better-Auth users with available providers */}
122
- {enableBetterAuthActions && availableProviders.length > 0 && (
114
+ {/* Link Account Button - Only show for logged in users with available providers */}
115
+ {enableAuthActions && availableProviders.length > 0 && (
123
116
  <DropdownMenu items={linkMenuItems} popupProps={{ style: { maxWidth: '200px' } }}>
124
117
  <Flexbox align={'center'} gap={6} horizontal style={{ cursor: 'pointer', fontSize: 12 }}>
125
118
  <Plus size={14} />
@@ -51,10 +51,7 @@ interface ProfileSettingProps {
51
51
  }
52
52
 
53
53
  const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
54
- const [isLoginWithNextAuth, isLoginWithBetterAuth] = useUserStore((s) => [
55
- authSelectors.isLoginWithNextAuth(s),
56
- authSelectors.isLoginWithBetterAuth(s),
57
- ]);
54
+ const isLogin = useUserStore(authSelectors.isLogin);
58
55
  const [userProfile, isUserLoaded] = useUserStore((s) => [
59
56
  userProfileSelectors.userProfile(s),
60
57
  s.isLoaded,
@@ -72,17 +69,14 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
72
69
  // Fetch Klavis servers
73
70
  useFetchUserKlavisServers(enableKlavis);
74
71
 
75
- const isLoginWithAuth = isLoginWithNextAuth || isLoginWithBetterAuth;
76
72
  const isLoading =
77
- !isUserLoaded ||
78
- (isLoginWithAuth && !isLoadedAuthProviders) ||
79
- (enableKlavis && !isServersInit);
73
+ !isUserLoaded || (isLogin && !isLoadedAuthProviders) || (enableKlavis && !isServersInit);
80
74
 
81
75
  useEffect(() => {
82
- if (isLoginWithAuth) {
76
+ if (isLogin) {
83
77
  fetchAuthProviders();
84
78
  }
85
- }, [isLoginWithAuth, fetchAuthProviders]);
79
+ }, [isLogin, fetchAuthProviders]);
86
80
 
87
81
  const { t } = useTranslation('auth');
88
82
 
@@ -118,8 +112,8 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
118
112
  {/* Interests Row - Editable */}
119
113
  <InterestsRow mobile={mobile} />
120
114
 
121
- {/* Password Row - For Better Auth users to change or set password */}
122
- {!isDesktop && isLoginWithBetterAuth && (
115
+ {/* Password Row - For logged in users to change or set password */}
116
+ {!isDesktop && isLogin && (
123
117
  <>
124
118
  <Divider style={{ margin: 0 }} />
125
119
  <PasswordRow mobile={mobile} />
@@ -127,7 +121,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
127
121
  )}
128
122
 
129
123
  {/* Email Row - Read Only */}
130
- {isLoginWithAuth && userProfile?.email && (
124
+ {isLogin && userProfile?.email && (
131
125
  <>
132
126
  <Divider style={{ margin: 0 }} />
133
127
  <ProfileRow label={t('profile.email')} mobile={mobile}>
@@ -137,7 +131,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
137
131
  )}
138
132
 
139
133
  {/* SSO Providers Row */}
140
- {isLoginWithAuth && (
134
+ {isLogin && (
141
135
  <>
142
136
  <Divider style={{ margin: 0 }} />
143
137
  <ProfileRow label={t('profile.sso.providers')} mobile={mobile}>
@@ -8,12 +8,10 @@ import {
8
8
  Github,
9
9
  Logto,
10
10
  MicrosoftEntra,
11
- NextAuth,
12
11
  Zitadel,
13
12
  } from '@lobehub/ui/icons';
14
- import React from 'react';
13
+ import { User } from 'lucide-react';
15
14
 
16
- // TODO: check this
17
15
  const iconComponents: { [key: string]: any } = {
18
16
  'apple': Apple,
19
17
  'auth0': Auth0,
@@ -22,7 +20,6 @@ const iconComponents: { [key: string]: any } = {
22
20
  'casdoor': Casdoor.Color,
23
21
  'cloudflare': Cloudflare.Color,
24
22
  'cognito': Aws.Color,
25
- 'default': NextAuth.Color,
26
23
  'github': Github,
27
24
  'google': Google.Color,
28
25
  'logto': Logto.Color,
@@ -32,14 +29,15 @@ const iconComponents: { [key: string]: any } = {
32
29
  };
33
30
 
34
31
  /**
35
- * Get the auth icons component for the given id
36
- * @param id
37
- * @param size default is 36
38
- * @returns
32
+ * Get the auth icons component for the given provider id
39
33
  */
40
34
  const AuthIcons = (id: string, size = 36) => {
41
- const IconComponent = iconComponents[id] || iconComponents.default;
42
- return <IconComponent size={size} />;
35
+ const IconComponent = iconComponents[id];
36
+ if (IconComponent) {
37
+ return <IconComponent size={size} />;
38
+ }
39
+ // Fallback to generic user icon for unknown providers
40
+ return <User size={size} />;
43
41
  };
44
42
 
45
43
  export default AuthIcons;
package/src/envs/auth.ts CHANGED
@@ -6,23 +6,15 @@ declare global {
6
6
  // eslint-disable-next-line @typescript-eslint/no-namespace
7
7
  namespace NodeJS {
8
8
  interface ProcessEnv {
9
- // ===== Auth (shared by Better Auth / Next Auth) ===== //
9
+ // ===== Better Auth ===== //
10
10
  AUTH_SECRET?: string;
11
11
  AUTH_EMAIL_VERIFICATION?: string;
12
12
  ENABLE_MAGIC_LINK?: string;
13
13
  AUTH_SSO_PROVIDERS?: string;
14
14
  AUTH_TRUSTED_ORIGINS?: string;
15
+ AUTH_ALLOWED_EMAILS?: string;
15
16
 
16
- // ===== Next Auth ===== //
17
- NEXT_AUTH_SECRET?: string;
18
-
19
- NEXT_AUTH_SSO_PROVIDERS?: string;
20
-
21
- NEXT_AUTH_DEBUG?: string;
22
-
23
- NEXT_AUTH_SSO_SESSION_STRATEGY?: string;
24
-
25
- // ===== Next Auth Provider Credentials ===== //
17
+ // ===== Auth Provider Credentials ===== //
26
18
  AUTH_GOOGLE_ID?: string;
27
19
  AUTH_GOOGLE_SECRET?: string;
28
20
 
@@ -130,26 +122,14 @@ declare global {
130
122
 
131
123
  export const getAuthConfig = () => {
132
124
  return createEnv({
133
- client: {
134
- // ---------------------------------- better auth ----------------------------------
135
- NEXT_PUBLIC_ENABLE_BETTER_AUTH: z.boolean().optional(),
136
-
137
- // ---------------------------------- next auth ----------------------------------
138
- NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
139
- },
125
+ client: {},
140
126
  server: {
141
- // ---------------------------------- better auth ----------------------------------
142
127
  AUTH_SECRET: z.string().optional(),
143
128
  AUTH_SSO_PROVIDERS: z.string().optional().default(''),
144
129
  AUTH_TRUSTED_ORIGINS: z.string().optional(),
145
130
  AUTH_EMAIL_VERIFICATION: z.boolean().optional().default(false),
146
131
  ENABLE_MAGIC_LINK: z.boolean().optional().default(false),
147
-
148
- // ---------------------------------- next auth ----------------------------------
149
- NEXT_AUTH_SECRET: z.string().optional(),
150
- NEXT_AUTH_SSO_PROVIDERS: z.string().optional().default('auth0'),
151
- NEXT_AUTH_DEBUG: z.boolean().optional().default(false),
152
- NEXT_AUTH_SSO_SESSION_STRATEGY: z.enum(['jwt', 'database']).optional().default('jwt'),
132
+ AUTH_ALLOWED_EMAILS: z.string().optional(),
153
133
 
154
134
  AUTH_GOOGLE_ID: z.string().optional(),
155
135
  AUTH_GOOGLE_SECRET: z.string().optional(),
@@ -248,33 +228,19 @@ export const getAuthConfig = () => {
248
228
  },
249
229
 
250
230
  runtimeEnv: {
251
- // ---------------------------------- better auth ----------------------------------
252
- NEXT_PUBLIC_ENABLE_BETTER_AUTH: process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1',
253
- // Fallback to NEXT_PUBLIC_* for seamless migration
254
- AUTH_EMAIL_VERIFICATION:
255
- process.env.AUTH_EMAIL_VERIFICATION === '1' ||
256
- process.env.NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION === '1',
257
- ENABLE_MAGIC_LINK:
258
- process.env.ENABLE_MAGIC_LINK === '1' || process.env.NEXT_PUBLIC_ENABLE_MAGIC_LINK === '1',
259
- // Fallback to NEXT_AUTH_SECRET for seamless migration from next-auth
260
- AUTH_SECRET: process.env.AUTH_SECRET || process.env.NEXT_AUTH_SECRET,
261
- // Fallback to NEXT_AUTH_SSO_PROVIDERS for seamless migration from next-auth
262
- AUTH_SSO_PROVIDERS: process.env.AUTH_SSO_PROVIDERS || process.env.NEXT_AUTH_SSO_PROVIDERS,
231
+ AUTH_EMAIL_VERIFICATION: process.env.AUTH_EMAIL_VERIFICATION === '1',
232
+ ENABLE_MAGIC_LINK: process.env.ENABLE_MAGIC_LINK === '1',
233
+ AUTH_SECRET: process.env.AUTH_SECRET,
234
+ AUTH_SSO_PROVIDERS: process.env.AUTH_SSO_PROVIDERS,
263
235
  AUTH_TRUSTED_ORIGINS: process.env.AUTH_TRUSTED_ORIGINS,
236
+ AUTH_ALLOWED_EMAILS: process.env.AUTH_ALLOWED_EMAILS,
264
237
 
265
- // better-auth env for Cognito provider is different from next-auth's one
238
+ // Cognito provider specific env vars
266
239
  AUTH_COGNITO_DOMAIN: process.env.AUTH_COGNITO_DOMAIN,
267
240
  AUTH_COGNITO_REGION: process.env.AUTH_COGNITO_REGION,
268
241
  AUTH_COGNITO_USERPOOL_ID: process.env.AUTH_COGNITO_USERPOOL_ID,
269
242
 
270
- // ---------------------------------- next auth ----------------------------------
271
- NEXT_PUBLIC_ENABLE_NEXT_AUTH: process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1',
272
- NEXT_AUTH_SSO_PROVIDERS: process.env.NEXT_AUTH_SSO_PROVIDERS,
273
- NEXT_AUTH_SECRET: process.env.NEXT_AUTH_SECRET,
274
- NEXT_AUTH_DEBUG: !!process.env.NEXT_AUTH_DEBUG,
275
- NEXT_AUTH_SSO_SESSION_STRATEGY: process.env.NEXT_AUTH_SSO_SESSION_STRATEGY || 'jwt',
276
-
277
- // Next Auth Provider Credentials
243
+ // Auth Provider Credentials
278
244
  AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
279
245
  AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
280
246
 
@@ -374,11 +340,6 @@ export const getAuthConfig = () => {
374
340
 
375
341
  export const authEnv = getAuthConfig();
376
342
 
377
- // Auth flags - use process.env directly for build-time dead code elimination
378
- // Better Auth is the default auth solution when NextAuth is not explicitly enabled
379
- export const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
380
- export const enableBetterAuth = !enableNextAuth;
381
-
382
343
  // Auth headers and constants
383
344
  export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
384
345
  export const LOBE_CHAT_OIDC_AUTH_HEADER = 'Oidc-Auth';
package/src/envs/email.ts CHANGED
@@ -9,6 +9,7 @@ declare global {
9
9
  EMAIL_SERVICE_PROVIDER?: string;
10
10
  RESEND_API_KEY?: string;
11
11
  RESEND_FROM?: string;
12
+ SMTP_FROM?: string;
12
13
  SMTP_HOST?: string;
13
14
  SMTP_PASS?: string;
14
15
  SMTP_PORT?: string;
@@ -24,6 +25,7 @@ export const getEmailConfig = () => {
24
25
  EMAIL_SERVICE_PROVIDER: z.enum(['nodemailer', 'resend']).optional(),
25
26
  RESEND_API_KEY: z.string().optional(),
26
27
  RESEND_FROM: z.string().optional(),
28
+ SMTP_FROM: z.string().optional(),
27
29
  SMTP_HOST: z.string().optional(),
28
30
  SMTP_PORT: z.coerce.number().optional(),
29
31
  SMTP_SECURE: z.boolean().optional(),
@@ -31,6 +33,7 @@ export const getEmailConfig = () => {
31
33
  SMTP_PASS: z.string().optional(),
32
34
  },
33
35
  runtimeEnv: {
36
+ SMTP_FROM: process.env.SMTP_FROM,
34
37
  SMTP_HOST: process.env.SMTP_HOST,
35
38
  SMTP_PORT: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined,
36
39
  SMTP_SECURE: process.env.SMTP_SECURE === 'true',
package/src/envs/redis.ts CHANGED
@@ -4,8 +4,6 @@ import { z } from 'zod';
4
4
 
5
5
  import type { RedisConfig } from '@/libs/redis';
6
6
 
7
- type UpstashRedisConfig = { token: string; url: string };
8
-
9
7
  const parseNumber = (value?: string) => {
10
8
  const parsed = Number.parseInt(value ?? '', 10);
11
9
 
@@ -30,8 +28,6 @@ export const getRedisEnv = () => {
30
28
  REDIS_TLS: parseRedisTls(process.env.REDIS_TLS),
31
29
  REDIS_URL: process.env.REDIS_URL,
32
30
  REDIS_USERNAME: process.env.REDIS_USERNAME,
33
- UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
34
- UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
35
31
  },
36
32
  server: {
37
33
  REDIS_DATABASE: z.number().int().optional(),
@@ -40,67 +36,29 @@ export const getRedisEnv = () => {
40
36
  REDIS_TLS: z.boolean().default(false),
41
37
  REDIS_URL: z.string().url().optional(),
42
38
  REDIS_USERNAME: z.string().optional(),
43
- UPSTASH_REDIS_REST_TOKEN: z.string().optional(),
44
- UPSTASH_REDIS_REST_URL: z.string().url().optional(),
45
39
  },
46
40
  });
47
41
  };
48
42
 
49
43
  export const redisEnv = getRedisEnv();
50
44
 
51
- export const getUpstashRedisConfig = (): UpstashRedisConfig | null => {
52
- const upstashConfigSchema = z.union([
53
- z.object({
54
- token: z.string(),
55
- url: z.string().url(),
56
- }),
57
- z.object({
58
- token: z.undefined().optional(),
59
- url: z.undefined().optional(),
60
- }),
61
- ]);
62
-
63
- const parsed = upstashConfigSchema.safeParse({
64
- token: redisEnv.UPSTASH_REDIS_REST_TOKEN,
65
- url: redisEnv.UPSTASH_REDIS_REST_URL,
66
- });
67
-
68
- if (!parsed.success) throw parsed.error;
69
- if (!parsed.data.token || !parsed.data.url) return null;
70
-
71
- return parsed.data;
72
- };
73
-
74
45
  export const getRedisConfig = (): RedisConfig => {
75
- const prefix = redisEnv.REDIS_PREFIX;
76
-
77
- if (redisEnv.REDIS_URL) {
78
- return {
79
- database: redisEnv.REDIS_DATABASE,
80
- enabled: true,
81
- password: redisEnv.REDIS_PASSWORD,
82
- prefix,
83
- provider: 'redis',
84
- tls: redisEnv.REDIS_TLS,
85
- url: redisEnv.REDIS_URL,
86
- username: redisEnv.REDIS_USERNAME,
87
- };
88
- }
89
-
90
- const upstashConfig = getUpstashRedisConfig();
91
- if (upstashConfig) {
46
+ if (!redisEnv.REDIS_URL) {
92
47
  return {
93
- enabled: true,
94
- prefix,
95
- provider: 'upstash',
96
- token: upstashConfig.token,
97
- url: upstashConfig.url,
48
+ enabled: false,
49
+ prefix: redisEnv.REDIS_PREFIX,
50
+ tls: false,
51
+ url: '',
98
52
  };
99
53
  }
100
54
 
101
55
  return {
102
- enabled: false,
103
- prefix,
104
- provider: false,
56
+ database: redisEnv.REDIS_DATABASE,
57
+ enabled: true,
58
+ password: redisEnv.REDIS_PASSWORD,
59
+ prefix: redisEnv.REDIS_PREFIX,
60
+ tls: redisEnv.REDIS_TLS,
61
+ url: redisEnv.REDIS_URL,
62
+ username: redisEnv.REDIS_USERNAME,
105
63
  };
106
64
  };