fix: csrf with get server side props

This commit is contained in:
Morgan Vernay 2023-11-17 22:20:58 +02:00
parent 48ef3224fc
commit 5b8c41c203
7 changed files with 31 additions and 17 deletions

View File

@ -2,6 +2,7 @@ import type { DehydratedState } from "@tanstack/react-query";
import classNames from "classnames";
import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Link from "next/link";
import { setCsrfToken } from "pages/api/auth/csrf";
import { Toaster } from "react-hot-toast";
import type { z } from "zod";
@ -274,6 +275,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);

View File

@ -1,4 +1,5 @@
import type { GetServerSidePropsContext } from "next";
import { setCsrfToken } from "pages/api/auth/csrf";
import { z } from "zod";
import { Booker } from "@calcom/atoms";
@ -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;

View File

@ -1,21 +1,26 @@
import { serialize } from "cookie";
import { randomBytes } from "crypto";
import type { ServerResponse } from "http";
import type { NextApiRequest, NextApiResponse } from "next";
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",
})
);
};
export default function handler(req: NextApiRequest, res: NextApiResponse) {
console.log("✨ Getting CSRF token...");
if (req.method === "GET") {
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",
})
);
setCsrfToken(res);
res.status(200).json({ message: "OK!" });
res.end();
} else {

View File

@ -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,

View File

@ -105,7 +105,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",

View File

@ -4,12 +4,16 @@ 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"];
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" });
}

View File

@ -330,6 +330,7 @@
"ZOHOCRM_CLIENT_ID",
"ZOHOCRM_CLIENT_SECRET",
"ZOOM_CLIENT_ID",
"ZOOM_CLIENT_SECRET"
"ZOOM_CLIENT_SECRET",
"CSRF_SECRET"
]
}