@lobehub/lobehub 2.0.0-next.355 → 2.0.0-next.356
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.desktop +0 -1
- package/.env.example +16 -20
- package/.env.example.development +1 -4
- package/.github/workflows/e2e.yml +10 -11
- package/CHANGELOG.md +33 -0
- package/Dockerfile +28 -4
- package/changelog/v1.json +9 -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/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/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/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,50 +0,0 @@
|
|
|
1
|
-
import { type OIDCConfig, type OIDCUserConfig } from '@auth/core/providers';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
interface CasdoorProfile extends Record<string, any> {
|
|
6
|
-
avatar: string;
|
|
7
|
-
displayName: string;
|
|
8
|
-
email: string;
|
|
9
|
-
emailVerified: boolean;
|
|
10
|
-
firstName: string;
|
|
11
|
-
id: string;
|
|
12
|
-
lastName: string;
|
|
13
|
-
name: string;
|
|
14
|
-
owner: string;
|
|
15
|
-
permanentAvatar: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function LobeCasdoorProvider(config: OIDCUserConfig<CasdoorProfile>): OIDCConfig<CasdoorProfile> {
|
|
19
|
-
return {
|
|
20
|
-
...CommonProviderConfig,
|
|
21
|
-
...config,
|
|
22
|
-
id: 'casdoor',
|
|
23
|
-
name: 'Casdoor',
|
|
24
|
-
profile(profile) {
|
|
25
|
-
return {
|
|
26
|
-
email: profile.email,
|
|
27
|
-
emailVerified: profile.emailVerified ? new Date() : null,
|
|
28
|
-
id: profile.id,
|
|
29
|
-
image: profile.avatar,
|
|
30
|
-
name: profile.displayName ?? profile.firstName ?? profile.lastName,
|
|
31
|
-
providerAccountId: profile.id,
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
type: 'oidc',
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const provider = {
|
|
39
|
-
id: 'casdoor',
|
|
40
|
-
provider: LobeCasdoorProvider({
|
|
41
|
-
authorization: {
|
|
42
|
-
params: { scope: 'openid profile email' },
|
|
43
|
-
},
|
|
44
|
-
clientId: process.env.AUTH_CASDOOR_ID,
|
|
45
|
-
clientSecret: process.env.AUTH_CASDOOR_SECRET,
|
|
46
|
-
issuer: process.env.AUTH_CASDOOR_ISSUER,
|
|
47
|
-
}),
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export default provider;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { OIDCConfig } from '@auth/core/providers';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
export type CloudflareZeroTrustProfile = {
|
|
6
|
-
email: string;
|
|
7
|
-
name: string;
|
|
8
|
-
sub: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const provider = {
|
|
12
|
-
id: 'cloudflare-zero-trust',
|
|
13
|
-
provider: {
|
|
14
|
-
...CommonProviderConfig,
|
|
15
|
-
authorization: { params: { scope: 'openid email profile' } },
|
|
16
|
-
checks: ['state', 'pkce'],
|
|
17
|
-
clientId: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ID,
|
|
18
|
-
clientSecret: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET,
|
|
19
|
-
id: 'cloudflare-zero-trust',
|
|
20
|
-
issuer: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER,
|
|
21
|
-
name: 'Cloudflare Zero Trust',
|
|
22
|
-
profile(profile) {
|
|
23
|
-
return {
|
|
24
|
-
email: profile.email,
|
|
25
|
-
id: profile.sub,
|
|
26
|
-
name: profile.name ?? profile.email,
|
|
27
|
-
providerAccountId: profile.sub,
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
type: 'oidc',
|
|
31
|
-
} satisfies OIDCConfig<CloudflareZeroTrustProfile>,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default provider;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { customFetch } from 'next-auth';
|
|
2
|
-
import type { OAuthConfig } from 'next-auth/providers';
|
|
3
|
-
|
|
4
|
-
interface FeishuProfile {
|
|
5
|
-
avatar_big: string;
|
|
6
|
-
avatar_middle: string;
|
|
7
|
-
avatar_thumb: string;
|
|
8
|
-
avatar_url: string;
|
|
9
|
-
en_name: string;
|
|
10
|
-
name: string;
|
|
11
|
-
open_id: string;
|
|
12
|
-
tenant_key: string;
|
|
13
|
-
union_id: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface FeishuProfileResponse {
|
|
17
|
-
data: FeishuProfile;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function Feishu(): OAuthConfig<FeishuProfileResponse> {
|
|
21
|
-
return {
|
|
22
|
-
authorization: {
|
|
23
|
-
params: {
|
|
24
|
-
scope: '',
|
|
25
|
-
},
|
|
26
|
-
url: 'https://accounts.feishu.cn/open-apis/authen/v1/authorize',
|
|
27
|
-
},
|
|
28
|
-
checks: ['state'],
|
|
29
|
-
client: {
|
|
30
|
-
token_endpoint_auth_method: 'client_secret_post',
|
|
31
|
-
},
|
|
32
|
-
clientId: process.env.AUTH_FEISHU_APP_ID,
|
|
33
|
-
clientSecret: process.env.AUTH_FEISHU_APP_SECRET,
|
|
34
|
-
[customFetch]: (url, options = {}) => {
|
|
35
|
-
if (
|
|
36
|
-
url === 'https://open.feishu.cn/open-apis/authen/v2/oauth/token' &&
|
|
37
|
-
options.method === 'POST'
|
|
38
|
-
) {
|
|
39
|
-
if (options?.headers) {
|
|
40
|
-
options.headers = {
|
|
41
|
-
...options.headers,
|
|
42
|
-
'content-type': 'application/json; charset=utf-8',
|
|
43
|
-
};
|
|
44
|
-
} else {
|
|
45
|
-
options.headers = {
|
|
46
|
-
'content-type': 'application/json; charset=utf-8',
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (options.body instanceof URLSearchParams) {
|
|
51
|
-
options.body = JSON.stringify(Object.fromEntries(options.body));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return fetch(url, options);
|
|
56
|
-
},
|
|
57
|
-
id: 'feishu',
|
|
58
|
-
name: 'Feishu',
|
|
59
|
-
profile(profileResponse) {
|
|
60
|
-
const profile = profileResponse.data;
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
id: profile.union_id,
|
|
64
|
-
image: profile.avatar_url,
|
|
65
|
-
name: profile.name,
|
|
66
|
-
providerAccountId: profile.union_id,
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
style: {
|
|
70
|
-
logo: 'https://p1-hera.feishucdn.com/tos-cn-i-jbbdkfciu3/268ec674a56a4510889f7f5ca14f1ba1~tplv-jbbdkfciu3-image:0:0.image',
|
|
71
|
-
},
|
|
72
|
-
token: 'https://open.feishu.cn/open-apis/authen/v2/oauth/token',
|
|
73
|
-
type: 'oauth',
|
|
74
|
-
userinfo: 'https://open.feishu.cn/open-apis/authen/v1/user_info',
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const provider = {
|
|
79
|
-
id: 'feishu',
|
|
80
|
-
provider: Feishu(),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export default provider;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { OIDCConfig } from '@auth/core/providers';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
export type GenericOIDCProfile = {
|
|
6
|
-
email: string;
|
|
7
|
-
id?: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
picture?: string;
|
|
10
|
-
sub: string;
|
|
11
|
-
username?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const provider = {
|
|
15
|
-
id: 'generic-oidc',
|
|
16
|
-
provider: {
|
|
17
|
-
...CommonProviderConfig,
|
|
18
|
-
authorization: { params: { scope: 'email openid profile' } },
|
|
19
|
-
checks: ['state', 'pkce'],
|
|
20
|
-
clientId: process.env.AUTH_GENERIC_OIDC_ID,
|
|
21
|
-
clientSecret: process.env.AUTH_GENERIC_OIDC_SECRET,
|
|
22
|
-
id: 'generic-oidc',
|
|
23
|
-
issuer: process.env.AUTH_GENERIC_OIDC_ISSUER,
|
|
24
|
-
name: 'Generic OIDC',
|
|
25
|
-
profile(profile) {
|
|
26
|
-
return {
|
|
27
|
-
email: profile.email,
|
|
28
|
-
id: profile.sub,
|
|
29
|
-
image: profile.picture,
|
|
30
|
-
name: profile.name ?? profile.username ?? profile.email,
|
|
31
|
-
providerAccountId: profile.sub,
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
type: 'oidc',
|
|
35
|
-
} satisfies OIDCConfig<GenericOIDCProfile>,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export default provider;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import GitHub from 'next-auth/providers/github';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
const provider = {
|
|
6
|
-
id: 'github',
|
|
7
|
-
provider: GitHub({
|
|
8
|
-
...CommonProviderConfig,
|
|
9
|
-
// Specify auth scope, at least include 'openid email'
|
|
10
|
-
authorization: { params: { scope: 'read:user user:email' } },
|
|
11
|
-
profile: (profile) => {
|
|
12
|
-
return {
|
|
13
|
-
email: profile.email,
|
|
14
|
-
id: profile.id.toString(),
|
|
15
|
-
image: profile.avatar_url,
|
|
16
|
-
name: profile.name,
|
|
17
|
-
providerAccountId: profile.id.toString(),
|
|
18
|
-
};
|
|
19
|
-
},
|
|
20
|
-
}),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default provider;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import Google from 'next-auth/providers/google';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
const provider = {
|
|
6
|
-
id: 'google',
|
|
7
|
-
provider: Google({
|
|
8
|
-
...CommonProviderConfig,
|
|
9
|
-
authorization: {
|
|
10
|
-
params: {
|
|
11
|
-
scope:
|
|
12
|
-
'openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email openid',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
}),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default provider;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Auth0 from './auth0';
|
|
2
|
-
import Authelia from './authelia';
|
|
3
|
-
import Authentik from './authentik';
|
|
4
|
-
import Casdoor from './casdoor';
|
|
5
|
-
import CloudflareZeroTrust from './cloudflare-zero-trust';
|
|
6
|
-
import Cognito from './cognito';
|
|
7
|
-
import Feishu from './feishu';
|
|
8
|
-
import GenericOIDC from './generic-oidc';
|
|
9
|
-
import Github from './github';
|
|
10
|
-
import Google from './google';
|
|
11
|
-
import Keycloak from './keycloak';
|
|
12
|
-
import Logto from './logto';
|
|
13
|
-
import MicrosoftEntraID from './microsoft-entra-id';
|
|
14
|
-
import Okta from './okta';
|
|
15
|
-
import WeChat from './wechat';
|
|
16
|
-
import Zitadel from './zitadel';
|
|
17
|
-
|
|
18
|
-
export const ssoProviders = [
|
|
19
|
-
Auth0,
|
|
20
|
-
Authentik,
|
|
21
|
-
GenericOIDC,
|
|
22
|
-
Github,
|
|
23
|
-
Zitadel,
|
|
24
|
-
Authelia,
|
|
25
|
-
Logto,
|
|
26
|
-
CloudflareZeroTrust,
|
|
27
|
-
Casdoor,
|
|
28
|
-
MicrosoftEntraID,
|
|
29
|
-
WeChat,
|
|
30
|
-
Keycloak,
|
|
31
|
-
Google,
|
|
32
|
-
Cognito,
|
|
33
|
-
Okta,
|
|
34
|
-
Feishu,
|
|
35
|
-
];
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import Keycloak from 'next-auth/providers/keycloak';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
const provider = {
|
|
6
|
-
id: 'keycloak',
|
|
7
|
-
provider: Keycloak({
|
|
8
|
-
...CommonProviderConfig,
|
|
9
|
-
// Specify auth scope, at least include 'openid email'
|
|
10
|
-
authorization: { params: { scope: 'openid email profile' } },
|
|
11
|
-
profile(profile) {
|
|
12
|
-
return {
|
|
13
|
-
email: profile.email,
|
|
14
|
-
id: profile.sub,
|
|
15
|
-
name: profile.name,
|
|
16
|
-
providerAccountId: profile.sub,
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
|
-
}),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default provider;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { type OIDCConfig, type OIDCUserConfig } from '@auth/core/providers';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
interface LogtoProfile extends Record<string, any> {
|
|
6
|
-
email: string;
|
|
7
|
-
id: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
picture: string;
|
|
10
|
-
sub: string;
|
|
11
|
-
username: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function LobeLogtoProvider(config: OIDCUserConfig<LogtoProfile>): OIDCConfig<LogtoProfile> {
|
|
15
|
-
return {
|
|
16
|
-
...CommonProviderConfig,
|
|
17
|
-
...config,
|
|
18
|
-
id: 'logto',
|
|
19
|
-
name: 'Logto',
|
|
20
|
-
profile(profile) {
|
|
21
|
-
// You can customize the user profile mapping here
|
|
22
|
-
return {
|
|
23
|
-
email: profile.email,
|
|
24
|
-
id: profile.sub,
|
|
25
|
-
image: profile.picture,
|
|
26
|
-
name: profile.name ?? profile.username ?? profile.email,
|
|
27
|
-
providerAccountId: profile.sub,
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
type: 'oidc',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const provider = {
|
|
35
|
-
id: 'logto',
|
|
36
|
-
provider: LobeLogtoProvider({
|
|
37
|
-
authorization: {
|
|
38
|
-
params: { scope: 'openid offline_access profile email' },
|
|
39
|
-
},
|
|
40
|
-
// You can get the issuer value from the Logto Application Details page,
|
|
41
|
-
// in the field "Issuer endpoint"
|
|
42
|
-
clientId: process.env.AUTH_LOGTO_ID,
|
|
43
|
-
clientSecret: process.env.AUTH_LOGTO_SECRET,
|
|
44
|
-
issuer: process.env.AUTH_LOGTO_ISSUER,
|
|
45
|
-
}),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export default provider;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { authEnv } from '@/envs/auth';
|
|
2
|
-
|
|
3
|
-
function getTenantId() {
|
|
4
|
-
return (
|
|
5
|
-
process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID ??
|
|
6
|
-
process.env.AUTH_AZURE_AD_TENANT_ID ??
|
|
7
|
-
authEnv.AZURE_AD_TENANT_ID
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function getClientLoginBaseUrl() {
|
|
12
|
-
return process.env.AUTH_MICROSOFT_ENTRA_ID_BASE_URL ?? 'https://login.microsoftonline.com';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function getIssuer() {
|
|
16
|
-
const issuer = process.env.MICROSOFT_ENTRA_ID_ISSUER;
|
|
17
|
-
if (issuer) {
|
|
18
|
-
return issuer;
|
|
19
|
-
}
|
|
20
|
-
const tenantId = getTenantId();
|
|
21
|
-
if (tenantId) {
|
|
22
|
-
// refs: https://github.com/nextauthjs/next-auth/discussions/9154#discussioncomment-10583104
|
|
23
|
-
return `${getClientLoginBaseUrl()}/${tenantId}/v2.0`;
|
|
24
|
-
} else {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export { getIssuer as getMicrosoftEntraIdIssuer, getTenantId as getMicrosoftEntraIdTenantId };
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import MicrosoftEntraID from 'next-auth/providers/microsoft-entra-id';
|
|
2
|
-
|
|
3
|
-
import { getMicrosoftEntraIdIssuer } from './microsoft-entra-id-helper';
|
|
4
|
-
import { CommonProviderConfig } from './sso.config';
|
|
5
|
-
|
|
6
|
-
const provider = {
|
|
7
|
-
id: 'microsoft-entra-id',
|
|
8
|
-
provider: MicrosoftEntraID({
|
|
9
|
-
...CommonProviderConfig,
|
|
10
|
-
// Specify auth scope, at least include 'openid email'
|
|
11
|
-
// all scopes in Azure AD ref: https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#openid-connect-scopes
|
|
12
|
-
authorization: { params: { scope: 'openid email profile' } },
|
|
13
|
-
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID ?? process.env.AUTH_AZURE_AD_ID,
|
|
14
|
-
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET ?? process.env.AUTH_AZURE_AD_SECRET,
|
|
15
|
-
issuer: getMicrosoftEntraIdIssuer(),
|
|
16
|
-
}),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export default provider;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import Okta from 'next-auth/providers/okta';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
const provider = {
|
|
6
|
-
id: 'okta',
|
|
7
|
-
provider: Okta({
|
|
8
|
-
...CommonProviderConfig,
|
|
9
|
-
authorization: { params: { scope: 'openid email profile' } },
|
|
10
|
-
profile(profile) {
|
|
11
|
-
return {
|
|
12
|
-
email: profile.email,
|
|
13
|
-
id: profile.sub,
|
|
14
|
-
image: profile.picture,
|
|
15
|
-
name: profile.name ?? profile.preferred_username,
|
|
16
|
-
providerAccountId: profile.sub,
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
|
-
}),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default provider;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type OAuth2Config } from '@auth/core/providers';
|
|
2
|
-
import type { Profile } from 'next-auth';
|
|
3
|
-
|
|
4
|
-
export const CommonProviderConfig = {
|
|
5
|
-
// Auth.js does not allow email account linking by default cause it's dangerous
|
|
6
|
-
// ref: https://authjs.dev/reference/core/providers#allowdangerousemailaccountlinking
|
|
7
|
-
allowDangerousEmailAccountLinking: true,
|
|
8
|
-
} satisfies Partial<OAuth2Config<Profile>>;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import WeChat, { type WeChatProfile } from '@auth/core/providers/wechat';
|
|
2
|
-
|
|
3
|
-
import { CommonProviderConfig } from './sso.config';
|
|
4
|
-
|
|
5
|
-
const provider = {
|
|
6
|
-
id: 'wechat',
|
|
7
|
-
provider: WeChat({
|
|
8
|
-
...CommonProviderConfig,
|
|
9
|
-
clientId: process.env.AUTH_WECHAT_ID,
|
|
10
|
-
clientSecret: process.env.AUTH_WECHAT_SECRET,
|
|
11
|
-
platformType: 'WebsiteApp',
|
|
12
|
-
profile: (profile: WeChatProfile) => {
|
|
13
|
-
return {
|
|
14
|
-
email: null,
|
|
15
|
-
id: profile.unionid,
|
|
16
|
-
image: profile.headimgurl,
|
|
17
|
-
name: profile.nickname,
|
|
18
|
-
providerAccountId: profile.unionid,
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
style: { bg: '#fff', logo: 'https://authjs.dev/img/providers/wechat.svg', text: '#000' },
|
|
22
|
-
token: {
|
|
23
|
-
async conform(response: Response) {
|
|
24
|
-
const data = await response.json();
|
|
25
|
-
console.log('wechat data:', data);
|
|
26
|
-
return new Response(JSON.stringify({ ...data, token_type: 'bearer' }), {
|
|
27
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
params: { appid: process.env.AUTH_WECHAT_ID, secret: process.env.AUTH_WECHAT_SECRET },
|
|
31
|
-
url: 'https://api.weixin.qq.com/sns/oauth2/access_token',
|
|
32
|
-
},
|
|
33
|
-
}),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default provider;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import Zitadel from 'next-auth/providers/zitadel';
|
|
2
|
-
|
|
3
|
-
const provider = {
|
|
4
|
-
id: 'zitadel',
|
|
5
|
-
provider: Zitadel({
|
|
6
|
-
// Available scopes in ZITADEL: https://zitadel.com/docs/apis/openidoauth/scopes
|
|
7
|
-
authorization: { params: { scope: 'openid email profile' } },
|
|
8
|
-
// TODO(NextAuth): map unique user id to `providerAccountId` field
|
|
9
|
-
// profile(profile) {
|
|
10
|
-
// return {
|
|
11
|
-
// email: profile.email,
|
|
12
|
-
// image: profile.picture,
|
|
13
|
-
// name: profile.name,
|
|
14
|
-
// providerAccountId: profile.user_id,
|
|
15
|
-
// id: profile.user_id,
|
|
16
|
-
// };
|
|
17
|
-
// },
|
|
18
|
-
}),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default provider;
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
// NOTICE: here due to the reason we are using [`happy-dom`](https://github.com/lobehub/lobe-chat/blob/13753145557a9dede98b1f5489f93ac570ef2956/vitest.config.mts#L45)
|
|
3
|
-
// for Vitest environment, and in fact that this is a known bug for happy-dom not including
|
|
4
|
-
// Authorization header in fetch requests.
|
|
5
|
-
//
|
|
6
|
-
// Read more here: https://github.com/capricorn86/happy-dom/issues/1042#issuecomment-3585851354
|
|
7
|
-
import { Buffer } from 'node:buffer';
|
|
8
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
-
|
|
10
|
-
import { UpstashConfig } from './types';
|
|
11
|
-
|
|
12
|
-
const buildUpstashConfig = (): UpstashConfig | null => {
|
|
13
|
-
const url = process.env.UPSTASH_REDIS_REST_URL;
|
|
14
|
-
const token = process.env.UPSTASH_REDIS_REST_TOKEN;
|
|
15
|
-
|
|
16
|
-
if (!url || !token) return null;
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
enabled: true,
|
|
20
|
-
prefix: process.env.REDIS_PREFIX ?? 'lobe-chat-test',
|
|
21
|
-
provider: 'upstash',
|
|
22
|
-
token,
|
|
23
|
-
url,
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const loadUpstashProvider = async () => (await import('./upstash')).UpstashRedisProvider;
|
|
28
|
-
|
|
29
|
-
const createMockedProvider = async () => {
|
|
30
|
-
const mocks = {
|
|
31
|
-
mockSet: vi.fn().mockResolvedValue('OK'),
|
|
32
|
-
mockGet: vi.fn().mockResolvedValue('mock-value'),
|
|
33
|
-
mockDel: vi.fn().mockResolvedValue(1),
|
|
34
|
-
mockSetex: vi.fn().mockResolvedValue('OK'),
|
|
35
|
-
mockMset: vi.fn().mockResolvedValue('OK'),
|
|
36
|
-
mockHset: vi.fn().mockResolvedValue(1),
|
|
37
|
-
mockHdel: vi.fn().mockResolvedValue(1),
|
|
38
|
-
mockHgetall: vi.fn().mockResolvedValue({ a: '1' }),
|
|
39
|
-
mockPing: vi.fn().mockResolvedValue('PONG'),
|
|
40
|
-
mockExists: vi.fn().mockResolvedValue(1),
|
|
41
|
-
mockExpire: vi.fn().mockResolvedValue(1),
|
|
42
|
-
mockTtl: vi.fn().mockResolvedValue(50),
|
|
43
|
-
mockIncr: vi.fn().mockResolvedValue(2),
|
|
44
|
-
mockDecr: vi.fn().mockResolvedValue(0),
|
|
45
|
-
mockMget: vi.fn().mockResolvedValue(['a', 'b']),
|
|
46
|
-
mockHget: vi.fn().mockResolvedValue('field'),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
vi.resetModules();
|
|
50
|
-
vi.doMock('@upstash/redis', () => {
|
|
51
|
-
class FakeRedis {
|
|
52
|
-
constructor(public config: any) {}
|
|
53
|
-
ping = mocks.mockPing;
|
|
54
|
-
set = mocks.mockSet;
|
|
55
|
-
get = mocks.mockGet;
|
|
56
|
-
del = mocks.mockDel;
|
|
57
|
-
setex = mocks.mockSetex;
|
|
58
|
-
exists = mocks.mockExists;
|
|
59
|
-
expire = mocks.mockExpire;
|
|
60
|
-
ttl = mocks.mockTtl;
|
|
61
|
-
incr = mocks.mockIncr;
|
|
62
|
-
decr = mocks.mockDecr;
|
|
63
|
-
mget = mocks.mockMget;
|
|
64
|
-
mset = mocks.mockMset;
|
|
65
|
-
hget = mocks.mockHget;
|
|
66
|
-
hset = mocks.mockHset;
|
|
67
|
-
hdel = mocks.mockHdel;
|
|
68
|
-
hgetall = mocks.mockHgetall;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { Redis: FakeRedis };
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const UpstashRedisProvider = await loadUpstashProvider();
|
|
75
|
-
const provider = new UpstashRedisProvider({
|
|
76
|
-
enabled: true,
|
|
77
|
-
prefix: 'mock',
|
|
78
|
-
provider: 'upstash',
|
|
79
|
-
token: 'token',
|
|
80
|
-
url: 'https://example.upstash.io',
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
await provider.initialize();
|
|
84
|
-
|
|
85
|
-
return { mocks, provider };
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const shouldSkipIntegration = (error: unknown) =>
|
|
89
|
-
error instanceof Error &&
|
|
90
|
-
['ENOTFOUND', 'ECONNREFUSED', 'EAI_AGAIN', 'Connection is closed'].some((msg) =>
|
|
91
|
-
error.message.includes(msg),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
afterEach(() => {
|
|
95
|
-
vi.clearAllMocks();
|
|
96
|
-
vi.resetModules();
|
|
97
|
-
vi.unmock('@upstash/redis');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('integrated', (test) => {
|
|
101
|
-
const config = buildUpstashConfig();
|
|
102
|
-
if (!config) {
|
|
103
|
-
test.skip('UPSTASH_REDIS_REST_URL/TOKEN not provided; skip integrated upstash tests');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
it('set -> get -> del roundtrip', async () => {
|
|
108
|
-
vi.unmock('@upstash/redis');
|
|
109
|
-
vi.resetModules();
|
|
110
|
-
|
|
111
|
-
const UpstashRedisProvider = await loadUpstashProvider();
|
|
112
|
-
const provider = new UpstashRedisProvider(config);
|
|
113
|
-
try {
|
|
114
|
-
await provider.initialize();
|
|
115
|
-
|
|
116
|
-
const key = `upstash:test:${Date.now()}`;
|
|
117
|
-
await provider.set(key, 'value', { ex: 60 });
|
|
118
|
-
expect(await provider.get(key)).toBe('value');
|
|
119
|
-
expect(await provider.del(key)).toBe(1);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (shouldSkipIntegration(error)) {
|
|
122
|
-
// Remote Upstash Redis unavailable in current environment; treat as skipped.
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
throw error;
|
|
127
|
-
} finally {
|
|
128
|
-
await provider.disconnect();
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('mocked', () => {
|
|
134
|
-
it('normalizes buffer keys to strings', async () => {
|
|
135
|
-
const { mocks, provider } = await createMockedProvider();
|
|
136
|
-
|
|
137
|
-
const bufKey = Buffer.from('buffer-key');
|
|
138
|
-
await provider.set(bufKey, 'value');
|
|
139
|
-
await provider.hset(bufKey, 'field', 'value');
|
|
140
|
-
await provider.del(bufKey);
|
|
141
|
-
|
|
142
|
-
expect(mocks.mockSet).toHaveBeenCalledWith('mock:buffer-key', 'value', undefined);
|
|
143
|
-
expect(mocks.mockHset).toHaveBeenCalledWith('mock:buffer-key', { field: 'value' });
|
|
144
|
-
expect(mocks.mockDel).toHaveBeenCalledWith('mock:buffer-key');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('passes set options through to upstash client', async () => {
|
|
148
|
-
const { mocks, provider } = await createMockedProvider();
|
|
149
|
-
|
|
150
|
-
await provider.set('key', 'value', { ex: 10, nx: true, get: true });
|
|
151
|
-
|
|
152
|
-
expect(mocks.mockSet).toHaveBeenCalledWith('mock:key', 'value', {
|
|
153
|
-
ex: 10,
|
|
154
|
-
nx: true,
|
|
155
|
-
get: true,
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
});
|