diff --git a/apps/builder/src/features/auth/components/SocialLoginButtons.tsx b/apps/builder/src/features/auth/components/SocialLoginButtons.tsx
index 756cba9d3..54e85812b 100644
--- a/apps/builder/src/features/auth/components/SocialLoginButtons.tsx
+++ b/apps/builder/src/features/auth/components/SocialLoginButtons.tsx
@@ -105,6 +105,15 @@ export const SocialLoginButtons = ({ providers }: Props) => {
Continue with {providers['azure-ad'].name}
)}
+ {providers?.['custom-oauth'] && (
+
+ )}
)
}
diff --git a/apps/builder/src/pages/api/auth/[...nextauth].ts b/apps/builder/src/pages/api/auth/[...nextauth].ts
index 9a3b811ba..ddc5b7432 100644
--- a/apps/builder/src/pages/api/auth/[...nextauth].ts
+++ b/apps/builder/src/pages/api/auth/[...nextauth].ts
@@ -10,7 +10,7 @@ import { Provider } from 'next-auth/providers'
import { NextApiRequest, NextApiResponse } from 'next'
import { CustomAdapter } from './adapter'
import { User } from 'db'
-import { env, isNotEmpty } from 'utils'
+import { env, getAtPath, isNotEmpty } from 'utils'
import { mockedUser } from '@/features/auth'
const providers: Provider[] = []
@@ -97,6 +97,34 @@ if (
)
}
+if (isNotEmpty(process.env.CUSTOM_OAUTH_AUTHORIZATION_URL)) {
+ providers.push({
+ id: 'custom-oauth',
+ name: process.env.CUSTOM_OAUTH_NAME ?? 'Custom OAuth',
+ type: 'oauth',
+ authorization: process.env.CUSTOM_OAUTH_AUTHORIZATION_URL,
+ token: process.env.CUSTOM_OAUTH_TOKEN_URL,
+ userinfo: process.env.CUSTOM_OAUTH_USERINFO_URL,
+ profile(profile) {
+ return {
+ id: getAtPath(profile, process.env.CUSTOM_OAUTH_USER_ID_PATH ?? 'id'),
+ name: getAtPath(
+ profile,
+ process.env.CUSTOM_OAUTH_USER_NAME_PATH ?? 'name'
+ ),
+ email: getAtPath(
+ profile,
+ process.env.CUSTOM_OAUTH_USER_EMAIL_PATH ?? 'email'
+ ),
+ image: getAtPath(
+ profile,
+ process.env.CUSTOM_OAUTH_USER_IMAGE_PATH ?? 'image'
+ ),
+ } as User
+ },
+ })
+}
+
const handler = (req: NextApiRequest, res: NextApiResponse) => {
if (
req.method === 'GET' &&
diff --git a/apps/docs/docs/self-hosting/configuration/builder.mdx b/apps/docs/docs/self-hosting/configuration/builder.mdx
index 38e0a7121..874a0142e 100644
--- a/apps/docs/docs/self-hosting/configuration/builder.mdx
+++ b/apps/docs/docs/self-hosting/configuration/builder.mdx
@@ -103,6 +103,21 @@ The Authorization callback URL should be `$NEXTAUTH_URL/api/auth/callback/azure-
| AZURE_AD_CLIENT_SECRET | -- | Application client secret. Can be obtained from Azure Portal. |
| AZURE_AD_TENANT_ID | -- | Azure Tenant ID |
+## Custom OAuth Provider (Auth)
+
+| Parameter | Default | Description |
+| ------------------------------ | ------------ | ----------------------------------------------------------------------- |
+| CUSTOM_OAUTH_NAME | Custom OAuth | Provider name. Is displayed in the sign in form. |
+| CUSTOM_OAUTH_AUTHORIZATION_URL | -- | OAuth autorization URL (i.e. `https://kauth.kakao.com/oauth/authorize`) |
+| CUSTOM_OAUTH_TOKEN_URL | -- | OAuth token URL (i.e. `https://kauth.kakao.com/oauth/token`) |
+| CUSTOM_OAUTH_USERINFO_URL | -- | User info URL (i.e. `https://kapi.kakao.com/v2/user/me`) |
+| CUSTOM_OAUTH_USER_ID_PATH | id | Used to map the id from the user info object |
+| CUSTOM_OAUTH_USER_NAME_PATH | name | Used to map the name from the user info object |
+| CUSTOM_OAUTH_USER_EMAIL_PATH | email | Used to map the email from the user info object |
+| CUSTOM_OAUTH_USER_IMAGE_PATH | image | Used to map the image from the user info object |
+
+For `*_PATH` parameters, you can use dot notation to access nested properties (i.e. `account.name`).
+
## S3 Storage (Media uploads)
Used for uploading images, videos, etc... It can be any S3 compatible object storage service (Minio, Digital Oceans Space, AWS S3...)
diff --git a/packages/utils/utils.ts b/packages/utils/utils.ts
index b6d07c7ff..df0b5e111 100644
--- a/packages/utils/utils.ts
+++ b/packages/utils/utils.ts
@@ -303,3 +303,17 @@ export const injectCustomHeadCode = (customHeadCode: string) => {
document.head.append(noScriptElement)
})
}
+
+export const getAtPath = (obj: T, path: string): unknown => {
+ if (isNotDefined(obj)) return undefined
+ const pathParts = path.split('.')
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let current: any = obj
+ for (const part of pathParts) {
+ if (current === undefined) {
+ return undefined
+ }
+ current = current[part]
+ }
+ return current
+}