@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.
Files changed (126) hide show
  1. package/.cursor/rules/db-migrations.mdc +16 -1
  2. package/.cursor/rules/project-introduce.mdc +1 -1
  3. package/.cursor/rules/project-structure.mdc +20 -2
  4. package/.env.example +148 -65
  5. package/.env.example.development +6 -8
  6. package/AGENTS.md +1 -3
  7. package/CHANGELOG.md +51 -0
  8. package/Dockerfile +6 -6
  9. package/GEMINI.md +63 -0
  10. package/README.md +8 -8
  11. package/README.zh-CN.md +8 -8
  12. package/changelog/v1.json +18 -0
  13. package/docs/development/database-schema.dbml +38 -0
  14. package/docs/self-hosting/advanced/auth.mdx +75 -2
  15. package/docs/self-hosting/advanced/auth.zh-CN.mdx +75 -2
  16. package/docs/self-hosting/environment-variables/auth.mdx +187 -1
  17. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +187 -1
  18. package/locales/en-US/auth.json +93 -0
  19. package/locales/zh-CN/auth.json +107 -1
  20. package/package.json +5 -2
  21. package/packages/const/src/auth.ts +2 -1
  22. package/packages/database/migrations/0048_add_editor_data.sql +1 -0
  23. package/packages/database/migrations/0049_better_auth.sql +49 -0
  24. package/packages/database/migrations/meta/0048_snapshot.json +7913 -0
  25. package/packages/database/migrations/meta/0049_snapshot.json +8151 -0
  26. package/packages/database/migrations/meta/_journal.json +14 -0
  27. package/packages/database/src/core/migrations.json +19 -0
  28. package/packages/database/src/index.ts +1 -0
  29. package/packages/database/src/models/__tests__/session.test.ts +1 -2
  30. package/packages/database/src/models/user.ts +9 -8
  31. package/packages/database/src/repositories/tableViewer/index.test.ts +2 -2
  32. package/packages/database/src/schemas/agent.ts +1 -0
  33. package/packages/database/src/schemas/betterAuth.ts +63 -0
  34. package/packages/database/src/schemas/index.ts +1 -0
  35. package/packages/database/src/schemas/ragEvals.ts +1 -2
  36. package/packages/database/src/schemas/user.ts +3 -2
  37. package/packages/database/src/server/models/__tests__/user.test.ts +1 -4
  38. package/packages/types/src/user/preference.ts +11 -0
  39. package/packages/utils/src/server/__tests__/auth.test.ts +52 -0
  40. package/packages/utils/src/server/auth.ts +18 -1
  41. package/src/app/(backend)/api/auth/[...all]/route.ts +19 -0
  42. package/src/app/(backend)/api/auth/check-user/route.ts +62 -0
  43. package/src/app/(backend)/middleware/auth/index.ts +14 -0
  44. package/src/app/(backend)/middleware/auth/utils.test.ts +16 -0
  45. package/src/app/(backend)/middleware/auth/utils.ts +13 -10
  46. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +1 -0
  47. package/src/app/[variants]/(auth)/reset-password/layout.tsx +12 -0
  48. package/src/app/[variants]/(auth)/reset-password/page.tsx +209 -0
  49. package/src/app/[variants]/(auth)/signin/layout.tsx +12 -0
  50. package/src/app/[variants]/(auth)/signin/page.tsx +448 -0
  51. package/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +192 -0
  52. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +31 -6
  53. package/src/app/[variants]/(auth)/verify-email/layout.tsx +12 -0
  54. package/src/app/[variants]/(auth)/verify-email/page.tsx +164 -0
  55. package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +12 -10
  56. package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +13 -11
  57. package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +15 -8
  58. package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/index.tsx +27 -30
  59. package/src/app/[variants]/(main)/profile/(home)/Client.tsx +306 -52
  60. package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx +89 -47
  61. package/src/auth.ts +118 -0
  62. package/src/components/NextAuth/AuthIcons.tsx +3 -1
  63. package/src/envs/auth.ts +260 -13
  64. package/src/envs/email.ts +37 -0
  65. package/src/features/AgentSetting/AgentPlugin/index.tsx +6 -2
  66. package/src/features/User/UserPanel/PanelContent.tsx +6 -5
  67. package/src/features/User/__tests__/PanelContent.test.tsx +15 -6
  68. package/src/features/User/__tests__/UserAvatar.test.tsx +17 -6
  69. package/src/features/User/__tests__/useMenu.test.tsx +14 -12
  70. package/src/layout/AuthProvider/BetterAuth/UserUpdater.tsx +51 -0
  71. package/src/layout/AuthProvider/BetterAuth/index.tsx +14 -0
  72. package/src/layout/AuthProvider/index.tsx +3 -0
  73. package/src/layout/GlobalProvider/StoreInitialization.tsx +3 -3
  74. package/src/libs/better-auth/auth-client.ts +34 -0
  75. package/src/libs/better-auth/constants.ts +13 -0
  76. package/src/libs/better-auth/email-templates/index.ts +3 -0
  77. package/src/libs/better-auth/email-templates/magic-link.ts +98 -0
  78. package/src/libs/better-auth/email-templates/reset-password.ts +91 -0
  79. package/src/libs/better-auth/email-templates/verification.ts +108 -0
  80. package/src/libs/better-auth/sso/helpers.ts +61 -0
  81. package/src/libs/better-auth/sso/index.ts +113 -0
  82. package/src/libs/better-auth/sso/providers/auth0.ts +33 -0
  83. package/src/libs/better-auth/sso/providers/authelia.ts +35 -0
  84. package/src/libs/better-auth/sso/providers/authentik.ts +35 -0
  85. package/src/libs/better-auth/sso/providers/casdoor.ts +48 -0
  86. package/src/libs/better-auth/sso/providers/cloudflare-zero-trust.ts +41 -0
  87. package/src/libs/better-auth/sso/providers/cognito.ts +45 -0
  88. package/src/libs/better-auth/sso/providers/feishu.ts +181 -0
  89. package/src/libs/better-auth/sso/providers/generic-oidc.ts +44 -0
  90. package/src/libs/better-auth/sso/providers/github.ts +30 -0
  91. package/src/libs/better-auth/sso/providers/google.ts +30 -0
  92. package/src/libs/better-auth/sso/providers/keycloak.ts +35 -0
  93. package/src/libs/better-auth/sso/providers/logto.ts +38 -0
  94. package/src/libs/better-auth/sso/providers/microsoft.ts +65 -0
  95. package/src/libs/better-auth/sso/providers/okta.ts +37 -0
  96. package/src/libs/better-auth/sso/providers/wechat.ts +140 -0
  97. package/src/libs/better-auth/sso/providers/zitadel.ts +54 -0
  98. package/src/libs/better-auth/sso/types.ts +25 -0
  99. package/src/libs/better-auth/utils/client.ts +1 -0
  100. package/src/libs/better-auth/utils/common.ts +20 -0
  101. package/src/libs/better-auth/utils/server.test.ts +61 -0
  102. package/src/libs/better-auth/utils/server.ts +18 -0
  103. package/src/libs/trpc/lambda/context.test.ts +116 -0
  104. package/src/libs/trpc/lambda/context.ts +27 -0
  105. package/src/libs/trpc/middleware/userAuth.ts +4 -2
  106. package/src/locales/default/auth.ts +114 -1
  107. package/src/proxy.ts +71 -7
  108. package/src/server/globalConfig/index.ts +12 -1
  109. package/src/server/routers/lambda/user.ts +4 -0
  110. package/src/server/services/email/README.md +241 -0
  111. package/src/server/services/email/impls/index.test.ts +39 -0
  112. package/src/server/services/email/impls/index.ts +32 -0
  113. package/src/server/services/email/impls/nodemailer/index.ts +108 -0
  114. package/src/server/services/email/impls/nodemailer/type.ts +31 -0
  115. package/src/server/services/email/impls/type.ts +61 -0
  116. package/src/server/services/email/index.test.ts +144 -0
  117. package/src/server/services/email/index.ts +40 -0
  118. package/src/services/user/index.test.ts +162 -2
  119. package/src/services/user/index.ts +6 -3
  120. package/src/store/aiInfra/slices/aiProvider/action.ts +4 -4
  121. package/src/store/user/slices/auth/action.test.ts +213 -16
  122. package/src/store/user/slices/auth/action.ts +86 -1
  123. package/src/store/user/slices/auth/initialState.ts +13 -2
  124. package/src/store/user/slices/auth/selectors.ts +6 -2
  125. package/src/store/user/slices/common/action.ts +5 -1
  126. package/src/app/(backend)/api/auth/[...nextauth]/route.ts +0 -3
@@ -0,0 +1,48 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_CASDOOR_ID: string;
8
+ AUTH_CASDOOR_ISSUER: string;
9
+ AUTH_CASDOOR_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_CASDOOR_ID,
14
+ clientSecret: env.AUTH_CASDOOR_SECRET,
15
+ issuer: env.AUTH_CASDOOR_ISSUER,
16
+ overrides: {
17
+ mapProfileToUser: (profile) => {
18
+ const composedName = [profile.firstName, profile.lastName]
19
+ .filter(Boolean)
20
+ .join(' ')
21
+ .trim();
22
+ const fallbackName = composedName.length > 0 ? composedName : undefined;
23
+
24
+ return {
25
+ email: profile.email,
26
+ emailVerified: Boolean(profile.emailVerified),
27
+ image: profile.avatar ?? profile.permanentAvatar,
28
+ name:
29
+ profile.displayName ?? fallbackName ?? profile.name ?? profile.email ?? profile.id,
30
+ };
31
+ },
32
+ },
33
+ providerId: 'casdoor',
34
+ }),
35
+ checkEnvs: () => {
36
+ return !!(authEnv.AUTH_CASDOOR_ID && authEnv.AUTH_CASDOOR_SECRET && authEnv.AUTH_CASDOOR_ISSUER)
37
+ ? {
38
+ AUTH_CASDOOR_ID: authEnv.AUTH_CASDOOR_ID,
39
+ AUTH_CASDOOR_ISSUER: authEnv.AUTH_CASDOOR_ISSUER,
40
+ AUTH_CASDOOR_SECRET: authEnv.AUTH_CASDOOR_SECRET,
41
+ }
42
+ : false;
43
+ },
44
+ id: 'casdoor',
45
+ type: 'generic',
46
+ };
47
+
48
+ export default provider;
@@ -0,0 +1,41 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_CLOUDFLARE_ZERO_TRUST_ID: string;
8
+ AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER: string;
9
+ AUTH_CLOUDFLARE_ZERO_TRUST_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_CLOUDFLARE_ZERO_TRUST_ID,
14
+ clientSecret: env.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET,
15
+ issuer: env.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER,
16
+ overrides: {
17
+ mapProfileToUser: (profile) => ({
18
+ email: profile.email,
19
+ name: profile.name ?? profile.email ?? profile.sub,
20
+ }),
21
+ },
22
+ providerId: 'cloudflare-zero-trust',
23
+ }),
24
+ checkEnvs: () => {
25
+ return !!(
26
+ authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_ID &&
27
+ authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET &&
28
+ authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER
29
+ )
30
+ ? {
31
+ AUTH_CLOUDFLARE_ZERO_TRUST_ID: authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_ID,
32
+ AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER: authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER,
33
+ AUTH_CLOUDFLARE_ZERO_TRUST_SECRET: authEnv.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET,
34
+ }
35
+ : false;
36
+ },
37
+ id: 'cloudflare-zero-trust',
38
+ type: 'generic',
39
+ };
40
+
41
+ export default provider;
@@ -0,0 +1,45 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import type { BuiltinProviderDefinition } from '../types';
4
+
5
+ const provider: BuiltinProviderDefinition<
6
+ {
7
+ AUTH_COGNITO_DOMAIN: string;
8
+ AUTH_COGNITO_ID: string;
9
+ AUTH_COGNITO_REGION: string;
10
+ AUTH_COGNITO_SECRET: string;
11
+ AUTH_COGNITO_USERPOOL_ID: string;
12
+ },
13
+ 'cognito'
14
+ > = {
15
+ build: (env) => {
16
+ return {
17
+ clientId: env.AUTH_COGNITO_ID,
18
+ clientSecret: env.AUTH_COGNITO_SECRET,
19
+ domain: env.AUTH_COGNITO_DOMAIN,
20
+ region: env.AUTH_COGNITO_REGION,
21
+ userPoolId: env.AUTH_COGNITO_USERPOOL_ID,
22
+ };
23
+ },
24
+ checkEnvs: () => {
25
+ return !!(
26
+ authEnv.AUTH_COGNITO_ID &&
27
+ authEnv.AUTH_COGNITO_SECRET &&
28
+ authEnv.AUTH_COGNITO_DOMAIN &&
29
+ authEnv.AUTH_COGNITO_REGION &&
30
+ authEnv.AUTH_COGNITO_USERPOOL_ID
31
+ )
32
+ ? {
33
+ AUTH_COGNITO_DOMAIN: authEnv.AUTH_COGNITO_DOMAIN,
34
+ AUTH_COGNITO_ID: authEnv.AUTH_COGNITO_ID,
35
+ AUTH_COGNITO_REGION: authEnv.AUTH_COGNITO_REGION,
36
+ AUTH_COGNITO_SECRET: authEnv.AUTH_COGNITO_SECRET,
37
+ AUTH_COGNITO_USERPOOL_ID: authEnv.AUTH_COGNITO_USERPOOL_ID,
38
+ }
39
+ : false;
40
+ },
41
+ id: 'cognito',
42
+ type: 'builtin',
43
+ };
44
+
45
+ export default provider;
@@ -0,0 +1,181 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import type { GenericProviderDefinition } from '../types';
4
+
5
+ const FEISHU_AUTHORIZATION_URL = 'https://accounts.feishu.cn/open-apis/authen/v1/authorize';
6
+ const FEISHU_TOKEN_URL = 'https://open.feishu.cn/open-apis/authen/v2/oauth/token';
7
+ const FEISHU_USERINFO_URL = 'https://open.feishu.cn/open-apis/authen/v1/user_info';
8
+
9
+ type FeishuUserProfile = {
10
+ avatar_big?: string;
11
+ avatar_middle?: string;
12
+ avatar_thumb?: string;
13
+ avatar_url?: string;
14
+ email?: string;
15
+ en_name?: string;
16
+ enterprise_email?: string;
17
+ name?: string;
18
+ open_id?: string;
19
+ tenant_key?: string;
20
+ union_id?: string;
21
+ };
22
+
23
+ type FeishuUserInfoResponse = {
24
+ code?: number;
25
+ data?: FeishuUserProfile;
26
+ msg?: string;
27
+ };
28
+
29
+ type FeishuTokenPayload = {
30
+ access_token?: string;
31
+ expires_in?: number;
32
+ refresh_token?: string;
33
+ scope?: string;
34
+ tokenType?: string;
35
+ token_type?: string;
36
+ };
37
+
38
+ type FeishuTokenResponse = {
39
+ code?: number;
40
+ data?: FeishuTokenPayload;
41
+ message?: string;
42
+ msg?: string;
43
+ } & FeishuTokenPayload;
44
+
45
+ const isFeishuProfile = (value: unknown): value is FeishuUserProfile => {
46
+ if (!value || typeof value !== 'object') return false;
47
+ const candidate = value as Record<string, unknown>;
48
+ return (
49
+ typeof candidate.union_id === 'string' ||
50
+ typeof candidate.open_id === 'string' ||
51
+ typeof candidate.avatar_url === 'string' ||
52
+ typeof candidate.name === 'string'
53
+ );
54
+ };
55
+
56
+ const parseScopes = (scope: string | undefined) =>
57
+ scope ? scope.split(/[\s,]+/).filter(Boolean) : [];
58
+
59
+ const provider: GenericProviderDefinition<{
60
+ AUTH_FEISHU_APP_ID: string;
61
+ AUTH_FEISHU_APP_SECRET: string;
62
+ }> = {
63
+ build: (env) => {
64
+ const clientId = env.AUTH_FEISHU_APP_ID;
65
+ const clientSecret = env.AUTH_FEISHU_APP_SECRET;
66
+
67
+ return {
68
+ authorizationUrl: FEISHU_AUTHORIZATION_URL,
69
+ authorizationUrlParams: {
70
+ app_id: clientId,
71
+ response_type: 'code',
72
+ scope: '',
73
+ },
74
+ clientId,
75
+ clientSecret,
76
+ /**
77
+ * Exchange code directly with Feishu (no proxy needed).
78
+ */
79
+ getToken: async ({ code, redirectURI }) => {
80
+ const tokenResponse = await fetch(FEISHU_TOKEN_URL, {
81
+ body: JSON.stringify({
82
+ app_id: clientId,
83
+ app_secret: clientSecret,
84
+ code,
85
+ grant_type: 'authorization_code',
86
+ redirect_uri: redirectURI,
87
+ }),
88
+ cache: 'no-store',
89
+ headers: {
90
+ 'content-type': 'application/json; charset=utf-8',
91
+ },
92
+ method: 'POST',
93
+ });
94
+
95
+ const parsed = (await tokenResponse.json()) as FeishuTokenResponse;
96
+ const payload = parsed.data ?? parsed;
97
+
98
+ const hasErrorCode = typeof parsed.code === 'number' && parsed.code !== 0;
99
+ const tokenMissing = !payload.access_token;
100
+
101
+ if (!tokenResponse.ok || hasErrorCode || tokenMissing) {
102
+ throw new Error(parsed.msg ?? parsed.message ?? 'Failed to fetch Feishu OAuth token');
103
+ }
104
+
105
+ return {
106
+ accessToken: payload.access_token,
107
+ accessTokenExpiresAt: payload.expires_in
108
+ ? new Date(Date.now() + payload.expires_in * 1000)
109
+ : undefined,
110
+ expiresIn: payload.expires_in,
111
+ raw: parsed,
112
+ refreshToken: payload.refresh_token,
113
+ scopes: parseScopes(payload.scope),
114
+ tokenType: payload.token_type ?? payload.tokenType ?? 'Bearer',
115
+ };
116
+ },
117
+ getUserInfo: async (tokens) => {
118
+ if (!tokens.accessToken) return null;
119
+
120
+ const response = await fetch(FEISHU_USERINFO_URL, {
121
+ cache: 'no-store',
122
+ headers: {
123
+ Authorization: `Bearer ${tokens.accessToken}`,
124
+ },
125
+ });
126
+
127
+ if (!response.ok) {
128
+ return null;
129
+ }
130
+
131
+ const payload = (await response.json()) as unknown;
132
+ const profileResponse = payload as FeishuUserInfoResponse;
133
+
134
+ if (profileResponse.code && profileResponse.code !== 0) {
135
+ return null;
136
+ }
137
+
138
+ const profile: FeishuUserProfile | undefined =
139
+ profileResponse.data ?? (isFeishuProfile(payload) ? payload : undefined);
140
+
141
+ if (!profile) return null;
142
+
143
+ const unionId = profile.union_id ?? profile.open_id;
144
+ if (!unionId) return null;
145
+
146
+ const syntheticEmail =
147
+ profile.email ?? profile.enterprise_email ?? `${unionId}@feishu.lobehub`;
148
+
149
+ return {
150
+ email: syntheticEmail,
151
+ emailVerified: false,
152
+ id: unionId,
153
+ image:
154
+ profile.avatar_url ??
155
+ profile.avatar_thumb ??
156
+ profile.avatar_middle ??
157
+ profile.avatar_big,
158
+ name: profile.name ?? profile.en_name ?? unionId,
159
+ ...profile,
160
+ };
161
+ },
162
+ pkce: false,
163
+ providerId: 'feishu',
164
+ responseMode: 'query',
165
+ scopes: [],
166
+ };
167
+ },
168
+
169
+ checkEnvs: () => {
170
+ return !!(authEnv.AUTH_FEISHU_APP_ID && authEnv.AUTH_FEISHU_APP_SECRET)
171
+ ? {
172
+ AUTH_FEISHU_APP_ID: authEnv.AUTH_FEISHU_APP_ID,
173
+ AUTH_FEISHU_APP_SECRET: authEnv.AUTH_FEISHU_APP_SECRET,
174
+ }
175
+ : false;
176
+ },
177
+ id: 'feishu',
178
+ type: 'generic',
179
+ };
180
+
181
+ export default provider;
@@ -0,0 +1,44 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_GENERIC_OIDC_ID: string;
8
+ AUTH_GENERIC_OIDC_ISSUER: string;
9
+ AUTH_GENERIC_OIDC_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_GENERIC_OIDC_ID,
14
+ clientSecret: env.AUTH_GENERIC_OIDC_SECRET,
15
+ issuer: env.AUTH_GENERIC_OIDC_ISSUER,
16
+ overrides: {
17
+ /**
18
+ * Mirror NextAuth's fallback that prefers name -> username -> email so Better Auth never
19
+ * fails with name_is_missing when upstream profiles only expose username/email fields.
20
+ */
21
+ mapProfileToUser: (profile) => ({
22
+ name: profile.name ?? profile.username ?? profile.email ?? profile.id,
23
+ }),
24
+ },
25
+ providerId: 'generic-oidc',
26
+ }),
27
+ checkEnvs: () => {
28
+ return !!(
29
+ authEnv.AUTH_GENERIC_OIDC_ID &&
30
+ authEnv.AUTH_GENERIC_OIDC_SECRET &&
31
+ authEnv.AUTH_GENERIC_OIDC_ISSUER
32
+ )
33
+ ? {
34
+ AUTH_GENERIC_OIDC_ID: authEnv.AUTH_GENERIC_OIDC_ID,
35
+ AUTH_GENERIC_OIDC_ISSUER: authEnv.AUTH_GENERIC_OIDC_ISSUER,
36
+ AUTH_GENERIC_OIDC_SECRET: authEnv.AUTH_GENERIC_OIDC_SECRET,
37
+ }
38
+ : false;
39
+ },
40
+ id: 'generic-oidc',
41
+ type: 'generic',
42
+ };
43
+
44
+ export default provider;
@@ -0,0 +1,30 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import type { BuiltinProviderDefinition } from '../types';
4
+
5
+ const provider: BuiltinProviderDefinition<
6
+ {
7
+ AUTH_GITHUB_ID: string;
8
+ AUTH_GITHUB_SECRET: string;
9
+ },
10
+ 'github'
11
+ > = {
12
+ build: (env) => {
13
+ return {
14
+ clientId: env.AUTH_GITHUB_ID,
15
+ clientSecret: env.AUTH_GITHUB_SECRET,
16
+ };
17
+ },
18
+ checkEnvs: () => {
19
+ return !!(authEnv.AUTH_GITHUB_ID && authEnv.AUTH_GITHUB_SECRET)
20
+ ? {
21
+ AUTH_GITHUB_ID: authEnv.AUTH_GITHUB_ID,
22
+ AUTH_GITHUB_SECRET: authEnv.AUTH_GITHUB_SECRET,
23
+ }
24
+ : false;
25
+ },
26
+ id: 'github',
27
+ type: 'builtin',
28
+ };
29
+
30
+ export default provider;
@@ -0,0 +1,30 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import type { BuiltinProviderDefinition } from '../types';
4
+
5
+ const provider: BuiltinProviderDefinition<
6
+ {
7
+ AUTH_GOOGLE_ID: string;
8
+ AUTH_GOOGLE_SECRET: string;
9
+ },
10
+ 'google'
11
+ > = {
12
+ build: (env) => {
13
+ return {
14
+ clientId: env.AUTH_GOOGLE_ID,
15
+ clientSecret: env.AUTH_GOOGLE_SECRET,
16
+ };
17
+ },
18
+ checkEnvs: () => {
19
+ return !!(authEnv.AUTH_GOOGLE_ID && authEnv.AUTH_GOOGLE_SECRET)
20
+ ? {
21
+ AUTH_GOOGLE_ID: authEnv.AUTH_GOOGLE_ID,
22
+ AUTH_GOOGLE_SECRET: authEnv.AUTH_GOOGLE_SECRET,
23
+ }
24
+ : false;
25
+ },
26
+ id: 'google',
27
+ type: 'builtin',
28
+ };
29
+
30
+ export default provider;
@@ -0,0 +1,35 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_KEYCLOAK_ID: string;
8
+ AUTH_KEYCLOAK_ISSUER: string;
9
+ AUTH_KEYCLOAK_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_KEYCLOAK_ID,
14
+ clientSecret: env.AUTH_KEYCLOAK_SECRET,
15
+ issuer: env.AUTH_KEYCLOAK_ISSUER,
16
+ providerId: 'keycloak',
17
+ }),
18
+ checkEnvs: () => {
19
+ return !!(
20
+ authEnv.AUTH_KEYCLOAK_ID &&
21
+ authEnv.AUTH_KEYCLOAK_SECRET &&
22
+ authEnv.AUTH_KEYCLOAK_ISSUER
23
+ )
24
+ ? {
25
+ AUTH_KEYCLOAK_ID: authEnv.AUTH_KEYCLOAK_ID,
26
+ AUTH_KEYCLOAK_ISSUER: authEnv.AUTH_KEYCLOAK_ISSUER,
27
+ AUTH_KEYCLOAK_SECRET: authEnv.AUTH_KEYCLOAK_SECRET,
28
+ }
29
+ : false;
30
+ },
31
+ id: 'keycloak',
32
+ type: 'generic',
33
+ };
34
+
35
+ export default provider;
@@ -0,0 +1,38 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_LOGTO_ID: string;
8
+ AUTH_LOGTO_ISSUER: string;
9
+ AUTH_LOGTO_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_LOGTO_ID,
14
+ clientSecret: env.AUTH_LOGTO_SECRET,
15
+ issuer: env.AUTH_LOGTO_ISSUER,
16
+ overrides: {
17
+ mapProfileToUser: (profile) => ({
18
+ email: profile.email,
19
+ name: profile.name ?? profile.username ?? profile.email ?? profile.sub,
20
+ }),
21
+ },
22
+ providerId: 'logto',
23
+ scopes: ['openid', 'profile', 'email', 'offline_access'],
24
+ }),
25
+ checkEnvs: () => {
26
+ return !!(authEnv.AUTH_LOGTO_ID && authEnv.AUTH_LOGTO_SECRET && authEnv.AUTH_LOGTO_ISSUER)
27
+ ? {
28
+ AUTH_LOGTO_ID: authEnv.AUTH_LOGTO_ID,
29
+ AUTH_LOGTO_ISSUER: authEnv.AUTH_LOGTO_ISSUER,
30
+ AUTH_LOGTO_SECRET: authEnv.AUTH_LOGTO_SECRET,
31
+ }
32
+ : false;
33
+ },
34
+ id: 'logto',
35
+ type: 'generic',
36
+ };
37
+
38
+ export default provider;
@@ -0,0 +1,65 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { pickEnv } from '../helpers';
4
+ import type { BuiltinProviderDefinition } from '../types';
5
+
6
+ type MicrosoftEnv = {
7
+ AUTH_AZURE_AD_ID?: string;
8
+ AUTH_AZURE_AD_SECRET?: string;
9
+ AUTH_MICROSOFT_ENTRA_ID_ID?: string;
10
+ AUTH_MICROSOFT_ENTRA_ID_SECRET?: string;
11
+ AUTH_MICROSOFT_ID?: string;
12
+ AUTH_MICROSOFT_SECRET?: string;
13
+ AZURE_AD_CLIENT_ID?: string;
14
+ AZURE_AD_CLIENT_SECRET?: string;
15
+ };
16
+
17
+ const getClientId = (env: MicrosoftEnv) => {
18
+ return pickEnv(
19
+ env.AUTH_MICROSOFT_ID,
20
+ env.AUTH_MICROSOFT_ENTRA_ID_ID,
21
+ env.AUTH_AZURE_AD_ID,
22
+ env.AZURE_AD_CLIENT_ID,
23
+ );
24
+ };
25
+
26
+ const getClientSecret = (env: MicrosoftEnv) => {
27
+ return pickEnv(
28
+ env.AUTH_MICROSOFT_SECRET,
29
+ env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
30
+ env.AUTH_AZURE_AD_SECRET,
31
+ env.AZURE_AD_CLIENT_SECRET,
32
+ );
33
+ };
34
+
35
+ const provider: BuiltinProviderDefinition<MicrosoftEnv, 'microsoft'> = {
36
+ aliases: ['microsoft-entra-id'],
37
+ build: (env) => {
38
+ const clientId = getClientId(env)!;
39
+ const clientSecret = getClientSecret(env)!;
40
+ return {
41
+ clientId,
42
+ clientSecret,
43
+ };
44
+ },
45
+ checkEnvs: () => {
46
+ const clientId = getClientId(authEnv);
47
+ const clientSecret = getClientSecret(authEnv);
48
+ return !!(clientId && clientSecret)
49
+ ? {
50
+ AUTH_AZURE_AD_ID: authEnv.AUTH_AZURE_AD_ID,
51
+ AUTH_AZURE_AD_SECRET: authEnv.AUTH_AZURE_AD_SECRET,
52
+ AUTH_MICROSOFT_ENTRA_ID_ID: authEnv.AUTH_MICROSOFT_ENTRA_ID_ID,
53
+ AUTH_MICROSOFT_ENTRA_ID_SECRET: authEnv.AUTH_MICROSOFT_ENTRA_ID_SECRET,
54
+ AUTH_MICROSOFT_ID: authEnv.AUTH_MICROSOFT_ID,
55
+ AUTH_MICROSOFT_SECRET: authEnv.AUTH_MICROSOFT_SECRET,
56
+ AZURE_AD_CLIENT_ID: authEnv.AZURE_AD_CLIENT_ID,
57
+ AZURE_AD_CLIENT_SECRET: authEnv.AZURE_AD_CLIENT_SECRET,
58
+ }
59
+ : false;
60
+ },
61
+ id: 'microsoft',
62
+ type: 'builtin',
63
+ };
64
+
65
+ export default provider;
@@ -0,0 +1,37 @@
1
+ import { authEnv } from '@/envs/auth';
2
+
3
+ import { buildOidcConfig } from '../helpers';
4
+ import type { GenericProviderDefinition } from '../types';
5
+
6
+ const provider: GenericProviderDefinition<{
7
+ AUTH_OKTA_ID: string;
8
+ AUTH_OKTA_ISSUER: string;
9
+ AUTH_OKTA_SECRET: string;
10
+ }> = {
11
+ build: (env) =>
12
+ buildOidcConfig({
13
+ clientId: env.AUTH_OKTA_ID,
14
+ clientSecret: env.AUTH_OKTA_SECRET,
15
+ issuer: env.AUTH_OKTA_ISSUER,
16
+ overrides: {
17
+ mapProfileToUser: (profile) => ({
18
+ email: profile.email,
19
+ name: profile.name ?? profile.preferred_username ?? profile.email ?? profile.sub,
20
+ }),
21
+ },
22
+ providerId: 'okta',
23
+ }),
24
+ checkEnvs: () => {
25
+ return !!(authEnv.AUTH_OKTA_ID && authEnv.AUTH_OKTA_SECRET && authEnv.AUTH_OKTA_ISSUER)
26
+ ? {
27
+ AUTH_OKTA_ID: authEnv.AUTH_OKTA_ID,
28
+ AUTH_OKTA_ISSUER: authEnv.AUTH_OKTA_ISSUER,
29
+ AUTH_OKTA_SECRET: authEnv.AUTH_OKTA_SECRET,
30
+ }
31
+ : false;
32
+ },
33
+ id: 'okta',
34
+ type: 'generic',
35
+ };
36
+
37
+ export default provider;