Compare commits

...

47 Commits

Author SHA1 Message Date
Morgan dcff308392
Merge branch 'main' into zomars/cal-2724-implement-csrf-protection-for-public-trpc-endpoints 2023-11-27 15:32:40 +02:00
zomars 0667f3514f WIP 2023-11-22 12:52:33 -07:00
zomars e879ae9aab WIP 2023-11-22 12:47:19 -07:00
zomars e118af0839 Merge branch 'main' into zomars/cal-2724-implement-csrf-protection-for-public-trpc-endpoints 2023-11-22 12:37:15 -07:00
zomars 15650afdfd fix: Locks Stripe version 2023-11-22 12:36:14 -07:00
JA a094c3e124 feat(webhooks): pass webhook secret to `testTrigger` (#12187) 2023-11-22 12:36:14 -07:00
Greg Pabian b575df9376 chore: migrate the event-types page to the app directory (#12390)
Co-authored-by: Omar López <zomars@me.com>
2023-11-22 12:36:14 -07:00
zomars ad78d99320 syncpack fix-mismatches 2023-11-22 12:36:14 -07:00
Udit Takkar 280a530859 fix: default organizer bug in managed event type (#11921) 2023-11-22 12:36:14 -07:00
Joe Au-Yeung 87316c79c9 test: Integration Test GCal Primary Calendar (#12011)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-11-22 12:36:14 -07:00
Joe Au-Yeung 48dd4048f9 chore: Clean Up Delete Credential Selected Calendar Error Message (#12353) 2023-11-22 12:36:14 -07:00
Matt Nicolls e49ff393d6 fix: include eventTypeId in BOOKING_CANCELLED event (#12445) 2023-11-22 12:36:14 -07:00
sebzz ae152db45e docs: add google credentials in example env (#11695)
* docs:add google credentials in example env

* docs: add a space after #

* chore: update .env.example

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 12:36:13 -07:00
Morgan c8c7a02d14 fix: alby payment isPaid always false on create (#12463) 2023-11-22 12:36:13 -07:00
Amit Sharma 4a9cd4007a chore: Add team invite tests (#12425)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-22 12:36:13 -07:00
Adugna Tadesse 3be6ed49e9 outlook second account fix (#12013)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-11-22 12:36:13 -07:00
Udit Takkar 0f165cae16 chore: reset form on submission (#12465) 2023-11-22 12:36:13 -07:00
Peer Richelsen 259166aa5f chore: ignore "platform" in pr-assign-team workflow (#12487) 2023-11-22 12:36:13 -07:00
Peer Richelsen 74d027b60e chore: fixed cal.ai thumbnail (#12486) 2023-11-22 12:36:13 -07:00
Peer Richelsen 4317d4df0a chore: fix cal.ai price (#12485) 2023-11-22 12:36:13 -07:00
Somay Chauhan 6cb1bfe89f fix: opening team invite link in email throws error on signup page (#12397)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-11-22 12:36:13 -07:00
Syed Ali Shahbaz ae56d78886 fix: Admin Logic for event-type API endpoint (#12482)
* Fix Admin logic

* chore: fix prettier

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 12:36:13 -07:00
Hariom Balhara c5c1a1f01c test: Add more orgs tests (#12241) 2023-11-22 12:36:13 -07:00
Morgan 8235e1bebe fix: better errors for googlecalendar integration (#12403) 2023-11-22 12:36:13 -07:00
Ujjwal Goyal 86cafd979b fix: Date overrides UI bug depending on screen size (#12423)
* Update DateOverrideInputDialog.tsx

fix: Date overrides UI bug depending on screen size (calcom#12406)

* chore: remove comment

---------

Co-authored-by: madhurgoyal19 <35370133+madhurgoyal19@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 12:36:13 -07:00
Crowdin Bot f8235610c7 New Crowdin translations by Github Action 2023-11-22 12:36:13 -07:00
Morgan a779ad801f fix: alby payment could not be created (#12460)
* fix: alby payment could not be created

* fixup! fix: alby payment could not be created

* fixup! fixup! fix: alby payment could not be created
2023-11-22 12:36:13 -07:00
Crowdin Bot 79c5f72e8c New Crowdin translations by Github Action 2023-11-22 12:36:13 -07:00
Peer Richelsen f63a59fa23 chore: paid support wip (#12419)
* paid support wip

* nit

* added upgrade box to intercom

* nit
2023-11-22 12:36:13 -07:00
Manpreet Singh 53164d5c0a fix: slug value in webhook payload (#12290)
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-22 12:36:13 -07:00
Lars Artmann c25ef3162f fix: [embed-react] types fix for compatible with TypeScript's "moduleResolution": "bundler" (#12327)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-22 12:36:13 -07:00
Somay Chauhan 7006f0bf25 fix: don't send notes to booker if "Hide notes in calendar" is on (#12333) 2023-11-22 12:36:13 -07:00
Alex van Andel 80fb612898 fix: Better error reporting & some fixes to deleteUser API (#12439) 2023-11-22 12:36:13 -07:00
sean-brydon 0c3321a35e fix: identifer (#12426)
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 12:36:13 -07:00
Alex van Andel 27d969f995 feat: Base implementation of v2 of avatars (#12369)
* feat: Base implementation of v2 of avatars

* Make avatarUrl and logoUrl entirely optional

* Made necessary backwards compat changes

* fix: type errors

* Fix: OG image

* fix types

* Consistency with other behaviour, ux tweak

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-22 12:36:13 -07:00
Hariom Balhara 09cc4d8227 fix: Members count when `team` slug is same as `org` slug (#12124) 2023-11-22 12:36:13 -07:00
sean-brydon 250022a030 feat: troubleshooter with weekly view (V2) (#12280)
* Inital UI + layout setup

* use booker approach of grid

* event-select - sidebar + store work

* adds get schedule by event-type-slug

* Calendar toggle

* Load schedule from event slug

* Add busy events to calendar

* useschedule

* Store more event info than just slug

* Add date override to calendar

* Changes sizes on smaller screens

* add event title as a tooltip

* Ensure header navigation works

* Stop navigator throwing errors on inital render

* Correct br

* Event duration fixes

* Add getMoreInfo if user is authed with current request.username

* Add calendar color map wip

* Add WIP comments for coloured outlines

* Revert more info changes

* Calculate date override correctly

* Add description option

* Fix inital schedule data not being populated

* Nudge overlap over to make it clearer

* Fix disabled state

* WIP on math logic

* Event list overlapping events logic

* NIT about width

* i18n + manage calendars link

* Delete old troubleshooter

* Update packages/features/calendars/weeklyview/components/event/EventList.tsx

* Remove t-slots

* Fix i18n & install calendar action

* sm:imrovments

* NITS

* Fix types

* fix: back button

* Month prop null as we control from query param

* Add head SEO

* Fix headseo import

* Fix date override tests
2023-11-22 12:36:13 -07:00
Crowdin Bot ce51fb5824 New Crowdin translations by Github Action 2023-11-22 12:36:12 -07:00
Hariom Balhara 6960aeefb0 fix: add node-mocks-http to web (#12435) 2023-11-22 12:36:12 -07:00
Morgan Vernay cd2d8bdb31 fix: csrf on more public pages 2023-11-17 22:48:57 +02:00
Morgan Vernay 5b8c41c203 fix: csrf with get server side props 2023-11-17 22:20:58 +02:00
zomars 48ef3224fc Merge branch 'main' into zomars/cal-2724-implement-csrf-protection-for-public-trpc-endpoints 2023-11-17 11:21:31 -07:00
Crowdin Bot 28baf4b62d New Crowdin translations by Github Action 2023-11-17 11:21:14 -07:00
Crowdin Bot 75bdd1a610 New Crowdin translations by Github Action 2023-11-17 11:21:14 -07:00
Omar López 5dcb42f031 feat: implements basic user locking for admins (#12393)
* feat: implements basic user locking for admins

* Update sendPasswordReset.handler.ts

* check fixes

* Update packages/features/ee/users/components/UsersTable.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-17 11:21:14 -07:00
Morgan 2011a4bbe2
Merge branch 'main' into zomars/cal-2724-implement-csrf-protection-for-public-trpc-endpoints 2023-11-17 16:41:36 +02:00
zomars 475ba81855 WIP 2023-11-16 17:19:12 -07:00
15 changed files with 138 additions and 13 deletions

View File

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

View File

@ -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);

View File

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

View File

@ -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);
}
}

View File

@ -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 });

View File

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

View File

@ -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 });

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

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

View File

@ -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",
})
);
};

View File

@ -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);
},

View File

@ -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();
});

View File

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

View File

@ -219,6 +219,7 @@
"CLOSECOM_API_KEY",
"CRON_API_KEY",
"CRON_ENABLE_APP_SYNC",
"CSRF_SECRET",
"DAILY_API_KEY",
"DAILY_SCALE_PLAN",
"DEBUG",

View File

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