fix: Add CORP and COEP headers to embedded pages (#15275)
* Add CORP headers * Improve test * Add unit tests as well --------- Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
co-authored by
Keith Williams
parent
6e66f7f8f8
commit
37caa57798
@@ -14,7 +14,10 @@ export default function withEmbedSsrAppDir<T extends Record<string, any>>(
|
||||
) {
|
||||
return async (context: GetServerSidePropsContext): Promise<T> => {
|
||||
const { embed, layout } = context.query;
|
||||
|
||||
const isCOEPEnabled = context.query["flag.coep"] === "true";
|
||||
if (isCOEPEnabled) {
|
||||
context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
}
|
||||
try {
|
||||
const props = await getData(context);
|
||||
|
||||
|
||||
@@ -133,6 +133,24 @@ describe("withEmbedSsr", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
redirectUrl: "/reschedule",
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when redirect destination is absolute, should add /embed to end of the path", () => {
|
||||
@@ -206,6 +224,23 @@ describe("withEmbedSsr", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
redirectUrl: "https://calcom.cal.local/owner",
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,6 +268,85 @@ describe("withEmbedSsr", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
props: {
|
||||
prop1: "value1",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp");
|
||||
});
|
||||
|
||||
it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is false', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
props: {
|
||||
prop1: "value1",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "false",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is not set', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
props: {
|
||||
prop1: "value1",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is set to something other than true', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
props: {
|
||||
prop1: "value1",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "abasdf",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when gSSP doesn't have props or redirect ", () => {
|
||||
@@ -254,5 +368,23 @@ describe("withEmbedSsr", () => {
|
||||
|
||||
expect(ret).toEqual({ notFound: true });
|
||||
});
|
||||
|
||||
it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => {
|
||||
const withEmbedGetSsr = withEmbedSsr(
|
||||
getServerSidePropsFnGenerator({
|
||||
notFound: true,
|
||||
})
|
||||
);
|
||||
|
||||
const context = getServerSidePropsContextArg({
|
||||
embedRelatedParams: {
|
||||
layout: "week_view",
|
||||
embed: "",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
});
|
||||
await withEmbedGetSsr(context);
|
||||
expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,10 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
|
||||
const ssrResponse = await getServerSideProps(context);
|
||||
const embed = context.query.embed;
|
||||
const layout = context.query.layout;
|
||||
const isCOEPEnabled = context.query["flag.coep"] === "true";
|
||||
if (isCOEPEnabled) {
|
||||
context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
}
|
||||
|
||||
if ("redirect" in ssrResponse) {
|
||||
const destinationUrl = ssrResponse.redirect.destination;
|
||||
@@ -44,6 +48,7 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
|
||||
if (!("props" in ssrResponse)) {
|
||||
return ssrResponse;
|
||||
}
|
||||
|
||||
return {
|
||||
...ssrResponse,
|
||||
props: {
|
||||
|
||||
@@ -387,6 +387,13 @@ const nextConfig = {
|
||||
};
|
||||
},
|
||||
async headers() {
|
||||
// This header can be set safely as it ensures the browser will load the resources even when COEP is set.
|
||||
// But this header must be set only on those resources that are safe to be loaded in a cross-origin context e.g. all embeddable pages's resources
|
||||
const CORP_CROSS_ORIGIN_HEADER = {
|
||||
key: "Cross-Origin-Resource-Policy",
|
||||
value: "cross-origin",
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
source: "/auth/:path*",
|
||||
@@ -419,6 +426,26 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "/embed/embed.js",
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER],
|
||||
},
|
||||
{
|
||||
source: "/:path*/embed",
|
||||
// COEP require-corp header is set conditionally when flag.coep is set to true
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER],
|
||||
},
|
||||
// These resources loads through embed as well, so they need to have CORP_CROSS_ORIGIN_HEADER
|
||||
...[
|
||||
{
|
||||
source: "/api/avatar/:path*",
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER],
|
||||
},
|
||||
{
|
||||
source: "/avatar.svg",
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER],
|
||||
},
|
||||
],
|
||||
...(isOrganizationsEnabled
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { uid: bookingUid, seatReferenceUid } = z
|
||||
.object({ uid: z.string(), seatReferenceUid: z.string().optional() })
|
||||
.parse(context.query);
|
||||
|
||||
const coepFlag = context.query["flag.coep"];
|
||||
const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat(
|
||||
prisma,
|
||||
bookingUid
|
||||
@@ -136,6 +136,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
|
||||
destinationUrl.set("rescheduleUid", seatReferenceUid || bookingUid);
|
||||
|
||||
// TODO: I think we should just forward all the query params here including coep flag
|
||||
if (coepFlag) {
|
||||
destinationUrl.set("flag.coep", coepFlag as string);
|
||||
}
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/${eventPage}?${destinationUrl.toString()}${
|
||||
|
||||
@@ -58,6 +58,9 @@ export const getServerSideProps = async function getServerSideProps(
|
||||
const { currentOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const isEmbed = params.appPages[1] === "embed";
|
||||
if (context.query["flag.coep"] === "true") {
|
||||
context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
}
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
Cal.ns.withoutReloadNamespace("inline", {
|
||||
elementOrSelector: selector,
|
||||
calLink: "pro?case=addInlineEmbedInANewNamespaceWithoutReload",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -88,6 +91,9 @@
|
||||
Cal.ns.doubleInstall("inline", {
|
||||
elementOrSelector: selector,
|
||||
calLink: "pro?case=doubleInstallSnippetWithInlineEmbedWithNonDefaultNamespace",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
embedSnippet();
|
||||
Cal("init", "doubleInstall", {
|
||||
@@ -97,6 +103,9 @@
|
||||
Cal.ns.doubleInstall("inline", {
|
||||
elementOrSelector: selector,
|
||||
calLink: "pro",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -109,6 +118,9 @@
|
||||
Cal("inline", {
|
||||
elementOrSelector: selector,
|
||||
calLink: "pro?case=doubleInstallSnippetWithInlineEmbed",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
embedSnippet();
|
||||
Cal("init", {
|
||||
@@ -118,6 +130,9 @@
|
||||
Cal("inline", {
|
||||
elementOrSelector: selector,
|
||||
calLink: "pro",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,10 +148,16 @@
|
||||
Cal.ns['namespace-init-test-2']("inline", {
|
||||
elementOrSelector: '#two-different-namespace-with-different-init-config-content-2',
|
||||
calLink: "pro",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
Cal.ns['namespace-init-test-1']("inline", {
|
||||
elementOrSelector: '#two-different-namespace-with-different-init-config-content-1',
|
||||
calLink: "pro",
|
||||
config: {
|
||||
"flag.coep":"true"
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -198,7 +219,7 @@
|
||||
<script>
|
||||
if (only === "all" || only === "prerender-test") {
|
||||
document.write(`
|
||||
<button data-cal-namespace="e2ePrerenderLightTheme" data-cal-config='{"theme":"dark", "email":"preloaded-prefilled@example.com", "name": "Preloaded Prefilled"}' data-cal-link="free/30min">Book with Free User[Dark Theme]</button>
|
||||
<button data-cal-namespace="e2ePrerenderLightTheme" data-cal-config='{"theme":"dark", "email":"preloaded-prefilled@example.com", "name": "Preloaded Prefilled", "flag.coep": "true"}' data-cal-link="free/30min">Book with Free User[Dark Theme]</button>
|
||||
<i
|
||||
>Corresponding Cal Link is being prerendered. Assuming that it would take you some time to click this
|
||||
as you are reading this text, it would open up super fast[If you are running a production build on
|
||||
@@ -208,7 +229,7 @@
|
||||
}
|
||||
if (only === "all" || only === "preload-test") {
|
||||
document.write(`
|
||||
<button data-cal-namespace="preloadTest" data-cal-config='{"theme":"dark", "email":"preloaded-prefilled@example.com", "name": "Preloaded Prefilled"}' data-cal-link="free/30min">Book with Free User[Dark Theme]</button>
|
||||
<button data-cal-namespace="preloadTest" data-cal-config='{"theme":"dark", "email":"preloaded-prefilled@example.com", "name": "Preloaded Prefilled", "flag.coep": "true"}' data-cal-link="free/30min">Book with Free User[Dark Theme]</button>
|
||||
<i
|
||||
>Corresponding Cal Link is being preloaded. That means that all the resources would be preloaded. This could be useful in preloading possible resources if you don't know before hand which type of embed you want to show</i
|
||||
>`);
|
||||
@@ -222,58 +243,58 @@
|
||||
<a href="?only=ns:floatingButton">Floating Popup</a>
|
||||
|
||||
<h2>Popup Examples</h2>
|
||||
<button data-cal-namespace="e2ePopupLightTheme" data-cal-link="free" data-cal-config='{"theme":"light"}'>Book an event with Free[Light Theme]</button>
|
||||
<button data-cal-namespace="popupAutoTheme" data-cal-link="free">
|
||||
<button data-cal-namespace="e2ePopupLightTheme" data-cal-link="free" data-cal-config='{"theme":"light", "flag.coep": "true"}'>Book an event with Free[Light Theme]</button>
|
||||
<button data-cal-namespace="popupAutoTheme" data-cal-link="free" data-cal-config='{"flag.coep": "true"}'>
|
||||
Book with Free User[Auto Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">
|
||||
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark", "flag.coep": "true"}' data-cal-link="free">
|
||||
Book with Free User[Dark Theme]
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="popupTeamLinkLightTheme"
|
||||
data-cal-config='{"theme":"light"}'
|
||||
data-cal-config='{"theme":"light", "flag.coep": "true"}'
|
||||
data-cal-link="team/seeded-team/collective-seeded-team-event">
|
||||
Book with Test Team[Light Theme]
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="popupTeamLinkDarkTheme"
|
||||
data-cal-config='{"theme":"dark"}'
|
||||
data-cal-config='{"theme":"dark", "flag.coep": "true"}'
|
||||
data-cal-link="team/seeded-team/collective-seeded-team-event">
|
||||
Book with Test Team[Dark Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">
|
||||
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/" data-cal-config='{"flag.coep": "true"}'>
|
||||
See Team Links [Auto Theme]
|
||||
</button>
|
||||
<script>
|
||||
let popupRescheduleId =
|
||||
new URL(document.URL).searchParams.get("popupRescheduleId") || "qm3kwt3aTnVD7vmP9tiT2f";
|
||||
document.write(
|
||||
`<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/${popupRescheduleId}">Reschedule Event[Auto Theme]</button>`
|
||||
`<button data-cal-namespace="popupReschedule" data-cal-config='{"flag.coep":"true"}' data-cal-link="reschedule/${popupRescheduleId}">Reschedule Event[Auto Theme]</button>`
|
||||
);
|
||||
</script>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid" data-cal-config='{"flag.coep": "true"}'>
|
||||
Book Paid Event [Auto Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">
|
||||
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min" data-cal-config='{"flag.coep": "true"}'>
|
||||
Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]
|
||||
</button>
|
||||
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
|
||||
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03" data-cal-config='{"flag.coep": "true"}'>
|
||||
Book through Routing Form [Auto Theme] - Test for 'routed' event
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="routingFormDark"
|
||||
data-cal-config='{"theme":"dark"}'
|
||||
data-cal-config='{"theme":"dark", "flag.coep": "true"}'
|
||||
data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
|
||||
Book through Routing Form [Dark Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"week_view"}' data-cal-link="pro/paid">
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"week_view", "flag.coep": "true"}' data-cal-link="pro/paid">
|
||||
Book Paid Event - weekly view
|
||||
</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"column_view"}' data-cal-link="pro/paid">
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"column_view", "flag.coep": "true"}' data-cal-link="pro/paid">
|
||||
Book Paid Event - column view
|
||||
</button>
|
||||
|
||||
<a style="display: block;" data-cal-namespace="childElementTarget" href="javascript:void(0)" data-cal-link="free/30min">
|
||||
<a style="display: block;" data-cal-namespace="childElementTarget" href="javascript:void(0)" data-cal-link="free/30min" data-cal-config='{"flag.coep": "true"}'>
|
||||
I am Anchor
|
||||
<b>[I am Bold inside anchor]</b>
|
||||
<span>
|
||||
@@ -350,6 +371,13 @@
|
||||
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-embed-container" id="cal-booking-place-corpTest">
|
||||
<h3><a href="?only=ns:corpTest">CORP/COEP Blocking Test</a></h3>
|
||||
<div class="place">
|
||||
<div>It would forever load</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-embed-container" id="cal-booking-place-fifth">
|
||||
|
||||
@@ -58,6 +58,7 @@ if (only === "all" || only === "ns:default") {
|
||||
notes: "Test Meeting",
|
||||
guests: ["janedoe@example.com", "test@example.com"],
|
||||
theme: "dark",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
});
|
||||
Cal("on", {
|
||||
@@ -84,6 +85,7 @@ if (only === "all" || only === "ns:second") {
|
||||
id: "cal-booking-place-second-iframe",
|
||||
},
|
||||
theme: "auto",
|
||||
"flag.coep": "true",
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -111,6 +113,7 @@ if (only === "all" || only === "ns:third") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-third-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -161,6 +164,7 @@ if (only === "all" || only === "ns:fourth") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-fourth-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -188,6 +192,28 @@ if (only === "all" || only === "ns:fourth") {
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
if (only === "all" || only === "ns:corpTest") {
|
||||
Cal("init", "corpTest", {
|
||||
debug: true,
|
||||
origin: "http://localhost:3000",
|
||||
});
|
||||
Cal.ns.corpTest([
|
||||
"inline",
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
{
|
||||
elementOrSelector: "#cal-booking-place-corpTest .place",
|
||||
calLink: "pro",
|
||||
config: {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-corpTest-iframe",
|
||||
},
|
||||
"flag.coep": "false",
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (only === "all" || only === "ns:fifth") {
|
||||
Cal("init", "fifth", {
|
||||
debug: true,
|
||||
@@ -204,6 +230,7 @@ if (only === "all" || only === "ns:fifth") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-fifth-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -219,7 +246,7 @@ if (only === "all" || only === "prerender-test") {
|
||||
origin: origin,
|
||||
});
|
||||
Cal.ns.e2ePrerenderLightTheme("prerender", {
|
||||
calLink: "free/30min",
|
||||
calLink: "free/30min?flag.coep=true",
|
||||
type: "modal",
|
||||
});
|
||||
}
|
||||
@@ -250,6 +277,7 @@ if (only === "all" || only === "inline-routing-form") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-inline-routing-form-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -274,6 +302,7 @@ if (only === "all" || only === "hideEventTypeDetails") {
|
||||
iframeAttrs: {
|
||||
id: `cal-booking-place-${identifier}-iframe`,
|
||||
},
|
||||
"flag.coep": "true",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -404,6 +433,7 @@ if (only === "all" || only == "ns:floatingButton") {
|
||||
iframeAttrs: {
|
||||
id: "floatingtest",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
name: "John",
|
||||
email: "johndoe@gmail.com",
|
||||
notes: "Test Meeting",
|
||||
@@ -431,6 +461,7 @@ if (only === "all" || only == "ns:monthView") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-monthView-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
layout: "month_view",
|
||||
},
|
||||
}
|
||||
@@ -459,6 +490,7 @@ if (only === "all" || only == "ns:weekView") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-weekView-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
layout: "week_view",
|
||||
},
|
||||
}
|
||||
@@ -487,6 +519,7 @@ if (only === "all" || only == "ns:columnView") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-columnView-iframe",
|
||||
},
|
||||
"flag.coep": "true",
|
||||
layout: "column_view",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -165,3 +165,13 @@ export async function installAppleCalendar(page: Page) {
|
||||
await page.waitForURL("/apps/apple-calendar");
|
||||
await page.click('[data-testid="install-app-button"]');
|
||||
}
|
||||
|
||||
export async function assertNoRequestIsBlocked(page: Page) {
|
||||
page.on("requestfailed", (request) => {
|
||||
const error = request.failure()?.errorText;
|
||||
// Identifies that the request is blocked by the browser due to COEP restrictions
|
||||
if (error?.includes("ERR_BLOCKED_BY_RESPONSE")) {
|
||||
throw new Error(`Request Blocked: ${request.url()}. Error: ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@ import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
import { bookFirstEvent, deleteAllBookingsByEmail, getEmbedIframe, todo } from "../lib/testUtils";
|
||||
import {
|
||||
assertNoRequestIsBlocked,
|
||||
bookFirstEvent,
|
||||
deleteAllBookingsByEmail,
|
||||
getEmbedIframe,
|
||||
todo,
|
||||
} from "../lib/testUtils";
|
||||
|
||||
test.describe("Inline Iframe", () => {
|
||||
test("Inline Iframe - Configured with Dark Theme", async ({
|
||||
test("Inline Iframe - Configured with Dark Theme. Do booking and verify that COEP/CORP headers are correctly set", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
@@ -24,10 +30,33 @@ test.describe("Inline Iframe", () => {
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
|
||||
assertNoRequestIsBlocked(page);
|
||||
|
||||
await bookFirstEvent("pro", embedIframe, page);
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
});
|
||||
|
||||
test("COEP flag if not enabled, embed is blocked", async ({ page, embeds }) => {
|
||||
const embedBlockedPromise = new Promise((resolve) => {
|
||||
page.on("requestfailed", (request) => {
|
||||
const error = request.failure()?.errorText;
|
||||
// Identifies that the request is blocked by the browser due to COEP restrictions
|
||||
if (error?.includes("ERR_BLOCKED_BY_RESPONSE")) {
|
||||
console.log("Request failed: ", request.url(), error);
|
||||
resolve(request.url().includes("/pro/embed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const calNamespace = "corpTest";
|
||||
await embeds.gotoPlayground({ calNamespace, url: `?only=ns:${calNamespace}` });
|
||||
|
||||
await embedBlockedPromise.then((isBlocked) => {
|
||||
expect(isBlocked).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
todo(
|
||||
"Ensure that on all pages - [user], [user]/[type], team/[slug], team/[slug]/book, UI styling works if these pages are directly linked in embed"
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ export type PrefillAndIframeAttrsConfig = Record<string, string | string[] | Rec
|
||||
|
||||
// TODO: It should have a dedicated prefill prop
|
||||
// prefill: {},
|
||||
"flag.coep"?: "true" | "false";
|
||||
|
||||
// TODO: Move layout and theme as nested props of ui as it makes it clear that these two can be configured using `ui` instruction as well any time.
|
||||
// ui: {layout; theme}
|
||||
|
||||
@@ -18,6 +18,12 @@ module.exports = defineConfig((configEnv) => {
|
||||
}),
|
||||
...(process.argv.includes("--https") ? [basicSsl()] : []),
|
||||
],
|
||||
server: {
|
||||
// Helps us to test that embed works with these headers
|
||||
headers: {
|
||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
|
||||
Reference in New Issue
Block a user