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 +}