2
0
Files
bot/apps/builder/src/pages/api/auth/[...nextauth].ts

264 lines
7.9 KiB
TypeScript
Raw Normal View History

import NextAuth, { Account, AuthOptions } from 'next-auth'
2021-11-29 15:19:07 +01:00
import EmailProvider from 'next-auth/providers/email'
import GitHubProvider from 'next-auth/providers/github'
2022-04-26 09:50:02 +02:00
import GitlabProvider from 'next-auth/providers/gitlab'
2021-11-29 15:19:07 +01:00
import GoogleProvider from 'next-auth/providers/google'
import FacebookProvider from 'next-auth/providers/facebook'
import AzureADProvider from 'next-auth/providers/azure-ad'
import prisma from '@typebot.io/lib/prisma'
2021-12-06 15:48:50 +01:00
import { Provider } from 'next-auth/providers'
2021-12-27 15:59:32 +01:00
import { NextApiRequest, NextApiResponse } from 'next'
import { customAdapter } from '../../../features/auth/api/customAdapter'
import { User } from '@typebot.io/prisma'
import { getAtPath, isDefined } from '@typebot.io/lib'
import { mockedUser } from '@typebot.io/lib/mockedUser'
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis/nodejs'
import got from 'got'
import { env } from '@typebot.io/env'
import * as Sentry from '@sentry/nextjs'
2021-11-29 15:19:07 +01:00
const providers: Provider[] = []
let rateLimit: Ratelimit | undefined
if (env.UPSTASH_REDIS_REST_URL && env.UPSTASH_REDIS_REST_TOKEN) {
rateLimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(1, '60 s'),
})
}
if (env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET)
2022-04-12 14:58:55 -05:00
providers.push(
GitHubProvider({
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
2022-04-12 14:58:55 -05:00
})
)
2021-12-06 15:48:50 +01:00
2023-08-28 11:00:25 +02:00
if (env.NEXT_PUBLIC_SMTP_FROM && !env.SMTP_AUTH_DISABLED)
2023-06-19 18:41:00 +02:00
providers.push(
EmailProvider({
server: {
host: env.SMTP_HOST,
2023-08-28 11:00:25 +02:00
port: env.SMTP_PORT,
secure: env.SMTP_SECURE,
2023-06-19 18:41:00 +02:00
auth: {
user: env.SMTP_USERNAME,
pass: env.SMTP_PASSWORD,
2023-06-19 18:41:00 +02:00
},
},
from: env.NEXT_PUBLIC_SMTP_FROM,
2023-06-19 18:41:00 +02:00
sendVerificationRequest,
})
)
2021-12-06 15:48:50 +01:00
if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET)
2021-12-06 15:48:50 +01:00
providers.push(
2021-11-29 15:19:07 +01:00
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
2021-12-06 15:48:50 +01:00
})
)
if (env.FACEBOOK_CLIENT_ID && env.FACEBOOK_CLIENT_SECRET)
2021-12-06 15:48:50 +01:00
providers.push(
2021-11-29 15:19:07 +01:00
FacebookProvider({
clientId: env.FACEBOOK_CLIENT_ID,
clientSecret: env.FACEBOOK_CLIENT_SECRET,
2021-12-06 15:48:50 +01:00
})
)
if (env.GITLAB_CLIENT_ID && env.GITLAB_CLIENT_SECRET) {
2023-08-28 11:00:25 +02:00
const BASE_URL = env.GITLAB_BASE_URL
2022-04-30 07:10:02 -07:00
providers.push(
GitlabProvider({
clientId: env.GITLAB_CLIENT_ID,
clientSecret: env.GITLAB_CLIENT_SECRET,
2022-04-30 07:10:02 -07:00
authorization: `${BASE_URL}/oauth/authorize?scope=read_api`,
token: `${BASE_URL}/oauth/token`,
userinfo: `${BASE_URL}/api/v4/user`,
2023-08-28 11:00:25 +02:00
name: env.GITLAB_NAME,
2022-04-30 07:10:02 -07:00
})
)
2022-04-26 09:50:02 +02:00
}
2022-06-06 15:34:47 +02:00
if (
env.AZURE_AD_CLIENT_ID &&
env.AZURE_AD_CLIENT_SECRET &&
env.AZURE_AD_TENANT_ID
2022-06-06 15:34:47 +02:00
) {
providers.push(
AzureADProvider({
clientId: env.AZURE_AD_CLIENT_ID,
clientSecret: env.AZURE_AD_CLIENT_SECRET,
tenantId: env.AZURE_AD_TENANT_ID,
})
2022-06-06 15:34:47 +02:00
)
}
if (env.CUSTOM_OAUTH_WELL_KNOWN_URL) {
providers.push({
id: 'custom-oauth',
2023-08-28 11:00:25 +02:00
name: env.CUSTOM_OAUTH_NAME,
type: 'oauth',
authorization: {
params: {
2023-08-28 11:00:25 +02:00
scope: env.CUSTOM_OAUTH_SCOPE,
},
},
clientId: env.CUSTOM_OAUTH_CLIENT_ID,
clientSecret: env.CUSTOM_OAUTH_CLIENT_SECRET,
wellKnown: env.CUSTOM_OAUTH_WELL_KNOWN_URL,
profile(profile) {
return {
2023-08-28 11:00:25 +02:00
id: getAtPath(profile, env.CUSTOM_OAUTH_USER_ID_PATH),
name: getAtPath(profile, env.CUSTOM_OAUTH_USER_NAME_PATH),
email: getAtPath(profile, env.CUSTOM_OAUTH_USER_EMAIL_PATH),
image: getAtPath(profile, env.CUSTOM_OAUTH_USER_IMAGE_PATH),
} as User
},
})
}
export const authOptions: AuthOptions = {
adapter: customAdapter(prisma),
secret: env.ENCRYPTION_SECRET,
providers,
session: {
strategy: 'database',
},
pages: {
signIn: '/signin',
newUser: env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID ? '/onboarding' : undefined,
},
events: {
signIn({ user }) {
Sentry.setUser({ id: user.id })
},
signOut() {
Sentry.setUser(null)
},
},
callbacks: {
session: async ({ session, user }) => {
const userFromDb = user as User
await updateLastActivityDate(userFromDb)
return {
...session,
user: userFromDb,
}
},
signIn: async ({ account, user }) => {
if (!account) return false
const isNewUser = !('createdAt' in user && isDefined(user.createdAt))
if (isNewUser && user.email) {
const { body } = await got.get(
'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf'
)
const disposableEmailDomains = body.split('\n')
if (disposableEmailDomains.includes(user.email.split('@')[1]))
return false
}
if (env.DISABLE_SIGNUP && isNewUser && user.email) {
const { invitations, workspaceInvitations } =
await getNewUserInvitations(prisma, user.email)
if (invitations.length === 0 && workspaceInvitations.length === 0)
return false
}
const requiredGroups = getRequiredGroups(account.provider)
if (requiredGroups.length > 0) {
const userGroups = await getUserGroups(account)
return checkHasGroups(userGroups, requiredGroups)
}
return true
2021-12-06 15:48:50 +01:00
},
},
}
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const isMockingSession =
req.method === 'GET' &&
req.url === '/api/auth/session' &&
env.NEXT_PUBLIC_E2E_TEST
if (isMockingSession) return res.send({ user: mockedUser })
const requestIsFromCompanyFirewall = req.method === 'HEAD'
if (requestIsFromCompanyFirewall) return res.status(200).end()
if (
rateLimit &&
req.url === '/api/auth/signin/email' &&
req.method === 'POST'
) {
let ip = req.headers['x-real-ip'] as string | undefined
if (!ip) {
const forwardedFor = req.headers['x-forwarded-for']
if (Array.isArray(forwardedFor)) {
ip = forwardedFor.at(0)
} else {
ip = forwardedFor?.split(',').at(0) ?? 'Unknown'
}
}
const { success } = await rateLimit.limit(ip as string)
if (!success) return res.status(429).json({ error: 'Too many requests' })
}
return await NextAuth(req, res, authOptions)
2021-12-27 15:59:32 +01:00
}
2022-01-28 17:57:14 +01:00
const updateLastActivityDate = async (user: User) => {
const datesAreOnSameDay = (first: Date, second: Date) =>
first.getFullYear() === second.getFullYear() &&
first.getMonth() === second.getMonth() &&
first.getDate() === second.getDate()
if (!datesAreOnSameDay(user.lastActivityAt, new Date()))
await prisma.user.updateMany({
where: { id: user.id },
data: { lastActivityAt: new Date() },
})
}
2022-04-30 07:10:02 -07:00
const getUserGroups = async (account: Account): Promise<string[]> => {
switch (account.provider) {
case 'gitlab': {
2022-05-16 08:02:02 -07:00
const getGitlabGroups = async (
accessToken: string,
page = 1
): Promise<{ full_path: string }[]> => {
2022-05-16 15:49:24 +02:00
const res = await fetch(
2022-05-16 08:02:02 -07:00
`${
env.GITLAB_BASE_URL || 'https://gitlab.com'
2022-05-16 08:02:02 -07:00
}/api/v4/groups?per_page=100&page=${page}`,
2022-05-16 15:49:24 +02:00
{ headers: { Authorization: `Bearer ${accessToken}` } }
)
2022-05-16 08:02:02 -07:00
const groups: { full_path: string }[] = await res.json()
2022-05-16 15:49:24 +02:00
const nextPage = parseInt(res.headers.get('X-Next-Page') || '')
2022-05-16 08:02:02 -07:00
if (nextPage)
groups.push(...(await getGitlabGroups(accessToken, nextPage)))
2022-05-16 15:49:24 +02:00
return groups
}
2022-05-16 08:02:02 -07:00
const groups = await getGitlabGroups(account.access_token as string)
return groups.map((group) => group.full_path)
}
2022-04-30 07:10:02 -07:00
default:
return []
}
}
2022-04-30 07:10:02 -07:00
const getRequiredGroups = (provider: string): string[] => {
switch (provider) {
2022-04-30 07:10:02 -07:00
case 'gitlab':
return env.GITLAB_REQUIRED_GROUPS ?? []
2022-04-30 07:10:02 -07:00
default:
return []
}
}
2022-04-30 07:10:02 -07:00
const checkHasGroups = (userGroups: string[], requiredGroups: string[]) =>
userGroups?.some((userGroup) => requiredGroups?.includes(userGroup))
export default handler