Compare commits
47 Commits
main
...
zomars/cal
Author | SHA1 | Date | |
---|---|---|---|
dcff308392 | |||
0667f3514f | |||
e879ae9aab | |||
e118af0839 | |||
15650afdfd | |||
a094c3e124 | |||
b575df9376 | |||
ad78d99320 | |||
280a530859 | |||
87316c79c9 | |||
48dd4048f9 | |||
e49ff393d6 | |||
ae152db45e | |||
c8c7a02d14 | |||
4a9cd4007a | |||
3be6ed49e9 | |||
0f165cae16 | |||
259166aa5f | |||
74d027b60e | |||
4317d4df0a | |||
6cb1bfe89f | |||
ae56d78886 | |||
c5c1a1f01c | |||
8235e1bebe | |||
86cafd979b | |||
f8235610c7 | |||
a779ad801f | |||
79c5f72e8c | |||
f63a59fa23 | |||
53164d5c0a | |||
c25ef3162f | |||
7006f0bf25 | |||
80fb612898 | |||
0c3321a35e | |||
27d969f995 | |||
09cc4d8227 | |||
250022a030 | |||
ce51fb5824 | |||
6960aeefb0 | |||
cd2d8bdb31 | |||
5b8c41c203 | |||
48ef3224fc | |||
28baf4b62d | |||
75bdd1a610 | |||
5dcb42f031 | |||
2011a4bbe2 | |||
475ba81855 |
|
@ -129,6 +129,7 @@
|
|||
"superjson": "1.9.1",
|
||||
"tailwindcss-radix": "^2.6.0",
|
||||
"turndown": "^7.1.1",
|
||||
"universal-cookie": "^6.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"web3": "^1.7.5",
|
||||
"zod": "^3.22.2"
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
|
@ -275,6 +276,7 @@ export type UserPageProps = {
|
|||
} & EmbedProps;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<UserPageProps> = async (context) => {
|
||||
setCsrfToken(context.res);
|
||||
const ssr = await ssrInit(context);
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||
const usernameList = getUsernameList(context.query.user as string);
|
||||
|
|
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import {
|
||||
|
@ -103,7 +104,6 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|||
} else if (bookingUid) {
|
||||
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
||||
}
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({
|
||||
|
@ -234,6 +234,7 @@ const paramsSchema = z.object({
|
|||
// Booker page fetches a tiny bit of data server side, to determine early
|
||||
// whether the page should show an away state or dynamic booking not allowed.
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
setCsrfToken(context.res);
|
||||
const { user } = paramsSchema.parse(context.params);
|
||||
const isDynamicGroup = user.length > 1;
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("✨ Getting CSRF token...");
|
||||
if (req.method === "GET") {
|
||||
setCsrfToken(res);
|
||||
res.status(200).json({ message: "OK!" });
|
||||
res.end();
|
||||
} else {
|
||||
// Handle any other HTTP method
|
||||
return res.status(501);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import { z } from "zod";
|
|||
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { WEBAPP_URL, WEBSITE_URL, HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
|
@ -279,6 +280,7 @@ inferSSRProps<typeof _getServerSideProps> & WithNonceProps<{}>) {
|
|||
|
||||
// TODO: Once we understand how to retrieve prop types automatically from getServerSideProps, remove this temporary variable
|
||||
const _getServerSideProps = async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
setCsrfToken(context.res);
|
||||
const { req, res, query } = context;
|
||||
|
||||
const session = await getServerSession({ req, res });
|
||||
|
|
|
@ -3,6 +3,7 @@ import { signOut, useSession } from "next-auth/react";
|
|||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
@ -71,6 +72,7 @@ Logout.PageWrapper = PageWrapper;
|
|||
export default Logout;
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
setCsrfToken(context.res);
|
||||
const ssr = await ssrInit(context);
|
||||
// Deleting old cookie manually, remove this code after all existing cookies have expired
|
||||
context.res.setHeader(
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { getProviders, signIn, getCsrfToken } from "next-auth/react";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { setCsrfToken } from "@calcom/features/auth/lib/set-csrf-token";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
@ -30,6 +31,7 @@ signin.PageWrapper = PageWrapper;
|
|||
export default signin;
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
setCsrfToken(context.res);
|
||||
const { req, res } = context;
|
||||
|
||||
const session = await getServerSession({ req, res });
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function ssrInit(context: GetServerSidePropsContext, options?: { no
|
|||
const ctx = await createContext(context);
|
||||
const locale = await getLocale(context.req);
|
||||
const i18n = await serverSideTranslations(locale, ["common", "vital"]);
|
||||
|
||||
ctx.req.headers["x-csrf-token"] = process.env.CSRF_SECRET;
|
||||
const ssr = createProxySSGHelpers({
|
||||
router: appRouter,
|
||||
transformer: superjson,
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
"city-timezones": "^1.2.1",
|
||||
"eslint": "^8.34.0",
|
||||
"lucide-react": "^0.171.0",
|
||||
"turbo": "^1.10.1"
|
||||
"turbo": "^1.10.1",
|
||||
"universal-cookie": "^6.1.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@apidevtools/json-schema-ref-parser": "9.0.9",
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { serialize } from "cookie";
|
||||
import { randomBytes } from "crypto";
|
||||
import type { ServerResponse } from "http";
|
||||
|
||||
export const setCsrfToken = (res: ServerResponse) => {
|
||||
const token = randomBytes(28).toString("hex");
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("csrf_token", token, {
|
||||
httpOnly: false, // important for reading cookie on the client
|
||||
maxAge: undefined, // expire with session
|
||||
sameSite: "strict",
|
||||
path: "/",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import type { NextPageContext } from "next/types";
|
||||
import superjson from "superjson";
|
||||
import Cookies from "universal-cookie";
|
||||
|
||||
import { httpBatchLink } from "../client/links/httpBatchLink";
|
||||
import { httpLink } from "../client/links/httpLink";
|
||||
|
@ -12,6 +13,8 @@ import type { TRPCClientErrorLike } from "../react";
|
|||
import type { inferRouterInputs, inferRouterOutputs, Maybe } from "../server";
|
||||
import type { AppRouter } from "../server/routers/_app";
|
||||
|
||||
const cookies = new Cookies();
|
||||
|
||||
/**
|
||||
* We deploy our tRPC router on multiple lambdas to keep number of imports as small as possible
|
||||
* TODO: Make this dynamic based on folders in trpc server?
|
||||
|
@ -68,19 +71,21 @@ const resolveEndpoint = (links: any) => {
|
|||
};
|
||||
};
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (typeof window !== "undefined") return ""; // browser should use relative url
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
|
||||
return `${process.env.NEXT_PUBLIC_WEBAPP_URL}`;
|
||||
};
|
||||
|
||||
const getCSRFToken = () => cookies.get("csrf_token") as string;
|
||||
|
||||
/**
|
||||
* A set of strongly-typed React hooks from your `AppRouter` type signature with `createTRPCReact`.
|
||||
* @link https://trpc.io/docs/v10/react#2-create-trpc-hooks
|
||||
*/
|
||||
export const trpc = createTRPCNext<AppRouter, NextPageContext, "ExperimentalSuspense">({
|
||||
config() {
|
||||
const url =
|
||||
typeof window !== "undefined"
|
||||
? "/api/trpc"
|
||||
: process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}/api/trpc`
|
||||
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
|
||||
|
||||
const url = `${getBaseUrl()}/api/trpc`;
|
||||
/**
|
||||
* If you want to use SSR, you need to use the server's full URL
|
||||
* @link https://trpc.io/docs/ssr
|
||||
|
@ -101,14 +106,46 @@ export const trpc = createTRPCNext<AppRouter, NextPageContext, "ExperimentalSusp
|
|||
// when condition is true, use normal request
|
||||
true: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [endpoint, httpLink({ url: `${url}/${endpoint}` })(runtime)])
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
headers() {
|
||||
return {
|
||||
"x-csrf-token": getCSRFToken(),
|
||||
};
|
||||
},
|
||||
fetch(url, options) {
|
||||
return fetch(url, {
|
||||
...options,
|
||||
credentials: "include",
|
||||
});
|
||||
},
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
// when condition is false, use batch request
|
||||
false: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [endpoint, httpBatchLink({ url: `${url}/${endpoint}` })(runtime)])
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpBatchLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
headers() {
|
||||
return {
|
||||
"x-csrf-token": getCSRFToken(),
|
||||
};
|
||||
},
|
||||
fetch(url, options) {
|
||||
return fetch(url, {
|
||||
...options,
|
||||
credentials: "include",
|
||||
});
|
||||
},
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { middleware } from "../trpc";
|
||||
|
||||
export const csrfMiddleware = middleware(({ ctx, next }) => {
|
||||
// Verify CSRF token
|
||||
const csrfCookie = ctx.req?.cookies["csrf_token"];
|
||||
const csrfToken = ctx.req?.headers["x-csrf-token"];
|
||||
console.table({
|
||||
"CSRF Token (from cookie)": csrfCookie,
|
||||
"CSRF Token (from headers)": csrfToken,
|
||||
});
|
||||
if (csrfToken && process.env.CSRF_SECRET && csrfToken === process.env.CSRF_SECRET) {
|
||||
console.info("CSRF secret detected, skipping middleware", process.env.CSRF_SECRET, csrfToken);
|
||||
return next();
|
||||
}
|
||||
if (!csrfToken || csrfToken !== csrfCookie) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "Invalid CSRF token" });
|
||||
}
|
||||
return next();
|
||||
});
|
|
@ -1,7 +1,11 @@
|
|||
import captureErrorsMiddleware from "../middlewares/captureErrorsMiddleware";
|
||||
import { csrfMiddleware } from "../middlewares/csrfMiddleware";
|
||||
import perfMiddleware from "../middlewares/perfMiddleware";
|
||||
import { tRPCContext } from "../trpc";
|
||||
|
||||
const publicProcedure = tRPCContext.procedure.use(captureErrorsMiddleware).use(perfMiddleware);
|
||||
const publicProcedure = tRPCContext.procedure
|
||||
.use(captureErrorsMiddleware)
|
||||
.use(perfMiddleware)
|
||||
.use(csrfMiddleware);
|
||||
|
||||
export default publicProcedure;
|
||||
|
|
|
@ -219,6 +219,7 @@
|
|||
"CLOSECOM_API_KEY",
|
||||
"CRON_API_KEY",
|
||||
"CRON_ENABLE_APP_SYNC",
|
||||
"CSRF_SECRET",
|
||||
"DAILY_API_KEY",
|
||||
"DAILY_SCALE_PLAN",
|
||||
"DEBUG",
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -4648,6 +4648,7 @@ __metadata:
|
|||
ts-node: ^10.9.1
|
||||
turndown: ^7.1.1
|
||||
typescript: ^4.9.4
|
||||
universal-cookie: ^6.1.1
|
||||
uuid: ^8.3.2
|
||||
web3: ^1.7.5
|
||||
zod: ^3.22.2
|
||||
|
@ -12985,6 +12986,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/cookie@npm:^0.5.1":
|
||||
version: 0.5.4
|
||||
resolution: "@types/cookie@npm:0.5.4"
|
||||
checksum: bd9603ce5e9bcbe1c2c9fabe3d1b1da131f1db10a7fa0077b17eb06d59bf4a6192fbb890597e8e2295067a078aaf8299bc662bfe993e8c2d1ecd62f859317ece
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/cross-spawn@npm:6.0.2":
|
||||
version: 6.0.2
|
||||
resolution: "@types/cross-spawn@npm:6.0.2"
|
||||
|
@ -17238,6 +17246,7 @@ __metadata:
|
|||
tsc-absolute: ^1.0.0
|
||||
turbo: ^1.10.1
|
||||
typescript: ^4.9.4
|
||||
universal-cookie: ^6.1.1
|
||||
vitest: ^0.34.3
|
||||
vitest-fetch-mock: ^0.2.2
|
||||
vitest-mock-extended: ^1.1.3
|
||||
|
@ -39754,6 +39763,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universal-cookie@npm:^6.1.1":
|
||||
version: 6.1.1
|
||||
resolution: "universal-cookie@npm:6.1.1"
|
||||
dependencies:
|
||||
"@types/cookie": ^0.5.1
|
||||
cookie: ^0.5.0
|
||||
checksum: 5c4d83f5879c5f133a51dadd080bf7a7a5c3a4b38695bb52ae560564884b84156d3c872abb1ade54a0ee00baf3870a59abc9907a4fa5bcf7a8d2d63af581c8e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^0.1.0, universalify@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "universalify@npm:0.1.2"
|
||||
|
|
Loading…
Reference in New Issue
Block a user