diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index 4a6a934e8d..c5637246d5 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -75,6 +75,26 @@ test.describe("user with a special character in the username", () => { const response = await page.goto(`/${user.username}/30-min`); expect(response?.status()).not.toBe(404); }); + + test("Should not throw 500 when redirecting user to his/her only event-type page even if username contains special characters", async ({ + page, + users, + }) => { + const benny = await users.create({ + username: "ßenny", // ß is a special character + eventTypes: [ + { + title: "15 min", + slug: "15-min", + length: 15, + }, + ], + overrideDefaultEventTypes: true, + }); + // This redirects to /[user]/[type] because this user has only 1 event-type + const response = await page.goto(`/${benny.username}`); + expect(response?.status()).not.toBe(500); + }); }); test.describe("free user", () => { diff --git a/apps/web/playwright/dynamic-booking-pages.e2e.ts b/apps/web/playwright/dynamic-booking-pages.e2e.ts index 137e7b4a1f..d67df43f6a 100644 --- a/apps/web/playwright/dynamic-booking-pages.e2e.ts +++ b/apps/web/playwright/dynamic-booking-pages.e2e.ts @@ -143,4 +143,33 @@ test.describe("Organization:", () => { } ); }); + + test("dynamic booking for usernames with special characters", async ({ page, users, orgs }) => { + const org = await orgs.create({ + name: "TestOrg", + }); + + const user1 = await users.create({ + organizationId: org.id, + name: "User 1", + roleInOrganization: MembershipRole.MEMBER, + }); + + const user2 = await users.create({ + username: "ßenny-Joo", // ß is a special character + organizationId: org.id, + name: "User 2", + roleInOrganization: MembershipRole.MEMBER, + }); + await doOnOrgDomain( + { + orgSlug: org.slug, + page, + }, + async () => { + const response = await page.goto(`/${user1.username}+${user2.username}`); + expect(response?.status()).not.toBe(500); + } + ); + }); }); diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts index a57d38e7cf..858b55da09 100644 --- a/apps/web/playwright/fixtures/users.ts +++ b/apps/web/playwright/fixtures/users.ts @@ -268,6 +268,7 @@ export const createUsersFixture = ( opts?: | (CustomUserOpts & { organizationId?: number | null; + overrideDefaultEventTypes?: boolean; }) | null, scenario: { @@ -300,18 +301,20 @@ export const createUsersFixture = ( }, }); - let defaultEventTypes: SupportedTestEventTypes[] = [ - { title: "30 min", slug: "30-min", length: 30 }, - { title: "Paid", slug: "paid", length: 30, price: 1000 }, - { title: "Opt in", slug: "opt-in", requiresConfirmation: true, length: 30 }, - { title: "Seated", slug: "seated", seatsPerTimeSlot: 2, length: 30 }, - { - title: "Multiple duration", - slug: "multiple-duration", - length: 30, - metadata: { multipleDuration: [30, 60, 90] }, - }, - ]; + let defaultEventTypes: SupportedTestEventTypes[] = opts?.overrideDefaultEventTypes + ? [] + : [ + { title: "30 min", slug: "30-min", length: 30 }, + { title: "Paid", slug: "paid", length: 30, price: 1000 }, + { title: "Opt in", slug: "opt-in", requiresConfirmation: true, length: 30 }, + { title: "Seated", slug: "seated", seatsPerTimeSlot: 2, length: 30 }, + { + title: "Multiple duration", + slug: "multiple-duration", + length: 30, + metadata: { multipleDuration: [30, 60, 90] }, + }, + ]; if (opts?.eventTypes) defaultEventTypes = defaultEventTypes.concat(opts.eventTypes); for (const eventTypeData of defaultEventTypes) { diff --git a/apps/web/server/lib/[user]/getServerSideProps.ts b/apps/web/server/lib/[user]/getServerSideProps.ts index d59545fb01..9dfd12a818 100644 --- a/apps/web/server/lib/[user]/getServerSideProps.ts +++ b/apps/web/server/lib/[user]/getServerSideProps.ts @@ -106,7 +106,7 @@ export const getServerSideProps: GetServerSideProps = async (cont log.debug(safeStringify({ usersInOrgContext, isValidOrgDomain, currentOrgDomain, isDynamicGroup })); if (isDynamicGroup) { - const destinationUrl = `/${usernameList.join("+")}/dynamic`; + const destinationUrl = encodeURI(`/${usernameList.join("+")}/dynamic`); // EXAMPLE - context.params: { orgSlug: 'acme', user: 'member0+owner1' } // EXAMPLE - context.query: { redirect: 'undefined', orgRedirection: 'undefined', user: 'member0+owner1' } @@ -166,7 +166,7 @@ export const getServerSideProps: GetServerSideProps = async (cont return { redirect: { permanent: false, - destination: `${urlDestination}?${urlQuery}`, + destination: `${encodeURI(urlDestination)}?${urlQuery}`, }, }; }