Skip to content

Commit

Permalink
fix: Encode usernames in booking url to UTF-8 to prevent 500 throws (#…
Browse files Browse the repository at this point in the history
…19475)

* fix: Encode usernames in booking url to UTF-8 to prevent throws

* add dynamic booking page e2e

* add 1 more test
  • Loading branch information
hbjORbj authored Feb 25, 2025
1 parent d1a9af8 commit acf270b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 14 deletions.
20 changes: 20 additions & 0 deletions apps/web/playwright/booking-pages.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
29 changes: 29 additions & 0 deletions apps/web/playwright/dynamic-booking-pages.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
);
});
});
27 changes: 15 additions & 12 deletions apps/web/playwright/fixtures/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export const createUsersFixture = (
opts?:
| (CustomUserOpts & {
organizationId?: number | null;
overrideDefaultEventTypes?: boolean;
})
| null,
scenario: {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/server/lib/[user]/getServerSideProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = 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' }
Expand Down Expand Up @@ -166,7 +166,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
return {
redirect: {
permanent: false,
destination: `${urlDestination}?${urlQuery}`,
destination: `${encodeURI(urlDestination)}?${urlQuery}`,
},
};
}
Expand Down

0 comments on commit acf270b

Please sign in to comment.