diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx index 8ca50a18f..3ae3d8c63 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx @@ -26,7 +26,7 @@ export default function ManageBooking({ booking }: ManageBookingProps) { lastName, confirmationNumber, }).toString() - document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` + document.cookie = `bv=${JSON.stringify(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` }, [confirmationNumber, email, firstName, lastName]) const myStayURL = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}` diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Promos/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Promos/index.tsx index 92f4725bb..8fc50b5e6 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Promos/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Promos/index.tsx @@ -25,7 +25,7 @@ export default function Promos({ booking }: PromosProps) { lastName, confirmationNumber, }).toString() - document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` + document.cookie = `bv=${JSON.stringify(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` }, [confirmationNumber, email, firstName, lastName]) const myStayURL = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}` diff --git a/apps/scandic-web/components/HotelReservation/FindMyBooking/AdditionalInfoForm.tsx b/apps/scandic-web/components/HotelReservation/FindMyBooking/AdditionalInfoForm.tsx index 738c8eab0..067cb2621 100644 --- a/apps/scandic-web/components/HotelReservation/FindMyBooking/AdditionalInfoForm.tsx +++ b/apps/scandic-web/components/HotelReservation/FindMyBooking/AdditionalInfoForm.tsx @@ -18,6 +18,13 @@ import { import styles from "./findMyBooking.module.css" +export type AdditionalInfoCookieValue = { + firstName: string + email: string + confirmationNumber: string + lastName: string +} + export default function AdditionalInfoForm({ confirmationNumber, lastName, @@ -36,12 +43,12 @@ export default function AdditionalInfoForm({ function onSubmit() { const values = form.getValues() - const value = new URLSearchParams({ + const value: AdditionalInfoCookieValue = { ...values, confirmationNumber, lastName, - }).toString() - document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` + } + document.cookie = `bv=${JSON.stringify(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` router.refresh() } diff --git a/apps/scandic-web/components/HotelReservation/FindMyBooking/index.tsx b/apps/scandic-web/components/HotelReservation/FindMyBooking/index.tsx index 59f83ae7c..cace889ad 100644 --- a/apps/scandic-web/components/HotelReservation/FindMyBooking/index.tsx +++ b/apps/scandic-web/components/HotelReservation/FindMyBooking/index.tsx @@ -24,6 +24,8 @@ import { type FindMyBookingFormSchema, findMyBookingFormSchema } from "./schema" import styles from "./findMyBooking.module.css" +import type { AdditionalInfoCookieValue } from "./AdditionalInfoForm" + export default function FindMyBooking() { const router = useRouter() const intl = useIntl() @@ -44,8 +46,10 @@ export default function FindMyBooking() { const update = trpc.booking.createRefId.useMutation({ onSuccess: (result) => { const values = form.getValues() - const value = new URLSearchParams(values).toString() - document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` + const value: AdditionalInfoCookieValue = { + ...values, + } + document.cookie = `bv=${JSON.stringify(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict` router.push(`${myStay[lang]}?RefId=${encodeURIComponent(result.refId)}`) }, onError: (error) => { diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx index 9484d4df5..0a07a51fa 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx @@ -15,10 +15,12 @@ import { getProfileSafely, } from "@/lib/trpc/memoizedRequests" +import AdditionalInfoForm, { + type AdditionalInfoCookieValue, +} from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" import { getIntl } from "@/i18n" import { isLoggedInUser } from "@/utils/isLoggedInUser" -import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm" import accessBooking, { ACCESS_GRANTED, ERROR_BAD_REQUEST, @@ -48,9 +50,9 @@ export async function Receipt({ refId }: { refId: string }) { if (isLoggedIn) { bookingConfirmation = await getBookingConfirmation(refId) } else if (bv) { - const params = new URLSearchParams(bv) - const firstName = params.get("firstName") - const email = params.get("email") + const values = JSON.parse(bv) as AdditionalInfoCookieValue + const firstName = values.firstName + const email = values.email if (firstName && email) { bookingConfirmation = await findBooking( diff --git a/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.test.ts b/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.test.ts index a87553aa6..fd9d8dba4 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.test.ts +++ b/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.test.ts @@ -11,6 +11,7 @@ import accessBooking, { import type { Guest } from "@scandic-hotels/trpc/routers/booking/output" import type { SafeUser } from "@/types/user" +import type { AdditionalInfoCookieValue } from "../FindMyBooking/AdditionalInfoForm" describe("Access booking", () => { describe("for logged in booking", () => { @@ -43,90 +44,90 @@ describe("Access booking", () => { describe("for anonymous booking", () => { it("should enable access if all is provided", () => { - const cookieString = new URLSearchParams({ + const cookie: AdditionalInfoCookieValue = { confirmationNumber: "123456789", firstName: "Anonymous", lastName: "Booking", email: "logged+out@scandichotels.com", - }).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ACCESS_GRANTED - ) + } + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ACCESS_GRANTED) }) it("should enable access if all is provided and be case-insensitive for first name", () => { - const cookieString = new URLSearchParams({ + const cookie: AdditionalInfoCookieValue = { confirmationNumber: "123456789", firstName: "AnOnYmOuS", lastName: "Booking", email: "logged+out@scandichotels.com", - }).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ACCESS_GRANTED - ) + } + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ACCESS_GRANTED) }) it("should enable access if all is provided and be case-insensitive for last name", () => { - const cookieString = new URLSearchParams({ + const cookie: AdditionalInfoCookieValue = { confirmationNumber: "123456789", firstName: "Anonymous", lastName: "Booking", email: "logged+out@scandichotels.com", - }).toString() - expect(accessBooking(loggedOutGuest, "BoOkInG", null, cookieString)).toBe( - ACCESS_GRANTED - ) + } + expect( + accessBooking(loggedOutGuest, "BoOkInG", null, JSON.stringify(cookie)) + ).toBe(ACCESS_GRANTED) }) it("should enable access if all is provided and be case-insensitive for email", () => { - const cookieString = new URLSearchParams({ + const cookie: AdditionalInfoCookieValue = { confirmationNumber: "123456789", firstName: "Anonymous", lastName: "Booking", email: "LOGGED+out@scandichotels.com", - }).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ACCESS_GRANTED - ) + } + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ACCESS_GRANTED) }) it("should prompt logout if user is logged in", () => { - const cookieString = new URLSearchParams({ + const cookie: AdditionalInfoCookieValue = { confirmationNumber: "123456789", firstName: "Anonymous", lastName: "Booking", email: "logged+out@scandichotels.com", - }).toString() + } expect( accessBooking( loggedOutGuest, "Booking", authenticatedUser, - cookieString + JSON.stringify(cookie) ) ).toBe(ERROR_FORBIDDEN) }) it("should prompt for more if first name is missing", () => { - const cookieString = new URLSearchParams({ + const cookie: Partial = { confirmationNumber: "123456789", lastName: "Booking", email: "logged+out@scandichotels.com", - }).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ERROR_BAD_REQUEST - ) + } + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ERROR_BAD_REQUEST) }) it("should prompt for more if email is missing", () => { - const cookieString = new URLSearchParams({ + const cookie: Partial = { confirmationNumber: "123456789", firstName: "Anonymous", lastName: "Booking", - }).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ERROR_BAD_REQUEST - ) + } + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ERROR_BAD_REQUEST) }) it("should prompt for more if cookie is invalid", () => { - const cookieString = new URLSearchParams({}).toString() - expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe( - ERROR_BAD_REQUEST - ) + const cookie = {} + expect( + accessBooking(loggedOutGuest, "Booking", null, JSON.stringify(cookie)) + ).toBe(ERROR_BAD_REQUEST) }) it("should deny access if refId mismatch", () => { expect(accessBooking(loggedOutGuest, "NotBooking", null)).toBe( diff --git a/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.ts b/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.ts index 9eb71defd..e832294e2 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.ts +++ b/apps/scandic-web/components/HotelReservation/MyStay/accessBooking.ts @@ -1,6 +1,7 @@ import type { Guest } from "@scandic-hotels/trpc/routers/booking/output" import type { SafeUser } from "@/types/user" +import type { AdditionalInfoCookieValue } from "../FindMyBooking/AdditionalInfoForm" export { ACCESS_GRANTED, @@ -36,13 +37,21 @@ function accessBooking( if (guest.lastName?.toLowerCase() === lastName.toLowerCase()) { if (user) { - return ERROR_FORBIDDEN - } else { - const params = new URLSearchParams(cookie) if ( - params.get("firstName")?.toLowerCase() === - guest.firstName?.toLowerCase() && - params.get("email")?.toLowerCase() === guest.email?.toLowerCase() + user.firstName.toLowerCase() === guest.firstName?.toLowerCase() && + user.email.toLowerCase() === guest.email?.toLowerCase() + ) { + return ACCESS_GRANTED + } else { + return ERROR_FORBIDDEN + } + } else { + const values = + cookie && (JSON.parse(cookie) as Partial) + if ( + values && + values.firstName?.toLowerCase() === guest.firstName?.toLowerCase() && + values.email?.toLowerCase() === guest.email?.toLowerCase() ) { return ACCESS_GRANTED } else { diff --git a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/index.tsx index 585bb3d81..ee27d065e 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/index.tsx @@ -2,6 +2,7 @@ import { cookies } from "next/headers" import { notFound } from "next/navigation" import { dt } from "@scandic-hotels/common/dt" +import { logger } from "@scandic-hotels/common/logger" import * as maskValue from "@scandic-hotels/common/utils/maskValue" import { Typography } from "@scandic-hotels/design-system/Typography" import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast" @@ -18,7 +19,9 @@ import { getSavedPaymentCardsSafely, } from "@/lib/trpc/memoizedRequests" -import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" +import AdditionalInfoForm, { + type AdditionalInfoCookieValue, +} from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" import accessBooking, { ACCESS_GRANTED, ERROR_BAD_REQUEST, @@ -68,9 +71,10 @@ export default async function MyStay(props: { if (isLoggedIn) { bookingConfirmation = await getBookingConfirmation(refId) } else if (bv) { - const params = new URLSearchParams(bv) - const firstName = params.get("firstName") - const email = params.get("email") + logger.info(`MyStay: bv`, bv) + const values = JSON.parse(bv) as AdditionalInfoCookieValue + const firstName = values.firstName + const email = values.email if (firstName && email) { bookingConfirmation = await findBooking( diff --git a/packages/trpc/lib/routers/booking/query.ts b/packages/trpc/lib/routers/booking/query.ts index 931b74e26..d66352b3e 100644 --- a/packages/trpc/lib/routers/booking/query.ts +++ b/packages/trpc/lib/routers/booking/query.ts @@ -11,6 +11,7 @@ import { import { getHotel } from "../../routers/hotels/utils" import { toApiLang } from "../../utils" import { encrypt } from "../../utils/encryption" +import { isValidSession } from "../../utils/session" import { getBookedHotelRoom } from "./helpers" import { createRefIdInput, @@ -30,22 +31,26 @@ export const bookingQueryRouter = router({ .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, input, next }) => { const lang = input.lang ?? ctx.lang + const token = isValidSession(ctx.session) + ? ctx.session.token.access_token + : ctx.serviceToken return next({ ctx: { lang, + token, }, }) }) .query(async function ({ ctx }) { - const { confirmationNumber, lang, serviceToken } = ctx + const { confirmationNumber, lang, token, serviceToken } = ctx const getBookingCounter = createCounter("trpc.booking", "get") const metricsGetBooking = getBookingCounter.init({ confirmationNumber }) metricsGetBooking.start() - const booking = await getBooking(confirmationNumber, lang, serviceToken) + const booking = await getBooking(confirmationNumber, lang, token) if (!booking) { metricsGetBooking.dataError( @@ -88,10 +93,24 @@ export const bookingQueryRouter = router({ }), findBooking: safeProtectedServiceProcedure .input(findBookingInput) + .use(async ({ ctx, input, next }) => { + const lang = input.lang ?? ctx.lang + const token = isValidSession(ctx.session) + ? ctx.session.token.access_token + : ctx.serviceToken + + return next({ + ctx: { + lang, + token, + }, + }) + }) .query(async function ({ ctx, input: { confirmationNumber, lastName, firstName, email }, }) { + const { lang, token, serviceToken } = ctx const findBookingCounter = createCounter("trpc.booking", "findBooking") const metricsFindBooking = findBookingCounter.init({ confirmationNumber }) @@ -99,8 +118,8 @@ export const bookingQueryRouter = router({ const booking = await findBooking( confirmationNumber, - ctx.lang, - ctx.serviceToken, + lang, + token, lastName, firstName, email @@ -118,9 +137,9 @@ export const bookingQueryRouter = router({ { hotelId: booking.hotelId, isCardOnlyPayment: false, - language: ctx.lang, + language: lang, }, - ctx.serviceToken + serviceToken ) if (!hotelData) { @@ -150,14 +169,19 @@ export const bookingQueryRouter = router({ .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, input, next }) => { const lang = input.lang ?? ctx.lang + const token = isValidSession(ctx.session) + ? ctx.session.token.access_token + : ctx.serviceToken + return next({ ctx: { lang, + token, }, }) }) .query(async function ({ ctx }) { - const { confirmationNumber, lang, serviceToken } = ctx + const { confirmationNumber, lang, token } = ctx const getLinkedReservationsCounter = createCounter( "trpc.booking", @@ -169,7 +193,7 @@ export const bookingQueryRouter = router({ metricsGetLinkedReservations.start() - const booking = await getBooking(confirmationNumber, lang, serviceToken) + const booking = await getBooking(confirmationNumber, lang, token) if (!booking) { return [] @@ -177,7 +201,7 @@ export const bookingQueryRouter = router({ const linkedReservationsResults = await Promise.allSettled( booking.linkedReservations.map((linkedReservation) => - getBooking(linkedReservation.confirmationNumber, lang, serviceToken) + getBooking(linkedReservation.confirmationNumber, lang, token) ) )