diff --git a/apps/web/app/future/auth/sso/[provider]/page.tsx b/apps/web/app/future/auth/sso/[provider]/page.tsx new file mode 100644 index 0000000000..2a59c87a8a --- /dev/null +++ b/apps/web/app/future/auth/sso/[provider]/page.tsx @@ -0,0 +1,12 @@ +import Provider from "@pages/auth/sso/[provider]"; +import { withAppDirSsr } from "app/WithAppDirSsr"; +import { WithLayout } from "app/layoutHOC"; +import type { InferGetServerSidePropsType } from "next"; + +import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps"; + +export default WithLayout({ + getLayout: null, + Page: Provider, + getData: withAppDirSsr>(getServerSideProps), +})<"P">; diff --git a/apps/web/app/future/auth/sso/direct/page.tsx b/apps/web/app/future/auth/sso/direct/page.tsx new file mode 100644 index 0000000000..0f69f324fe --- /dev/null +++ b/apps/web/app/future/auth/sso/direct/page.tsx @@ -0,0 +1,11 @@ +import DirectSSOLogin from "@pages/auth/sso/direct"; +import { withAppDirSsr } from "app/WithAppDirSsr"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps"; + +export default WithLayout({ + getLayout: null, + Page: DirectSSOLogin, + getData: withAppDirSsr(getServerSideProps), +})<"P">; diff --git a/apps/web/pages/auth/sso/[provider].tsx b/apps/web/pages/auth/sso/[provider].tsx index f1a2e759ba..8aa522667f 100644 --- a/apps/web/pages/auth/sso/[provider].tsx +++ b/apps/web/pages/auth/sso/[provider].tsx @@ -1,25 +1,16 @@ -import type { GetServerSidePropsContext } from "next"; +"use client"; + import { signIn } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; -import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; -import stripe from "@calcom/features/ee/payments/server/stripe"; -import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; -import { ssoTenantProduct } from "@calcom/features/ee/sso/lib/sso"; -import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; -import { checkUsername } from "@calcom/lib/server/checkUsername"; -import prisma from "@calcom/prisma"; -import { asStringOrNull } from "@lib/asStringOrNull"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; import PageWrapper from "@components/PageWrapper"; -import { ssrInit } from "@server/lib/ssr"; +import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps"; export type SSOProviderPageProps = inferSSRProps; @@ -52,129 +43,4 @@ export default function Provider(props: SSOProviderPageProps) { Provider.PageWrapper = PageWrapper; -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - // get query params and typecast them to string - // (would be even better to assert them instead of typecasting) - const providerParam = asStringOrNull(context.query.provider); - const emailParam = asStringOrNull(context.query.email); - const usernameParam = asStringOrNull(context.query.username); - const successDestination = `/getting-started${usernameParam ? `?username=${usernameParam}` : ""}`; - if (!providerParam) { - throw new Error(`File is not named sso/[provider]`); - } - - const { req } = context; - - const session = await getServerSession({ req }); - const ssr = await ssrInit(context); - const { currentOrgDomain } = orgDomainConfig(context.req); - - if (session) { - // Validating if username is Premium, while this is true an email its required for stripe user confirmation - if (usernameParam && session.user.email) { - const availability = await checkUsername(usernameParam, currentOrgDomain); - if (availability.available && availability.premium && IS_PREMIUM_USERNAME_ENABLED) { - const stripePremiumUrl = await getStripePremiumUsernameUrl({ - userEmail: session.user.email, - username: usernameParam, - successDestination, - }); - if (stripePremiumUrl) { - return { - redirect: { - destination: stripePremiumUrl, - permanent: false, - }, - }; - } - } - } - - return { - redirect: { - destination: successDestination, - permanent: false, - }, - }; - } - - let error: string | null = null; - - let tenant = samlTenantID; - let product = samlProductID; - - if (providerParam === "saml" && hostedCal) { - if (!emailParam) { - error = "Email not provided"; - } else { - try { - const ret = await ssoTenantProduct(prisma, emailParam); - tenant = ret.tenant; - product = ret.product; - } catch (e) { - if (e instanceof Error) { - error = e.message; - } - } - } - } - - if (error) { - return { - redirect: { - destination: `/auth/error?error=${error}`, - permanent: false, - }, - }; - } - - return { - props: { - trpcState: ssr.dehydrate(), - provider: providerParam, - isSAMLLoginEnabled, - hostedCal, - tenant, - product, - error, - }, - }; -}; - -type GetStripePremiumUsernameUrl = { - userEmail: string; - username: string; - successDestination: string; -}; - -const getStripePremiumUsernameUrl = async ({ - userEmail, - username, - successDestination, -}: GetStripePremiumUsernameUrl): Promise => { - // @TODO: probably want to check if stripe user email already exists? or not - const customer = await stripe.customers.create({ - email: userEmail, - metadata: { - email: userEmail, - username, - }, - }); - - const checkoutSession = await stripe.checkout.sessions.create({ - mode: "subscription", - payment_method_types: ["card"], - customer: customer.id, - line_items: [ - { - price: getPremiumMonthlyPlanPriceId(), - quantity: 1, - }, - ], - success_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}${successDestination}&session_id={CHECKOUT_SESSION_ID}`, - cancel_url: process.env.NEXT_PUBLIC_WEBAPP_URL || "https://app.cal.com", - allow_promotion_codes: true, - }); - - return checkoutSession.url; -}; +export { getServerSideProps }; diff --git a/apps/web/pages/auth/sso/direct.tsx b/apps/web/pages/auth/sso/direct.tsx index d3b5ce8d84..b616c59250 100644 --- a/apps/web/pages/auth/sso/direct.tsx +++ b/apps/web/pages/auth/sso/direct.tsx @@ -1,14 +1,17 @@ +"use client"; + import { signIn } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; -import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; import PageWrapper from "@components/PageWrapper"; +import type { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps"; + // This page is used to initiate the SAML authentication flow by redirecting to the SAML provider. // Accessible only on self-hosted Cal.com instances. export default function Page({ samlTenantID, samlProductID }: inferSSRProps) { @@ -36,13 +39,6 @@ export default function Page({ samlTenantID, samlProductID }: inferSSRProps { + // get query params and typecast them to string + // (would be even better to assert them instead of typecasting) + const providerParam = asStringOrNull(context.query.provider); + const emailParam = asStringOrNull(context.query.email); + const usernameParam = asStringOrNull(context.query.username); + const successDestination = `/getting-started${usernameParam ? `?username=${usernameParam}` : ""}`; + if (!providerParam) { + throw new Error(`File is not named sso/[provider]`); + } + + const { req } = context; + + const session = await getServerSession({ req }); + const ssr = await ssrInit(context); + const { currentOrgDomain } = orgDomainConfig(context.req); + + if (session) { + // Validating if username is Premium, while this is true an email its required for stripe user confirmation + if (usernameParam && session.user.email) { + const availability = await checkUsername(usernameParam, currentOrgDomain); + if (availability.available && availability.premium && IS_PREMIUM_USERNAME_ENABLED) { + const stripePremiumUrl = await getStripePremiumUsernameUrl({ + userEmail: session.user.email, + username: usernameParam, + successDestination, + }); + if (stripePremiumUrl) { + return { + redirect: { + destination: stripePremiumUrl, + permanent: false, + }, + }; + } + } + } + + return { + redirect: { + destination: successDestination, + permanent: false, + }, + }; + } + + let error: string | null = null; + + let tenant = samlTenantID; + let product = samlProductID; + + if (providerParam === "saml" && hostedCal) { + if (!emailParam) { + error = "Email not provided"; + } else { + try { + const ret = await ssoTenantProduct(prisma, emailParam); + tenant = ret.tenant; + product = ret.product; + } catch (e) { + if (e instanceof Error) { + error = e.message; + } + } + } + } + + if (error) { + return { + redirect: { + destination: `/auth/error?error=${error}`, + permanent: false, + }, + }; + } + + return { + props: { + trpcState: ssr.dehydrate(), + provider: providerParam, + isSAMLLoginEnabled, + hostedCal, + tenant, + product, + error, + }, + }; +}; + +type GetStripePremiumUsernameUrl = { + userEmail: string; + username: string; + successDestination: string; +}; + +const getStripePremiumUsernameUrl = async ({ + userEmail, + username, + successDestination, +}: GetStripePremiumUsernameUrl): Promise => { + // @TODO: probably want to check if stripe user email already exists? or not + const customer = await stripe.customers.create({ + email: userEmail, + metadata: { + email: userEmail, + username, + }, + }); + + const checkoutSession = await stripe.checkout.sessions.create({ + mode: "subscription", + payment_method_types: ["card"], + customer: customer.id, + line_items: [ + { + price: getPremiumMonthlyPlanPriceId(), + quantity: 1, + }, + ], + success_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}${successDestination}&session_id={CHECKOUT_SESSION_ID}`, + cancel_url: process.env.NEXT_PUBLIC_WEBAPP_URL || "https://app.cal.com", + allow_promotion_codes: true, + }); + + return checkoutSession.url; +}; diff --git a/apps/web/server/lib/auth/sso/direct/getServerSideProps.tsx b/apps/web/server/lib/auth/sso/direct/getServerSideProps.tsx new file mode 100644 index 0000000000..e18ac44cc4 --- /dev/null +++ b/apps/web/server/lib/auth/sso/direct/getServerSideProps.tsx @@ -0,0 +1,10 @@ +import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; + +export async function getServerSideProps() { + return { + props: { + samlTenantID, + samlProductID, + }, + }; +}