@lobehub/lobehub 2.0.0-next.355 → 2.0.0-next.357
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/package.json +4 -6
- package/packages/database/src/schemas/nextauth.ts +7 -2
- package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +370 -0
- package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +18 -5
- 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/cron/[cronId]/features/CronJobContentEditor.tsx +34 -21
- 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/EditorCanvas/DiffAllToolbar.tsx +4 -5
- package/src/features/EditorCanvas/DocumentIdMode.tsx +21 -1
- 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/server/globalConfig/index.ts +1 -3
- 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/user/index.test.ts +0 -14
- package/src/services/user/index.ts +0 -4
- package/src/store/document/slices/document/action.ts +1 -0
- 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
|
@@ -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 };
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import debug from 'debug';
|
|
2
|
-
import { type NextRequest, NextResponse } from 'next/server';
|
|
3
|
-
|
|
4
|
-
import { serverDBEnv } from '@/config/db';
|
|
5
|
-
import { serverDB } from '@/database/server';
|
|
6
|
-
import { dateKeys } from '@/libs/next-auth/adapter';
|
|
7
|
-
import { NextAuthUserService } from '@/server/services/nextAuthUser';
|
|
8
|
-
|
|
9
|
-
const log = debug('lobe-next-auth:api:auth:adapter');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @description Process the db query for the NextAuth adapter.
|
|
13
|
-
* Returns the db query result directly and let NextAuth handle the raw results.
|
|
14
|
-
* @returns {
|
|
15
|
-
* success: boolean; // Only return false if the database query fails or the action is invalid.
|
|
16
|
-
* data?: any;
|
|
17
|
-
* error?: string;
|
|
18
|
-
* }
|
|
19
|
-
*/
|
|
20
|
-
export async function POST(req: NextRequest) {
|
|
21
|
-
try {
|
|
22
|
-
// try validate the request
|
|
23
|
-
if (
|
|
24
|
-
!req.headers.get('Authorization') ||
|
|
25
|
-
req.headers.get('Authorization')?.trim() !== `Bearer ${serverDBEnv.KEY_VAULTS_SECRET}`
|
|
26
|
-
) {
|
|
27
|
-
log('Unauthorized request, missing or invalid Authorization header');
|
|
28
|
-
return NextResponse.json({ error: 'Unauthorized', success: false }, { status: 401 });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Parse the request body
|
|
32
|
-
const data = await req.json();
|
|
33
|
-
log('Received request data:', data);
|
|
34
|
-
// Preprocess
|
|
35
|
-
if (data?.data) {
|
|
36
|
-
for (const key of dateKeys) {
|
|
37
|
-
if (data?.data && data.data[key]) {
|
|
38
|
-
data.data[key] = new Date(data.data[key]);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const service = new NextAuthUserService(serverDB);
|
|
44
|
-
let result;
|
|
45
|
-
switch (data.action) {
|
|
46
|
-
case 'createAuthenticator': {
|
|
47
|
-
result = await service.createAuthenticator(data.data);
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case 'createSession': {
|
|
51
|
-
result = await service.createSession(data.data);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
case 'createUser': {
|
|
55
|
-
result = await service.createUser(data.data);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
case 'createVerificationToken': {
|
|
59
|
-
result = await service.createVerificationToken(data.data);
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
case 'deleteSession': {
|
|
63
|
-
result = await service.deleteSession(data.data);
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case 'deleteUser': {
|
|
67
|
-
result = await service.deleteUser(data.data);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
case 'getAccount': {
|
|
71
|
-
result = await service.getAccount(data.data.providerAccountId, data.data.provider);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case 'getAuthenticator': {
|
|
75
|
-
result = await service.getAuthenticator(data.data);
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
case 'getSessionAndUser': {
|
|
79
|
-
result = await service.getSessionAndUser(data.data);
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
case 'getUser': {
|
|
83
|
-
result = await service.getUser(data.data);
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
case 'getUserByAccount': {
|
|
87
|
-
result = await service.getUserByAccount(data.data);
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
case 'getUserByEmail': {
|
|
91
|
-
result = await service.getUserByEmail(data.data);
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
case 'linkAccount': {
|
|
95
|
-
result = await service.linkAccount(data.data);
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
case 'listAuthenticatorsByUserId': {
|
|
99
|
-
result = await service.listAuthenticatorsByUserId(data.data);
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
case 'unlinkAccount': {
|
|
103
|
-
result = await service.unlinkAccount(data.data);
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
case 'updateAuthenticatorCounter': {
|
|
107
|
-
result = await service.updateAuthenticatorCounter(
|
|
108
|
-
data.data.credentialID,
|
|
109
|
-
data.data.counter,
|
|
110
|
-
);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
case 'updateSession': {
|
|
114
|
-
result = await service.updateSession(data.data);
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
case 'updateUser': {
|
|
118
|
-
result = await service.updateUser(data.data);
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
case 'useVerificationToken': {
|
|
122
|
-
result = await service.useVerificationToken(data.data);
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
default: {
|
|
126
|
-
return NextResponse.json({ error: 'Invalid action', success: false }, { status: 400 });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return NextResponse.json({ data: result, success: true });
|
|
130
|
-
} catch (error) {
|
|
131
|
-
log('Error processing request:');
|
|
132
|
-
log(error);
|
|
133
|
-
return NextResponse.json({ error, success: false }, { status: 400 });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export const runtime = 'nodejs';
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { signIn } from 'next-auth/react';
|
|
4
|
-
import { useSearchParams } from '@/libs/next/navigation';
|
|
5
|
-
import { memo } from 'react';
|
|
6
|
-
|
|
7
|
-
import ErrorCapture from '@/components/Error';
|
|
8
|
-
|
|
9
|
-
enum ErrorEnum {
|
|
10
|
-
AccessDenied = 'AccessDenied',
|
|
11
|
-
Configuration = 'Configuration',
|
|
12
|
-
Default = 'Default',
|
|
13
|
-
Verification = 'Verification',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const errorMap = {
|
|
17
|
-
[ErrorEnum.Configuration]:
|
|
18
|
-
'Wrong configuration, make sure you have the correct environment variables set. Visit https://lobehub.com/docs/self-hosting/advanced/authentication for more details.',
|
|
19
|
-
[ErrorEnum.AccessDenied]:
|
|
20
|
-
'Access was denied. Visit https://authjs.dev/reference/core/errors#accessdenied for more details. ',
|
|
21
|
-
[ErrorEnum.Verification]:
|
|
22
|
-
'Verification error, visit https://authjs.dev/reference/core/errors#verification for more details.',
|
|
23
|
-
[ErrorEnum.Default]:
|
|
24
|
-
'There was a problem when trying to authenticate. Visit https://authjs.dev/reference/core/errors for more details.',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default memo(() => {
|
|
28
|
-
const search = useSearchParams();
|
|
29
|
-
const error = search.get('error') as ErrorEnum;
|
|
30
|
-
const props = {
|
|
31
|
-
error: {
|
|
32
|
-
cause: error,
|
|
33
|
-
message: errorMap[error] || 'Unknown error type.',
|
|
34
|
-
name: 'NextAuth Error',
|
|
35
|
-
},
|
|
36
|
-
reset: () => signIn(undefined, { callbackUrl: '/' }),
|
|
37
|
-
};
|
|
38
|
-
console.log('[NextAuth] Error:', props.error);
|
|
39
|
-
return <ErrorCapture {...props} />;
|
|
40
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Suspense } from 'react';
|
|
2
|
-
|
|
3
|
-
import Loading from '@/components/Loading/BrandTextLoading';
|
|
4
|
-
|
|
5
|
-
import AuthErrorPage from './AuthErrorPage';
|
|
6
|
-
|
|
7
|
-
export default () => (
|
|
8
|
-
<Suspense fallback={<Loading debugId="Auth > Error" />}>
|
|
9
|
-
<AuthErrorPage />
|
|
10
|
-
</Suspense>
|
|
11
|
-
);
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { BRANDING_NAME } from '@lobechat/business-const';
|
|
4
|
-
import { DOCUMENTS_REFER_URL, PRIVACY_URL, TERMS_URL } from '@lobechat/const';
|
|
5
|
-
import { Button, Skeleton, Text } from '@lobehub/ui';
|
|
6
|
-
import { LobeHub } from '@lobehub/ui/brand';
|
|
7
|
-
import { Col, Flex, Row } from 'antd';
|
|
8
|
-
import { createStaticStyles } from 'antd-style';
|
|
9
|
-
import { AuthError } from 'next-auth';
|
|
10
|
-
import { signIn } from 'next-auth/react';
|
|
11
|
-
import { useRouter, useSearchParams } from '@/libs/next/navigation';
|
|
12
|
-
import { memo, useState } from 'react';
|
|
13
|
-
import { useTranslation } from 'react-i18next';
|
|
14
|
-
|
|
15
|
-
import BrandWatermark from '@/components/BrandWatermark';
|
|
16
|
-
import AuthIcons from '@/components/NextAuth/AuthIcons';
|
|
17
|
-
import { useUserStore } from '@/store/user';
|
|
18
|
-
|
|
19
|
-
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
20
|
-
button: css`
|
|
21
|
-
text-transform: capitalize;
|
|
22
|
-
`,
|
|
23
|
-
container: css`
|
|
24
|
-
min-width: 360px;
|
|
25
|
-
border: 1px solid ${cssVar.colorBorder};
|
|
26
|
-
border-radius: ${cssVar.borderRadiusLG}px;
|
|
27
|
-
background: ${cssVar.colorBgContainer};
|
|
28
|
-
`,
|
|
29
|
-
contentCard: css`
|
|
30
|
-
padding-block: 2.5rem;
|
|
31
|
-
padding-inline: 2rem;
|
|
32
|
-
`,
|
|
33
|
-
description: css`
|
|
34
|
-
margin: 0;
|
|
35
|
-
color: ${cssVar.colorTextSecondary};
|
|
36
|
-
`,
|
|
37
|
-
footer: css`
|
|
38
|
-
padding: 1rem;
|
|
39
|
-
border-block-start: 1px solid ${cssVar.colorBorder};
|
|
40
|
-
border-radius: 0 0 8px 8px;
|
|
41
|
-
|
|
42
|
-
color: ${cssVar.colorTextDescription};
|
|
43
|
-
|
|
44
|
-
background: ${cssVar.colorBgElevated};
|
|
45
|
-
`,
|
|
46
|
-
text: css`
|
|
47
|
-
text-align: center;
|
|
48
|
-
`,
|
|
49
|
-
title: css`
|
|
50
|
-
margin: 0;
|
|
51
|
-
color: ${cssVar.colorTextHeading};
|
|
52
|
-
`,
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
const BtnListLoading = memo(() => {
|
|
56
|
-
return (
|
|
57
|
-
<Flex gap={'small'} vertical>
|
|
58
|
-
<Skeleton.Button active style={{ minWidth: 300 }} />
|
|
59
|
-
<Skeleton.Button active style={{ minWidth: 300 }} />
|
|
60
|
-
<Skeleton.Button active style={{ minWidth: 300 }} />
|
|
61
|
-
</Flex>
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Follow the implementation from AuthJS official documentation,
|
|
67
|
-
* but using client components.
|
|
68
|
-
* ref: https://authjs.dev/guides/pages/signin
|
|
69
|
-
*/
|
|
70
|
-
export default memo(() => {
|
|
71
|
-
const { t } = useTranslation('auth');
|
|
72
|
-
const { t: tCommon } = useTranslation('common');
|
|
73
|
-
const router = useRouter();
|
|
74
|
-
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
|
|
75
|
-
|
|
76
|
-
const oAuthSSOProviders = useUserStore((s) => s.oAuthSSOProviders);
|
|
77
|
-
|
|
78
|
-
const searchParams = useSearchParams();
|
|
79
|
-
|
|
80
|
-
// Redirect back to the page url, fallback to '/' if failed
|
|
81
|
-
const callbackUrl = searchParams.get('callbackUrl') ?? '/';
|
|
82
|
-
|
|
83
|
-
const handleSignIn = async (provider: string) => {
|
|
84
|
-
setLoadingProvider(provider);
|
|
85
|
-
try {
|
|
86
|
-
await signIn(provider, { redirectTo: callbackUrl });
|
|
87
|
-
} catch (error) {
|
|
88
|
-
setLoadingProvider(null);
|
|
89
|
-
// Signin can fail for a number of reasons, such as the user
|
|
90
|
-
// not existing, or the user not having the correct role.
|
|
91
|
-
// In some cases, you may want to redirect to a custom error
|
|
92
|
-
if (error instanceof AuthError) {
|
|
93
|
-
return router.push(`/next-auth/?error=${error.type}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Otherwise if a redirects happens Next.js can handle it
|
|
97
|
-
// so you can just re-thrown the error and let Next.js handle it.
|
|
98
|
-
// Docs: https://nextjs.org/docs/app/api-reference/functions/redirect#server-component
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const footerBtns = [
|
|
104
|
-
{ href: DOCUMENTS_REFER_URL, id: 0, label: tCommon('document') },
|
|
105
|
-
{ href: PRIVACY_URL, id: 1, label: t('footer.privacy') },
|
|
106
|
-
{ href: TERMS_URL, id: 2, label: t('footer.terms') },
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div className={styles.container}>
|
|
111
|
-
<div className={styles.contentCard}>
|
|
112
|
-
{/* Card Body */}
|
|
113
|
-
<Flex gap="large" vertical>
|
|
114
|
-
{/* Header */}
|
|
115
|
-
<div className={styles.text}>
|
|
116
|
-
<Text as={'h4'} className={styles.title}>
|
|
117
|
-
<div>
|
|
118
|
-
<LobeHub size={48} />
|
|
119
|
-
</div>
|
|
120
|
-
{t('signin.title')}
|
|
121
|
-
</Text>
|
|
122
|
-
<Text as={'p'} className={styles.description}>
|
|
123
|
-
{t('signin.subtitle', { appName: BRANDING_NAME })}
|
|
124
|
-
</Text>
|
|
125
|
-
</div>
|
|
126
|
-
{/* Content */}
|
|
127
|
-
<Flex gap="small" vertical>
|
|
128
|
-
{oAuthSSOProviders ? (
|
|
129
|
-
oAuthSSOProviders.map((provider) => (
|
|
130
|
-
<Button
|
|
131
|
-
className={styles.button}
|
|
132
|
-
icon={AuthIcons(provider, 16)}
|
|
133
|
-
key={provider}
|
|
134
|
-
loading={loadingProvider === provider}
|
|
135
|
-
onClick={() => handleSignIn(provider)}
|
|
136
|
-
>
|
|
137
|
-
{provider}
|
|
138
|
-
</Button>
|
|
139
|
-
))
|
|
140
|
-
) : (
|
|
141
|
-
<BtnListLoading />
|
|
142
|
-
)}
|
|
143
|
-
</Flex>
|
|
144
|
-
</Flex>
|
|
145
|
-
</div>
|
|
146
|
-
<div className={styles.footer}>
|
|
147
|
-
{/* Footer */}
|
|
148
|
-
<Row>
|
|
149
|
-
<Col span={12}>
|
|
150
|
-
<Flex justify="left" style={{ height: '100%' }}>
|
|
151
|
-
<BrandWatermark />
|
|
152
|
-
</Flex>
|
|
153
|
-
</Col>
|
|
154
|
-
<Col offset={4} span={8}>
|
|
155
|
-
<Flex justify="right">
|
|
156
|
-
{footerBtns.map((btn) => (
|
|
157
|
-
<Button key={btn.id} onClick={() => router.push(btn.href)} size="small" type="text">
|
|
158
|
-
{btn.label}
|
|
159
|
-
</Button>
|
|
160
|
-
))}
|
|
161
|
-
</Flex>
|
|
162
|
-
</Col>
|
|
163
|
-
</Row>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
);
|
|
167
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Suspense } from 'react';
|
|
2
|
-
|
|
3
|
-
import Loading from '@/components/Loading/BrandTextLoading';
|
|
4
|
-
|
|
5
|
-
import AuthSignInBox from './AuthSignInBox';
|
|
6
|
-
|
|
7
|
-
export default () => (
|
|
8
|
-
<Suspense fallback={<Loading debugId="Auth > SignIn" />}>
|
|
9
|
-
<AuthSignInBox />
|
|
10
|
-
</Suspense>
|
|
11
|
-
);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { notFound } from '@/libs/next/navigation';
|
|
2
|
-
import { type PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
import { enableBetterAuth } from '@/envs/auth';
|
|
5
|
-
|
|
6
|
-
const Layout = ({ children }: PropsWithChildren) => {
|
|
7
|
-
if (!enableBetterAuth) return notFound();
|
|
8
|
-
|
|
9
|
-
return children;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default Layout;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { notFound } from '@/libs/next/navigation';
|
|
2
|
-
import { type PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
import { enableBetterAuth } from '@/envs/auth';
|
|
5
|
-
|
|
6
|
-
const Layout = ({ children }: PropsWithChildren) => {
|
|
7
|
-
if (!enableBetterAuth) return notFound();
|
|
8
|
-
|
|
9
|
-
return children;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default Layout;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { notFound } from '@/libs/next/navigation';
|
|
2
|
-
import { type PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
import { enableBetterAuth } from '@/envs/auth';
|
|
5
|
-
|
|
6
|
-
const Layout = ({ children }: PropsWithChildren) => {
|
|
7
|
-
if (!enableBetterAuth) return notFound();
|
|
8
|
-
|
|
9
|
-
return children;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default Layout;
|
package/src/envs/auth.test.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { getAuthConfig } from './auth';
|
|
4
|
-
|
|
5
|
-
const ORIGINAL_ENV = { ...process.env };
|
|
6
|
-
const ORIGINAL_WINDOW = globalThis.window;
|
|
7
|
-
|
|
8
|
-
describe('getAuthConfig fallbacks', () => {
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
// reset env to a clean clone before each test
|
|
11
|
-
process.env = { ...ORIGINAL_ENV };
|
|
12
|
-
globalThis.window = ORIGINAL_WINDOW;
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
process.env = { ...ORIGINAL_ENV };
|
|
17
|
-
globalThis.window = ORIGINAL_WINDOW;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should fall back to NEXT_AUTH_SSO_PROVIDERS when AUTH_SSO_PROVIDERS is empty string', () => {
|
|
21
|
-
process.env.AUTH_SSO_PROVIDERS = '';
|
|
22
|
-
process.env.NEXT_AUTH_SSO_PROVIDERS = 'logto,github';
|
|
23
|
-
|
|
24
|
-
// Simulate server runtime so @t3-oss/env treats this as server-side access
|
|
25
|
-
// (happy-dom sets window by default in Vitest)
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
27
|
-
// @ts-expect-error - allow overriding for test
|
|
28
|
-
globalThis.window = undefined;
|
|
29
|
-
|
|
30
|
-
const config = getAuthConfig();
|
|
31
|
-
|
|
32
|
-
expect(config.AUTH_SSO_PROVIDERS).toBe('logto,github');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should fall back to NEXT_AUTH_SECRET when AUTH_SECRET is empty string', () => {
|
|
36
|
-
process.env.AUTH_SECRET = '';
|
|
37
|
-
process.env.NEXT_AUTH_SECRET = 'nextauth-secret';
|
|
38
|
-
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
40
|
-
// @ts-expect-error - allow overriding for test
|
|
41
|
-
globalThis.window = undefined;
|
|
42
|
-
|
|
43
|
-
const config = getAuthConfig();
|
|
44
|
-
|
|
45
|
-
expect(config.AUTH_SECRET).toBe('nextauth-secret');
|
|
46
|
-
});
|
|
47
|
-
});
|