@lobehub/chat 1.7.10 → 1.8.0

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 (42) hide show
  1. package/.env.example +8 -0
  2. package/CHANGELOG.md +25 -0
  3. package/package.json +1 -1
  4. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +9 -5
  5. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +27 -10
  6. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +22 -3
  7. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +2 -2
  8. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +3 -2
  9. package/src/app/(main)/settings/common/features/Common.tsx +2 -43
  10. package/src/app/(main)/settings/common/features/Theme/index.tsx +10 -3
  11. package/src/app/api/auth/[...nextauth]/route.ts +2 -2
  12. package/src/app/api/auth/error/AuthErrorPage.tsx +38 -0
  13. package/src/app/api/auth/error/page.tsx +5 -0
  14. package/src/database/server/migrations/0004_add_next_auth.sql +60 -0
  15. package/src/database/server/migrations/meta/0004_snapshot.json +2119 -0
  16. package/src/database/server/migrations/meta/_journal.json +7 -0
  17. package/src/database/server/models/__tests__/nextauth.test.ts +496 -0
  18. package/src/database/server/models/__tests__/user.test.ts +13 -0
  19. package/src/database/server/models/user.ts +4 -0
  20. package/src/database/server/schemas/lobechat.ts +7 -0
  21. package/src/database/server/schemas/nextauth.ts +90 -0
  22. package/src/layout/GlobalProvider/StoreInitialization.tsx +18 -3
  23. package/src/libs/next-auth/adapter/index.ts +264 -0
  24. package/src/libs/next-auth/adapter/utils.ts +62 -0
  25. package/src/libs/next-auth/auth.config.ts +45 -0
  26. package/src/libs/next-auth/edge.ts +26 -0
  27. package/src/libs/next-auth/index.ts +26 -39
  28. package/src/libs/next-auth/sso-providers/auth0.ts +11 -0
  29. package/src/libs/next-auth/sso-providers/authentik.ts +12 -0
  30. package/src/libs/next-auth/sso-providers/azure-ad.ts +12 -0
  31. package/src/libs/next-auth/sso-providers/github.ts +11 -0
  32. package/src/libs/next-auth/sso-providers/sso.config.ts +8 -0
  33. package/src/libs/next-auth/sso-providers/zitadel.ts +9 -0
  34. package/src/libs/trpc/middleware/password.test.ts +6 -0
  35. package/src/libs/trpc/middleware/userAuth.test.ts +6 -0
  36. package/src/middleware.ts +3 -2
  37. package/src/server/context.ts +22 -5
  38. package/src/server/routers/edge/config/index.test.ts +6 -0
  39. package/src/store/agent/slices/chat/action.test.ts +16 -2
  40. package/src/store/agent/slices/chat/action.ts +3 -2
  41. package/src/store/user/slices/auth/selectors.ts +2 -0
  42. package/src/types/next-auth.d.ts +3 -0
package/src/middleware.ts CHANGED
@@ -2,7 +2,7 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
2
2
  import { NextResponse } from 'next/server';
3
3
 
4
4
  import { authEnv } from '@/config/auth';
5
- import { auth } from '@/libs/next-auth';
5
+ import NextAuthEdge from '@/libs/next-auth/edge';
6
6
 
7
7
  import { OAUTH_AUTHORIZED } from './const/auth';
8
8
 
@@ -20,7 +20,8 @@ export const config = {
20
20
 
21
21
  const defaultMiddleware = () => NextResponse.next();
22
22
 
23
- const nextAuthMiddleware = auth((req) => {
23
+ // Initialize an Edge compatible NextAuth middleware
24
+ const nextAuthMiddleware = NextAuthEdge.auth((req) => {
24
25
  // skip the '/' route
25
26
  if (req.nextUrl.pathname === '/') return NextResponse.next();
26
27
 
@@ -1,15 +1,18 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  import { getAuth } from '@clerk/nextjs/server';
3
+ import { User } from 'next-auth';
3
4
  import { NextRequest } from 'next/server';
4
5
 
5
- import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk } from '@/const/auth';
6
+ import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk, enableNextAuth } from '@/const/auth';
7
+ import NextAuthEdge from '@/libs/next-auth/edge';
6
8
 
7
9
  type ClerkAuth = ReturnType<typeof getAuth>;
8
10
 
9
11
  export interface AuthContext {
10
- auth?: ClerkAuth;
11
12
  authorizationHeader?: string | null;
13
+ clerkAuth?: ClerkAuth;
12
14
  jwtPayload?: JWTPayload | null;
15
+ nextAuth?: User;
13
16
  userId?: string | null;
14
17
  }
15
18
 
@@ -18,12 +21,14 @@ export interface AuthContext {
18
21
  * This is useful for testing when we don't want to mock Next.js' request/response
19
22
  */
20
23
  export const createContextInner = async (params?: {
21
- auth?: ClerkAuth;
22
24
  authorizationHeader?: string | null;
25
+ clerkAuth?: ClerkAuth;
26
+ nextAuth?: User;
23
27
  userId?: string | null;
24
28
  }): Promise<AuthContext> => ({
25
- auth: params?.auth,
26
29
  authorizationHeader: params?.authorizationHeader,
30
+ clerkAuth: params?.clerkAuth,
31
+ nextAuth: params?.nextAuth,
27
32
  userId: params?.userId,
28
33
  });
29
34
 
@@ -45,7 +50,19 @@ export const createContext = async (request: NextRequest): Promise<Context> => {
45
50
  auth = getAuth(request);
46
51
 
47
52
  userId = auth.userId;
53
+ return createContextInner({ authorizationHeader: authorization, clerkAuth: auth, userId });
48
54
  }
49
55
 
50
- return createContextInner({ auth, authorizationHeader: authorization, userId });
56
+ if (enableNextAuth) {
57
+ try {
58
+ const session = await NextAuthEdge.auth();
59
+ if (session && session?.user?.id) {
60
+ auth = session.user;
61
+ userId = session.user.id;
62
+ }
63
+ return createContextInner({ authorizationHeader: authorization, nextAuth: auth, userId });
64
+ } catch {}
65
+ }
66
+
67
+ return createContextInner({ authorizationHeader: authorization, userId });
51
68
  };
@@ -14,6 +14,12 @@ const createCaller = createCallerFactory(configRouter);
14
14
  let ctx: AuthContext;
15
15
  let router: ReturnType<typeof createCaller>;
16
16
 
17
+ vi.mock('@/libs/next-auth/edge', () => {
18
+ return {
19
+ auth: vi.fn().mockResolvedValue(undefined),
20
+ };
21
+ });
22
+
17
23
  beforeEach(async () => {
18
24
  vi.resetAllMocks();
19
25
  ctx = await createContextInner();
@@ -207,7 +207,7 @@ describe('AgentSlice', () => {
207
207
  model: 'gemini-pro',
208
208
  } as any);
209
209
 
210
- renderHook(() => result.current.useInitAgentStore());
210
+ renderHook(() => result.current.useInitAgentStore(true));
211
211
 
212
212
  await waitFor(async () => {
213
213
  expect(result.current.agentMap[INBOX_SESSION_ID]).toEqual({ model: 'gemini-pro' });
@@ -215,12 +215,26 @@ describe('AgentSlice', () => {
215
215
  });
216
216
  });
217
217
 
218
+ it('should not modify state if user not logged in', async () => {
219
+ const { result } = renderHook(() => useAgentStore());
220
+ vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValue({
221
+ model: 'gemini-pro',
222
+ } as any);
223
+
224
+ renderHook(() => result.current.useInitAgentStore(false));
225
+
226
+ await waitFor(async () => {
227
+ expect(result.current.agentMap[INBOX_SESSION_ID]).toBeUndefined();
228
+ expect(result.current.isInboxAgentConfigInit).toBe(false);
229
+ });
230
+ });
231
+
218
232
  it('should not modify state on failure', async () => {
219
233
  const { result } = renderHook(() => useAgentStore());
220
234
 
221
235
  vi.spyOn(globalService, 'getDefaultAgentConfig').mockRejectedValueOnce(new Error());
222
236
 
223
- renderHook(() => result.current.useInitAgentStore());
237
+ renderHook(() => result.current.useInitAgentStore(true));
224
238
 
225
239
  await waitFor(async () => {
226
240
  expect(result.current.agentMap[INBOX_SESSION_ID]).toBeUndefined();
@@ -27,6 +27,7 @@ export interface AgentChatAction {
27
27
 
28
28
  useFetchAgentConfig: (id: string) => SWRResponse<LobeAgentConfig>;
29
29
  useInitAgentStore: (
30
+ isLogin: boolean | undefined,
30
31
  defaultAgentConfig?: DeepPartial<LobeAgentConfig>,
31
32
  ) => SWRResponse<DeepPartial<LobeAgentConfig>>;
32
33
 
@@ -111,9 +112,9 @@ export const createChatSlice: StateCreator<
111
112
  suspense: true,
112
113
  },
113
114
  ),
114
- useInitAgentStore: (defaultAgentConfig) =>
115
+ useInitAgentStore: (isLogin, defaultAgentConfig) =>
115
116
  useOnlyFetchOnceSWR<DeepPartial<LobeAgentConfig>>(
116
- 'fetchInboxAgentConfig',
117
+ !!isLogin ? 'fetchInboxAgentConfig' : null,
117
118
  () => sessionService.getSessionConfig(INBOX_SESSION_ID),
118
119
  {
119
120
  onSuccess: (data) => {
@@ -41,6 +41,8 @@ const isLogin = (s: UserStore) => {
41
41
  };
42
42
 
43
43
  export const authSelectors = {
44
+ enabledAuth: (s: UserStore): boolean => s.enableAuth(),
45
+ enabledNextAuth: (s: UserStore): boolean => !!s.enabledNextAuth,
44
46
  isLoaded: (s: UserStore) => s.isLoaded,
45
47
  isLogin,
46
48
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
@@ -9,6 +9,9 @@ declare module 'next-auth' {
9
9
  firstName?: string;
10
10
  } & DefaultSession['user'];
11
11
  }
12
+ interface User {
13
+ providerAccountId?: string;
14
+ }
12
15
  /**
13
16
  * More types can be extends here
14
17
  * ref: https://authjs.dev/getting-started/typescript