diff --git a/apps/builder/assets/logos/GitlabLogo.tsx b/apps/builder/assets/logos/GitlabLogo.tsx
new file mode 100644
index 000000000..97d0253c6
--- /dev/null
+++ b/apps/builder/assets/logos/GitlabLogo.tsx
@@ -0,0 +1,13 @@
+import { IconProps, Icon } from '@chakra-ui/react'
+
+export const GitlabLogo = (props: IconProps) => (
+
+
+
+
+
+
+
+
+
+)
diff --git a/apps/builder/assets/logos/index.tsx b/apps/builder/assets/logos/index.tsx
index e582658dd..454eacdcb 100644
--- a/apps/builder/assets/logos/index.tsx
+++ b/apps/builder/assets/logos/index.tsx
@@ -1,4 +1,5 @@
export * from './GiphyLogo'
+export * from './GitlabLogo'
export * from './GoogleAnalyticsLogo'
export * from './GoogleSheetsLogo'
export * from './GtmLogo'
diff --git a/apps/builder/components/auth/SocialLoginButtons.tsx b/apps/builder/components/auth/SocialLoginButtons.tsx
index 51f167cc9..0c9805cbf 100644
--- a/apps/builder/components/auth/SocialLoginButtons.tsx
+++ b/apps/builder/components/auth/SocialLoginButtons.tsx
@@ -4,7 +4,7 @@ import { signIn, useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import React from 'react'
import { stringify } from 'qs'
-import { FacebookLogo, GoogleLogo } from 'assets/logos'
+import { FacebookLogo, GoogleLogo, GitlabLogo } from 'assets/logos'
export const SocialLoginButtons = () => {
const { query } = useRouter()
@@ -25,6 +25,11 @@ export const SocialLoginButtons = () => {
callbackUrl: `/typebots?${stringify(query)}`,
})
+ const handleGitlabClick = async () =>
+ signIn('gitlab', {
+ callbackUrl: `/typebots?${stringify(query)}`,
+ })
+
return (
{process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID && (
@@ -60,6 +65,17 @@ export const SocialLoginButtons = () => {
Continue with Facebook
)}
+ {process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID && (
+ }
+ onClick={handleGitlabClick}
+ data-testid="gitlab"
+ isLoading={['loading', 'authenticated'].includes(status)}
+ variant="outline"
+ >
+ Continue with {process.env.NEXT_PUBLIC_GITLAB_NAME || 'GitLab'}
+
+ )}
)
}
diff --git a/apps/builder/pages/api/auth/[...nextauth].ts b/apps/builder/pages/api/auth/[...nextauth].ts
index a7f0583d0..81e6608ce 100644
--- a/apps/builder/pages/api/auth/[...nextauth].ts
+++ b/apps/builder/pages/api/auth/[...nextauth].ts
@@ -1,6 +1,7 @@
-import NextAuth from 'next-auth'
+import NextAuth, { Account } from 'next-auth'
import EmailProvider from 'next-auth/providers/email'
import GitHubProvider from 'next-auth/providers/github'
+import GitlabProvider from 'next-auth/providers/gitlab'
import GoogleProvider from 'next-auth/providers/google'
import FacebookProvider from 'next-auth/providers/facebook'
import prisma from 'libs/prisma'
@@ -60,6 +61,22 @@ if (
})
)
+if (
+ process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID &&
+ process.env.GITLAB_CLIENT_SECRET
+) {
+ const BASE_URL = process.env.GITLAB_BASE_URL || 'https://gitlab.com'
+ providers.push(
+ GitlabProvider({
+ clientId: process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID,
+ clientSecret: process.env.GITLAB_CLIENT_SECRET,
+ authorization: `${BASE_URL}/oauth/authorize?scope=read_api`,
+ token: `${BASE_URL}/oauth/token`,
+ userinfo: `${BASE_URL}/api/v4/user`,
+ })
+ )
+}
+
const handler = (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'HEAD') {
res.status(200)
@@ -81,6 +98,14 @@ const handler = (req: NextApiRequest, res: NextApiResponse) => {
user: userFromDb,
}
},
+ signIn: async ({ account }) => {
+ const requiredGroups = getRequiredGroups(account.provider)
+ if (requiredGroups.length > 0) {
+ const userGroups = await getUserGroups(account)
+ return checkHasGroups(userGroups, requiredGroups)
+ }
+ return true
+ },
},
})
}
@@ -98,4 +123,31 @@ const updateLastActivityDate = async (user: User) => {
})
}
+const getUserGroups = async (account: Account): Promise => {
+ switch (account.provider) {
+ case 'gitlab': {
+ const res = await fetch(
+ `${process.env.GITLAB_BASE_URL || 'https://gitlab.com'}/api/v4/groups`,
+ { headers: { Authorization: `Bearer ${account.access_token}` } }
+ )
+ const userGroups = await res.json()
+ return userGroups.map((group: { full_path: string }) => group.full_path)
+ }
+ default:
+ return []
+ }
+}
+
+const getRequiredGroups = (provider: string): string[] => {
+ switch (provider) {
+ case 'gitlab':
+ return process.env.GITLAB_REQUIRED_GROUPS?.split(',') || []
+ default:
+ return []
+ }
+}
+
+const checkHasGroups = (userGroups: string[], requiredGroups: string[]) =>
+ userGroups?.some((userGroup) => requiredGroups?.includes(userGroup))
+
export default withSentry(handler)
diff --git a/apps/docs/docs/self-hosting/configuration.md b/apps/docs/docs/self-hosting/configuration.md
index 12433e661..732745768 100644
--- a/apps/docs/docs/self-hosting/configuration.md
+++ b/apps/docs/docs/self-hosting/configuration.md
@@ -80,6 +80,23 @@ You can create your own GitHub OAuth app [here](https://github.com/settings/deve
+GitLab (Auth)
+
+
+Used for authenticating with GitLab.
+Follow the official GitLab guide for creating OAuth2 applications [here](https://docs.gitlab.com/ee/integration/oauth_provider.html).
+The Authorization callback URL should be `$NEXTAUTH_URL/api/auth/callback/gitlab`
+
+| Parameter | Default | Description |
+| ---------------------------- | ------------------ | ------------------------------------------------------------------------------------ | --- |
+| NEXT_PUBLIC_GITLAB_CLIENT_ID | -- | Application client ID. Also used to check if it is enabled in the front-end |
+| GITLAB_CLIENT_SECRET | -- | Application secret |
+| GITLAB_BASE_URL | https://gitlab.com | Base URL of the GitLab instance | |
+| GITLAB_REQUIRED_GROUPS | -- | Comma-separated list of groups the user has to be a direct member of, e.g. `foo,bar` |
+| NEXT_PUBLIC_GITLAB_NAME | GitLab | Name of the GitLab instance, used for the SSO Login Button |
+
+
+
Facebook (Auth)