@lobehub/lobehub 2.0.0-next.123 → 2.0.0-next.125
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/.cursor/rules/db-migrations.mdc +16 -1
- package/.cursor/rules/project-introduce.mdc +1 -1
- package/.cursor/rules/project-structure.mdc +20 -2
- package/.env.example +148 -65
- package/.env.example.development +6 -8
- package/AGENTS.md +1 -3
- package/CHANGELOG.md +51 -0
- package/Dockerfile +6 -6
- package/GEMINI.md +63 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +38 -0
- package/docs/self-hosting/advanced/auth.mdx +75 -2
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +75 -2
- package/docs/self-hosting/environment-variables/auth.mdx +187 -1
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +187 -1
- package/locales/en-US/auth.json +93 -0
- package/locales/zh-CN/auth.json +107 -1
- package/package.json +5 -2
- package/packages/const/src/auth.ts +2 -1
- package/packages/database/migrations/0048_add_editor_data.sql +1 -0
- package/packages/database/migrations/0049_better_auth.sql +49 -0
- package/packages/database/migrations/meta/0048_snapshot.json +7913 -0
- package/packages/database/migrations/meta/0049_snapshot.json +8151 -0
- package/packages/database/migrations/meta/_journal.json +14 -0
- package/packages/database/src/core/migrations.json +19 -0
- package/packages/database/src/index.ts +1 -0
- package/packages/database/src/models/__tests__/session.test.ts +1 -2
- package/packages/database/src/models/user.ts +9 -8
- package/packages/database/src/repositories/tableViewer/index.test.ts +2 -2
- package/packages/database/src/schemas/agent.ts +1 -0
- package/packages/database/src/schemas/betterAuth.ts +63 -0
- package/packages/database/src/schemas/index.ts +1 -0
- package/packages/database/src/schemas/ragEvals.ts +1 -2
- package/packages/database/src/schemas/user.ts +3 -2
- package/packages/database/src/server/models/__tests__/user.test.ts +1 -4
- package/packages/types/src/user/preference.ts +11 -0
- package/packages/utils/src/server/__tests__/auth.test.ts +52 -0
- package/packages/utils/src/server/auth.ts +18 -1
- package/src/app/(backend)/api/auth/[...all]/route.ts +19 -0
- package/src/app/(backend)/api/auth/check-user/route.ts +62 -0
- package/src/app/(backend)/middleware/auth/index.ts +14 -0
- package/src/app/(backend)/middleware/auth/utils.test.ts +16 -0
- package/src/app/(backend)/middleware/auth/utils.ts +13 -10
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +1 -0
- package/src/app/[variants]/(auth)/reset-password/layout.tsx +12 -0
- package/src/app/[variants]/(auth)/reset-password/page.tsx +209 -0
- package/src/app/[variants]/(auth)/signin/layout.tsx +12 -0
- package/src/app/[variants]/(auth)/signin/page.tsx +448 -0
- package/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +192 -0
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +31 -6
- package/src/app/[variants]/(auth)/verify-email/layout.tsx +12 -0
- package/src/app/[variants]/(auth)/verify-email/page.tsx +164 -0
- package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +12 -10
- package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +13 -11
- package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +15 -8
- package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/index.tsx +27 -30
- package/src/app/[variants]/(main)/profile/(home)/Client.tsx +306 -52
- package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx +89 -47
- package/src/auth.ts +118 -0
- package/src/components/NextAuth/AuthIcons.tsx +3 -1
- package/src/envs/auth.ts +260 -13
- package/src/envs/email.ts +37 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +6 -2
- package/src/features/User/UserPanel/PanelContent.tsx +6 -5
- package/src/features/User/__tests__/PanelContent.test.tsx +15 -6
- package/src/features/User/__tests__/UserAvatar.test.tsx +17 -6
- package/src/features/User/__tests__/useMenu.test.tsx +14 -12
- package/src/layout/AuthProvider/BetterAuth/UserUpdater.tsx +51 -0
- package/src/layout/AuthProvider/BetterAuth/index.tsx +14 -0
- package/src/layout/AuthProvider/index.tsx +3 -0
- package/src/layout/GlobalProvider/StoreInitialization.tsx +3 -3
- package/src/libs/better-auth/auth-client.ts +34 -0
- package/src/libs/better-auth/constants.ts +13 -0
- package/src/libs/better-auth/email-templates/index.ts +3 -0
- package/src/libs/better-auth/email-templates/magic-link.ts +98 -0
- package/src/libs/better-auth/email-templates/reset-password.ts +91 -0
- package/src/libs/better-auth/email-templates/verification.ts +108 -0
- package/src/libs/better-auth/sso/helpers.ts +61 -0
- package/src/libs/better-auth/sso/index.ts +113 -0
- package/src/libs/better-auth/sso/providers/auth0.ts +33 -0
- package/src/libs/better-auth/sso/providers/authelia.ts +35 -0
- package/src/libs/better-auth/sso/providers/authentik.ts +35 -0
- package/src/libs/better-auth/sso/providers/casdoor.ts +48 -0
- package/src/libs/better-auth/sso/providers/cloudflare-zero-trust.ts +41 -0
- package/src/libs/better-auth/sso/providers/cognito.ts +45 -0
- package/src/libs/better-auth/sso/providers/feishu.ts +181 -0
- package/src/libs/better-auth/sso/providers/generic-oidc.ts +44 -0
- package/src/libs/better-auth/sso/providers/github.ts +30 -0
- package/src/libs/better-auth/sso/providers/google.ts +30 -0
- package/src/libs/better-auth/sso/providers/keycloak.ts +35 -0
- package/src/libs/better-auth/sso/providers/logto.ts +38 -0
- package/src/libs/better-auth/sso/providers/microsoft.ts +65 -0
- package/src/libs/better-auth/sso/providers/okta.ts +37 -0
- package/src/libs/better-auth/sso/providers/wechat.ts +140 -0
- package/src/libs/better-auth/sso/providers/zitadel.ts +54 -0
- package/src/libs/better-auth/sso/types.ts +25 -0
- package/src/libs/better-auth/utils/client.ts +1 -0
- package/src/libs/better-auth/utils/common.ts +20 -0
- package/src/libs/better-auth/utils/server.test.ts +61 -0
- package/src/libs/better-auth/utils/server.ts +18 -0
- package/src/libs/trpc/lambda/context.test.ts +116 -0
- package/src/libs/trpc/lambda/context.ts +27 -0
- package/src/libs/trpc/middleware/userAuth.ts +4 -2
- package/src/locales/default/auth.ts +114 -1
- package/src/proxy.ts +71 -7
- package/src/server/globalConfig/index.ts +12 -1
- package/src/server/routers/lambda/user.ts +4 -0
- package/src/server/services/email/README.md +241 -0
- package/src/server/services/email/impls/index.test.ts +39 -0
- package/src/server/services/email/impls/index.ts +32 -0
- package/src/server/services/email/impls/nodemailer/index.ts +108 -0
- package/src/server/services/email/impls/nodemailer/type.ts +31 -0
- package/src/server/services/email/impls/type.ts +61 -0
- package/src/server/services/email/index.test.ts +144 -0
- package/src/server/services/email/index.ts +40 -0
- package/src/services/user/index.test.ts +162 -2
- package/src/services/user/index.ts +6 -3
- package/src/store/aiInfra/slices/aiProvider/action.ts +4 -4
- package/src/store/user/slices/auth/action.test.ts +213 -16
- package/src/store/user/slices/auth/action.ts +86 -1
- package/src/store/user/slices/auth/initialState.ts +13 -2
- package/src/store/user/slices/auth/selectors.ts +6 -2
- package/src/store/user/slices/common/action.ts +5 -1
- package/src/app/(backend)/api/auth/[...nextauth]/route.ts +0 -3
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
import { SSOProvider } from '@lobechat/types';
|
|
1
2
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
3
|
|
|
3
|
-
import { enableAuth, enableClerk, enableNextAuth } from '@/const/auth';
|
|
4
|
+
import { enableAuth, enableBetterAuth, enableClerk, enableNextAuth } from '@/const/auth';
|
|
5
|
+
import { userService } from '@/services/user';
|
|
4
6
|
|
|
5
7
|
import type { UserStore } from '../../store';
|
|
6
8
|
|
|
9
|
+
interface AuthProvidersData {
|
|
10
|
+
isEmailPasswordAuth: boolean;
|
|
11
|
+
providers: SSOProvider[];
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
export interface UserAuthAction {
|
|
8
15
|
enableAuth: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Fetch auth providers (SSO accounts) for the current user
|
|
18
|
+
*/
|
|
19
|
+
fetchAuthProviders: () => Promise<void>;
|
|
9
20
|
/**
|
|
10
21
|
* universal logout method
|
|
11
22
|
*/
|
|
@@ -14,8 +25,40 @@ export interface UserAuthAction {
|
|
|
14
25
|
* universal login method
|
|
15
26
|
*/
|
|
16
27
|
openLogin: () => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Refresh auth providers after link/unlink
|
|
30
|
+
*/
|
|
31
|
+
refreshAuthProviders: () => Promise<void>;
|
|
17
32
|
}
|
|
18
33
|
|
|
34
|
+
const fetchAuthProvidersData = async (): Promise<AuthProvidersData> => {
|
|
35
|
+
if (enableBetterAuth) {
|
|
36
|
+
const { accountInfo, listAccounts } = await import('@/libs/better-auth/auth-client');
|
|
37
|
+
const result = await listAccounts();
|
|
38
|
+
const accounts = result.data || [];
|
|
39
|
+
const isEmailPasswordAuth = accounts.some((account) => account.providerId === 'credential');
|
|
40
|
+
const providers = await Promise.all(
|
|
41
|
+
accounts
|
|
42
|
+
.filter((account) => account.providerId !== 'credential')
|
|
43
|
+
.map(async (account) => {
|
|
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 { isEmailPasswordAuth, providers };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback for NextAuth
|
|
58
|
+
const providers = await userService.getUserSSOProviders();
|
|
59
|
+
return { isEmailPasswordAuth: false, providers };
|
|
60
|
+
};
|
|
61
|
+
|
|
19
62
|
export const createAuthSlice: StateCreator<
|
|
20
63
|
UserStore,
|
|
21
64
|
[['zustand/devtools', never]],
|
|
@@ -25,6 +68,18 @@ export const createAuthSlice: StateCreator<
|
|
|
25
68
|
enableAuth: () => {
|
|
26
69
|
return enableAuth;
|
|
27
70
|
},
|
|
71
|
+
fetchAuthProviders: async () => {
|
|
72
|
+
// Skip if already loaded
|
|
73
|
+
if (get().isLoadedAuthProviders) return;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const { isEmailPasswordAuth, providers } = await fetchAuthProvidersData();
|
|
77
|
+
set({ authProviders: providers, isEmailPasswordAuth, isLoadedAuthProviders: true });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Failed to fetch auth providers:', error);
|
|
80
|
+
set({ isLoadedAuthProviders: true });
|
|
81
|
+
}
|
|
82
|
+
},
|
|
28
83
|
logout: async () => {
|
|
29
84
|
if (enableClerk) {
|
|
30
85
|
get().clerkSignOut?.({ redirectUrl: location.toString() });
|
|
@@ -32,6 +87,21 @@ export const createAuthSlice: StateCreator<
|
|
|
32
87
|
return;
|
|
33
88
|
}
|
|
34
89
|
|
|
90
|
+
if (enableBetterAuth) {
|
|
91
|
+
const { signOut } = await import('@/libs/better-auth/auth-client');
|
|
92
|
+
await signOut({
|
|
93
|
+
fetchOptions: {
|
|
94
|
+
onSuccess: () => {
|
|
95
|
+
// Use window.location.href to trigger a full page reload
|
|
96
|
+
// This ensures all client-side state (React, Zustand, cache) is cleared
|
|
97
|
+
window.location.href = '/signin';
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
35
105
|
if (enableNextAuth) {
|
|
36
106
|
const { signOut } = await import('next-auth/react');
|
|
37
107
|
signOut();
|
|
@@ -49,6 +119,13 @@ export const createAuthSlice: StateCreator<
|
|
|
49
119
|
return;
|
|
50
120
|
}
|
|
51
121
|
|
|
122
|
+
if (enableBetterAuth) {
|
|
123
|
+
const currentUrl = location.toString();
|
|
124
|
+
window.location.href = `/signin?callbackUrl=${encodeURIComponent(currentUrl)}`;
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
52
129
|
if (enableNextAuth) {
|
|
53
130
|
const { signIn } = await import('next-auth/react');
|
|
54
131
|
// Check if only one provider is available
|
|
@@ -60,4 +137,12 @@ export const createAuthSlice: StateCreator<
|
|
|
60
137
|
signIn();
|
|
61
138
|
}
|
|
62
139
|
},
|
|
140
|
+
refreshAuthProviders: async () => {
|
|
141
|
+
try {
|
|
142
|
+
const { isEmailPasswordAuth, providers } = await fetchAuthProvidersData();
|
|
143
|
+
set({ authProviders: providers, isEmailPasswordAuth });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Failed to refresh auth providers:', error);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
63
148
|
});
|
|
@@ -6,17 +6,25 @@ import {
|
|
|
6
6
|
UserProfileProps,
|
|
7
7
|
UserResource,
|
|
8
8
|
} from '@clerk/types';
|
|
9
|
+
import { SSOProvider } from '@lobechat/types';
|
|
9
10
|
|
|
11
|
+
import { enableClerk } from '@/const/auth';
|
|
10
12
|
import { LobeUser } from '@/types/user';
|
|
11
13
|
|
|
12
14
|
export interface UserAuthState {
|
|
15
|
+
authProviders?: SSOProvider[];
|
|
13
16
|
clerkOpenUserProfile?: (props?: UserProfileProps) => void;
|
|
14
|
-
|
|
15
17
|
clerkSession?: SignedInSessionResource;
|
|
18
|
+
|
|
16
19
|
clerkSignIn?: (props?: SignInProps) => void;
|
|
17
20
|
clerkSignOut?: SignOut;
|
|
18
21
|
clerkUser?: UserResource;
|
|
22
|
+
/**
|
|
23
|
+
* Whether user registered with email/password (credential login)
|
|
24
|
+
*/
|
|
25
|
+
isEmailPasswordAuth?: boolean;
|
|
19
26
|
isLoaded?: boolean;
|
|
27
|
+
isLoadedAuthProviders?: boolean;
|
|
20
28
|
|
|
21
29
|
isSignedIn?: boolean;
|
|
22
30
|
nextSession?: Session;
|
|
@@ -25,4 +33,7 @@ export interface UserAuthState {
|
|
|
25
33
|
user?: LobeUser;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
export const initialAuthState: UserAuthState = {
|
|
36
|
+
export const initialAuthState: UserAuthState = {
|
|
37
|
+
// Clerk doesn't need to fetch auth providers
|
|
38
|
+
isLoadedAuthProviders: enableClerk ? true : undefined,
|
|
39
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BRANDING_NAME, isDesktop } from '@lobechat/const';
|
|
2
|
-
import { LobeUser } from '@lobechat/types';
|
|
2
|
+
import { LobeUser, SSOProvider } from '@lobechat/types';
|
|
3
3
|
import { t } from 'i18next';
|
|
4
4
|
|
|
5
|
-
import { enableAuth, enableClerk, enableNextAuth } from '@/const/auth';
|
|
5
|
+
import { enableAuth, enableBetterAuth, enableClerk, enableNextAuth } from '@/const/auth';
|
|
6
6
|
import type { UserStore } from '@/store/user';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_USERNAME = BRANDING_NAME;
|
|
@@ -56,9 +56,13 @@ const isLogin = (s: UserStore) => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
export const authSelectors = {
|
|
59
|
+
authProviders: (s: UserStore): SSOProvider[] => s.authProviders || [],
|
|
60
|
+
isEmailPasswordAuth: (s: UserStore) => s.isEmailPasswordAuth ?? false,
|
|
59
61
|
isLoaded: (s: UserStore) => s.isLoaded,
|
|
62
|
+
isLoadedAuthProviders: (s: UserStore) => s.isLoadedAuthProviders ?? false,
|
|
60
63
|
isLogin,
|
|
61
64
|
isLoginWithAuth: (s: UserStore) => s.isSignedIn,
|
|
65
|
+
isLoginWithBetterAuth: (s: UserStore): boolean => (s.isSignedIn && enableBetterAuth) || false,
|
|
62
66
|
isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,
|
|
63
67
|
isLoginWithNextAuth: (s: UserStore): boolean => (s.isSignedIn && !!enableNextAuth) || false,
|
|
64
68
|
};
|
|
@@ -24,6 +24,7 @@ const GET_USER_STATE_KEY = 'initUserState';
|
|
|
24
24
|
export interface CommonAction {
|
|
25
25
|
refreshUserState: () => Promise<void>;
|
|
26
26
|
updateAvatar: (avatar: string) => Promise<void>;
|
|
27
|
+
updateFullName: (fullName: string) => Promise<void>;
|
|
27
28
|
updateKeyVaultConfig: (provider: string, config: any) => Promise<void>;
|
|
28
29
|
useCheckTrace: (shouldFetch: boolean) => SWRResponse;
|
|
29
30
|
useInitUserState: (
|
|
@@ -46,9 +47,12 @@ export const createCommonSlice: StateCreator<
|
|
|
46
47
|
await mutate(GET_USER_STATE_KEY);
|
|
47
48
|
},
|
|
48
49
|
updateAvatar: async (avatar) => {
|
|
49
|
-
// 1. 更新服务端/数据库中的头像
|
|
50
50
|
await userService.updateAvatar(avatar);
|
|
51
|
+
await get().refreshUserState();
|
|
52
|
+
},
|
|
51
53
|
|
|
54
|
+
updateFullName: async (fullName) => {
|
|
55
|
+
await userService.updateFullName(fullName);
|
|
52
56
|
await get().refreshUserState();
|
|
53
57
|
},
|
|
54
58
|
|