2
0

first commit

This commit is contained in:
2024-08-09 00:39:27 +02:00
commit 79688abe2e
5698 changed files with 497838 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
import { jwtVerify } from "jose";
import type { GetServerSidePropsContext } from "next";
import { getCsrfToken } from "next-auth/react";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import prisma from "@calcom/prisma";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res, query } = context;
const session = await getServerSession({ req, res });
const ssr = await ssrInit(context);
const verifyJwt = (jwt: string) => {
const secret = new TextEncoder().encode(process.env.CALENDSO_ENCRYPTION_KEY);
return jwtVerify(jwt, secret, {
issuer: WEBSITE_URL,
audience: `${WEBSITE_URL}/auth/login`,
algorithms: ["HS256"],
});
};
let totpEmail = null;
if (context.query.totp) {
try {
const decryptedJwt = await verifyJwt(context.query.totp as string);
if (decryptedJwt.payload) {
totpEmail = decryptedJwt.payload.email as string;
} else {
return {
redirect: {
destination: "/auth/error?error=JWT%20Invalid%20Payload",
permanent: false,
},
};
}
} catch (e) {
return {
redirect: {
destination: "/auth/error?error=Invalid%20JWT%3A%20Please%20try%20again",
permanent: false,
},
};
}
}
if (session) {
const { callbackUrl } = query;
if (callbackUrl) {
try {
const destination = getSafeRedirectUrl(callbackUrl as string);
if (destination) {
return {
redirect: {
destination,
permanent: false,
},
};
}
} catch (e) {
console.warn(e);
}
}
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
const userCount = await prisma.user.count();
if (userCount === 0) {
// Proceed to new onboarding to create first admin user
return {
redirect: {
destination: "/auth/setup",
permanent: false,
},
};
}
return {
props: {
csrfToken: await getCsrfToken(context),
trpcState: ssr.dehydrate(),
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
samlTenantID,
samlProductID,
totpEmail,
},
};
}

View File

@@ -0,0 +1,19 @@
import type { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
// Deleting old cookie manually, remove this code after all existing cookies have expired
context.res?.setHeader(
"Set-Cookie",
"next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
);
return {
props: {
trpcState: ssr.dehydrate(),
query: context.query,
},
};
}

View File

@@ -0,0 +1,23 @@
import type { GetServerSidePropsContext } from "next";
import { getProviders, getCsrfToken } from "next-auth/react";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
const session = await getServerSession({ req });
const csrfToken = await getCsrfToken(context);
const providers = await getProviders();
if (session) {
return {
redirect: { destination: "/" },
};
}
return {
props: {
csrfToken,
providers,
},
};
}

View File

@@ -0,0 +1,142 @@
import type { GetServerSidePropsContext } from "next";
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 { checkUsername } from "@calcom/lib/server/checkUsername";
import prisma from "@calcom/prisma";
import { asStringOrNull } from "@lib/asStringOrNull";
import { ssrInit } from "@server/lib/ssr";
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<string | null> => {
// @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;
};

View File

@@ -0,0 +1,10 @@
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
export async function getServerSideProps() {
return {
props: {
samlTenantID,
samlProductID,
},
};
}

View File

@@ -0,0 +1,11 @@
import type { GetServerSidePropsContext } from "next";
export const getServerSideProps = async (_context: GetServerSidePropsContext) => {
const EMAIL_FROM = process.env.EMAIL_FROM;
return {
props: {
EMAIL_FROM,
},
};
};

View File

@@ -0,0 +1,6 @@
export const GOOGLE_API_CREDENTIALS = process.env.GOOGLE_API_CREDENTIALS || "{}";
export const { client_id: GOOGLE_CLIENT_ID, client_secret: GOOGLE_CLIENT_SECRET } =
JSON.parse(GOOGLE_API_CREDENTIALS)?.web || {};
export const GOOGLE_LOGIN_ENABLED = process.env.GOOGLE_LOGIN_ENABLED === "true";
export const IS_GOOGLE_LOGIN_ENABLED = !!(GOOGLE_CLIENT_ID && GOOGLE_CLIENT_SECRET && GOOGLE_LOGIN_ENABLED);
export const IS_SAML_LOGIN_ENABLED = !!(process.env.SAML_DATABASE_URL && process.env.SAML_ADMINS);

View File

@@ -0,0 +1,37 @@
import type { GetServerSidePropsContext } from "next";
import { getCsrfToken } from "next-auth/react";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import prisma from "@calcom/prisma";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const id = context.params?.id as string;
let resetPasswordRequest = await prisma.resetPasswordRequest.findFirst({
where: {
id,
expires: {
gt: new Date(),
},
},
select: {
email: true,
},
});
try {
resetPasswordRequest &&
(await prisma.user.findUniqueOrThrow({ where: { email: resetPasswordRequest.email } }));
} catch (e) {
resetPasswordRequest = null;
}
const locale = await getLocale(context.req);
return {
props: {
isRequestExpired: !resetPasswordRequest,
requestId: id,
csrfToken: await getCsrfToken({ req: context.req }),
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View File

@@ -0,0 +1,27 @@
import type { GetServerSidePropsContext } from "next";
import { getCsrfToken } from "next-auth/react";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res } = context;
const session = await getServerSession({ req });
// @TODO res will not be available in future pages (app dir)
if (session) {
res.writeHead(302, { Location: "/" });
res.end();
return { props: {} };
}
const locale = await getLocale(context.req);
return {
props: {
csrfToken: await getCsrfToken(context),
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View File

@@ -0,0 +1,22 @@
import type { GetStaticPropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { i18n } = require("@calcom/config/next-i18next.config");
export async function getTranslations<TParams extends { locale?: string }>(
opts: GetStaticPropsContext<TParams>
) {
const requestedLocale = opts.params?.locale || opts.locale || i18n.defaultLocale;
const isSupportedLocale = i18n.locales.includes(requestedLocale);
if (!isSupportedLocale) {
console.warn(`Requested unsupported locale "${requestedLocale}"`);
}
const locale = isSupportedLocale ? requestedLocale : i18n.defaultLocale;
const _i18n = await serverSideTranslations(locale, ["common"]);
return {
i18n: _i18n,
};
}

View File

@@ -0,0 +1,56 @@
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { ssrInit } from "@server/lib/ssr";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
const ssr = await ssrInit(context);
const userCount = await prisma.user.count();
const session = await getServerSession({ req });
if (session?.user.role && session?.user.role !== UserPermissionRole.ADMIN) {
return {
redirect: {
destination: `/404`,
permanent: false,
},
};
}
const deploymentKey = await prisma.deployment.findUnique({
where: { id: 1 },
select: { licenseKey: true },
});
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) {
await prisma.deployment.upsert({
where: { id: 1 },
update: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
create: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
});
}
const isFreeLicense = (await getDeploymentKey(prisma)) === "";
return {
props: {
trpcState: ssr.dehydrate(),
isFreeLicense,
userCount,
},
};
}

View File

@@ -0,0 +1,51 @@
import type { GetStaticPropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import superjson from "superjson";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import prisma, { readonlyPrisma } from "@calcom/prisma";
import { createServerSideHelpers } from "@calcom/trpc/react/server";
import { appRouter } from "@calcom/trpc/server/routers/_app";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { i18n } = require("@calcom/config/next-i18next.config");
/**
* Initialize static site rendering tRPC helpers.
* Provides a method to prefetch tRPC-queries in a `getStaticProps`-function.
* Automatically prefetches i18n based on the passed in `context`-object to prevent i18n-flickering.
* Make sure to `return { props: { trpcState: ssr.dehydrate() } }` at the end.
*/
export async function ssgInit<TParams extends { locale?: string }>(opts: GetStaticPropsContext<TParams>) {
const requestedLocale = opts.params?.locale || opts.locale || i18n.defaultLocale;
const isSupportedLocale = i18n.locales.includes(requestedLocale);
if (!isSupportedLocale) {
console.warn(`Requested unsupported locale "${requestedLocale}"`);
}
const locale = isSupportedLocale ? requestedLocale : i18n.defaultLocale;
const _i18n = await serverSideTranslations(locale, ["common"]);
const ssg = createServerSideHelpers({
router: appRouter,
transformer: superjson,
ctx: {
prisma,
insightsDb: readonlyPrisma,
session: null,
locale,
i18n: _i18n,
},
});
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
// we can set query data directly to the queryClient
const queryKey = [
["viewer", "public", "i18n"],
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
];
ssg.queryClient.setQueryData(queryKey, { i18n: _i18n });
return ssg;
}

View File

@@ -0,0 +1,88 @@
import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import superjson from "superjson";
import { forms } from "@calcom/app-store/routing-forms/trpc/procedures/forms";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { map } from "@calcom/features/flags/server/procedures/map";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import { createServerSideHelpers } from "@calcom/trpc/react/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { me } from "@calcom/trpc/server/routers/loggedInViewer/procedures/me";
import { teamsAndUserProfilesQuery } from "@calcom/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery";
import { event } from "@calcom/trpc/server/routers/publicViewer/procedures/event";
import { session } from "@calcom/trpc/server/routers/publicViewer/procedures/session";
import { get } from "@calcom/trpc/server/routers/viewer/eventTypes/procedures/get";
import { hasTeamPlan } from "@calcom/trpc/server/routers/viewer/teams/procedures/hasTeamPlan";
import { router, mergeRouters } from "@calcom/trpc/server/trpc";
const loggedInRouter = router({
me,
});
// Temporary workaround for OOM issue, import only procedures that are called on the server side
const routerSlice = router({
viewer: mergeRouters(
loggedInRouter,
router({
features: router({
map,
}),
public: router({
session,
event,
}),
teams: router({
hasTeamPlan,
}),
appRoutingForms: router({
forms,
}),
teamsAndUserProfilesQuery: router({
teamsAndUserProfilesQuery,
}),
eventTypes: router({
get,
}),
})
),
});
/**
* Initialize server-side rendering tRPC helpers.
* Provides a method to prefetch tRPC-queries in a `getServerSideProps`-function.
* Automatically prefetches i18n based on the passed in `context`-object to prevent i18n-flickering.
* Make sure to `return { props: { trpcState: ssr.dehydrate() } }` at the end.
*/
export async function ssrInit(context: GetServerSidePropsContext, options?: { noI18nPreload: boolean }) {
const ctx = await createContext(context);
const locale = await getLocale(context.req);
const i18n = await serverSideTranslations(locale, ["common", "vital"]);
const ssr = createServerSideHelpers({
router: routerSlice,
transformer: superjson,
ctx: { ...ctx, locale, i18n },
});
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
// we can set query data directly to the queryClient
const queryKey = [
["viewer", "public", "i18n"],
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
];
if (!options?.noI18nPreload) {
ssr.queryClient.setQueryData(queryKey, { i18n });
}
await Promise.allSettled([
// So feature flags are available on first render
ssr.viewer.features.map.prefetch(),
// Provides a better UX to the users who have already upgraded.
ssr.viewer.teams.hasTeamPlan.prefetch(),
ssr.viewer.public.session.prefetch(),
ssr.viewer.me.prefetch(),
]);
return ssr;
}