diff --git a/.vscode/launch.json b/.vscode/launch.json index 795f38dd3..918b3e6d8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,14 +1,27 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Web", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/next/dist/bin/next", - "localRoot": "${workspaceFolder}/apps/scandic-web", - "runtimeArgs": ["--inspect", "--openssl-legacy-provider"], - "skipFiles": ["/**"] - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Web", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/next/dist/bin/next", + "localRoot": "${workspaceFolder}/apps/scandic-web", + "runtimeArgs": ["--inspect", "--openssl-legacy-provider"], + "skipFiles": ["/**"] + }, + { + "name": "Partner-SAS", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/next/dist/bin/next", + "localRoot": "${workspaceFolder}/apps/partner-sas", + "env": { + "PORT": "3001", + "NEXT_PUBLIC_PORT": "3001" + }, + "runtimeArgs": ["--inspect", "--openssl-legacy-provider"], + "skipFiles": ["/**"] + } + ] } diff --git a/apps/partner-sas/app/[lang]/(auth)/logout/route.ts b/apps/partner-sas/app/[lang]/(auth)/logout/route.ts index c97f073a3..d1989eabc 100644 --- a/apps/partner-sas/app/[lang]/(auth)/logout/route.ts +++ b/apps/partner-sas/app/[lang]/(auth)/logout/route.ts @@ -3,7 +3,7 @@ import { type NextRequest, NextResponse } from "next/server" import { getPublicURL } from "@/server/utils" import { signOut } from "@/auth" -import { destroySession } from "@/auth/scandic/session" +import { destroySocialSession } from "@/auth/scandic/session" import type { Lang } from "@scandic-hotels/common/constants/language" @@ -17,7 +17,7 @@ export async function GET( await signOut({ redirectTo, redirect: false }) // Delete scandic session once user logouts from sas - await destroySession() + await destroySocialSession() return NextResponse.redirect(redirectTo) } diff --git a/apps/partner-sas/app/api/web/auth/callback/curity/route.ts b/apps/partner-sas/app/api/web/auth/callback/curity/route.ts index 7776e5a3d..71ef26666 100644 --- a/apps/partner-sas/app/api/web/auth/callback/curity/route.ts +++ b/apps/partner-sas/app/api/web/auth/callback/curity/route.ts @@ -6,7 +6,7 @@ import { createLogger } from "@scandic-hotels/common/logger/createLogger" import { env } from "@/env/server" import { getToken } from "@/auth/scandic/getToken" -import { createSession } from "@/auth/scandic/session" +import { createSocialSession } from "@/auth/scandic/session" const logger = createLogger("curity-callback") export async function GET(req: NextRequest) { @@ -37,7 +37,7 @@ export async function GET(req: NextRequest) { code, }) - await createSession({ + await createSocialSession({ access_token: tokenResponse.access_token, refresh_token: tokenResponse.refresh_token, expires_in: tokenResponse.expires_in, diff --git a/apps/partner-sas/app/api/web/auth/scandic/logout/route.ts b/apps/partner-sas/app/api/web/auth/scandic/logout/route.ts index 0696ec661..8bef9b14c 100644 --- a/apps/partner-sas/app/api/web/auth/scandic/logout/route.ts +++ b/apps/partner-sas/app/api/web/auth/scandic/logout/route.ts @@ -2,10 +2,10 @@ import { type NextRequest } from "next/server" import { noContent } from "@/server/errors/next" -import { destroySession } from "@/auth/scandic/session" +import { destroySocialSession } from "@/auth/scandic/session" export async function GET(_req: NextRequest) { - await destroySession() + await destroySocialSession() // TODO: Should we call Scandic's logout endpoint? return noContent() diff --git a/apps/partner-sas/app/api/web/auth/scandic/refresh/route.ts b/apps/partner-sas/app/api/web/auth/scandic/refresh/route.ts index c6d9b4b85..799b22657 100644 --- a/apps/partner-sas/app/api/web/auth/scandic/refresh/route.ts +++ b/apps/partner-sas/app/api/web/auth/scandic/refresh/route.ts @@ -14,14 +14,14 @@ import { import { config } from "@/auth/scandic/config" import { endpoints } from "@/auth/scandic/endpoints" import { - createSession, - destroySession, - getSession, + createSocialSession, + destroySocialSession, + getSocialSession, } from "@/auth/scandic/session" const logger = createLogger("scandic/refresh") export async function POST(_req: NextRequest) { - const session = await getSession() + const session = await getSocialSession() if (!session) { return badRequest("No session found") } @@ -39,7 +39,7 @@ export async function POST(_req: NextRequest) { if (isResponseError(error)) { if (error.status === 400 && error.cause === "invalid_grant") { - await destroySession() + await destroySocialSession() return badRequest("invalid_grant") } @@ -55,7 +55,7 @@ export async function POST(_req: NextRequest) { got_new_access_token: newTokens.access_token !== session.access_token, }) - await createSession({ + await createSocialSession({ access_token: newTokens.access_token, refresh_token: newTokens.refresh_token ?? session.refresh_token, expires_in: newTokens.expires_in, diff --git a/apps/partner-sas/app/api/web/auth/scandic/session/route.ts b/apps/partner-sas/app/api/web/auth/scandic/session/route.ts index d43751747..6b5cc8faa 100644 --- a/apps/partner-sas/app/api/web/auth/scandic/session/route.ts +++ b/apps/partner-sas/app/api/web/auth/scandic/session/route.ts @@ -4,7 +4,7 @@ import { z } from "zod" import { dt } from "@scandic-hotels/common/dt" import { createLogger } from "@scandic-hotels/common/logger/createLogger" -import { getSession } from "@/auth/scandic/session" +import { getSocialSession } from "@/auth/scandic/session" const logger = createLogger("scandic/session") @@ -29,8 +29,8 @@ export type SocialSessionResponse = z.infer export async function GET(): Promise> { try { - const session = await getSession() - if (!session || !session.access_token) { + const session = await getSocialSession() + if (!session) { return createResponse({ status: "no_session", user: null }) } diff --git a/apps/partner-sas/auth/scandic/session.ts b/apps/partner-sas/auth/scandic/session.ts index 3382be7b0..963de5cf0 100644 --- a/apps/partner-sas/auth/scandic/session.ts +++ b/apps/partner-sas/auth/scandic/session.ts @@ -7,8 +7,8 @@ import { dt } from "@scandic-hotels/common/dt" import { env } from "@/env/server" -export async function getSession() { - return getIronSession<{ +async function internalGetSession() { + return await getIronSession<{ access_token: string refresh_token: string | undefined expires_at: string @@ -18,7 +18,17 @@ export async function getSession() { }) } -export async function createSession({ +export async function getSocialSession() { + const session = await internalGetSession() + + if (!session?.access_token) { + return null + } + + return session +} + +export async function createSocialSession({ access_token, refresh_token, expires_in, @@ -27,7 +37,7 @@ export async function createSession({ expires_in: number refresh_token?: string }) { - const session = await getSession() + const session = await internalGetSession() session.access_token = access_token session.refresh_token = refresh_token @@ -38,8 +48,8 @@ export async function createSession({ await session.save() } -export async function destroySession() { - const session = await getSession() +export async function destroySocialSession() { + const session = await internalGetSession() if (!session) return session.destroy() diff --git a/apps/partner-sas/lib/trpc/index.ts b/apps/partner-sas/lib/trpc/index.ts index b183cc88d..78a43078d 100644 --- a/apps/partner-sas/lib/trpc/index.ts +++ b/apps/partner-sas/lib/trpc/index.ts @@ -10,7 +10,7 @@ import { } from "@scandic-hotels/trpc/serverClient" import { auth } from "@/auth" -import { getSession } from "@/auth/scandic/session" +import { getSocialSession } from "@/auth/scandic/session" import type { Lang } from "@scandic-hotels/common/constants/language" @@ -29,7 +29,7 @@ export async function createAppContext() { return session }, getScandicUserToken: async () => { - const session = await getSession() + const session = await getSocialSession() return session?.access_token ?? null }, getUserPointsBalance: async () => { @@ -46,17 +46,17 @@ export async function createAppContext() { return euroBonusProfile.points.total }, getScandicUser: async () => { - const session = await getSession() + const session = await getSocialSession() + if (!session) return null - // The getSession will either return empty object or session object, hence we need to validate if the object is empty or not - if (!session?.access_token) return null - - return await getVerifiedUser({ + const user = await getVerifiedUser({ token: { expires_at: dt(session.expires_at).unix() * 1000, access_token: session.access_token, }, }) + + return user ?? null }, }) diff --git a/apps/scandic-web/lib/trpc/server.ts b/apps/scandic-web/lib/trpc/server.ts index 523638513..230a96990 100644 --- a/apps/scandic-web/lib/trpc/server.ts +++ b/apps/scandic-web/lib/trpc/server.ts @@ -73,12 +73,14 @@ export async function createAppContext() { const session = await getUserSession() if (!session) return null - return await getVerifiedUser({ + const user = await getVerifiedUser({ token: { expires_at: session.token.expires_at ?? 0, access_token: session.token.access_token, }, }) + + return user ?? null }, }) diff --git a/packages/common/constants/loginType.ts b/packages/common/constants/loginType.ts index b9439666d..b861bae6c 100644 --- a/packages/common/constants/loginType.ts +++ b/packages/common/constants/loginType.ts @@ -6,3 +6,13 @@ export enum LoginTypeEnum { "eurobonus" = "eurobonus", } export type LoginType = keyof typeof LoginTypeEnum +export type ScandicLoginType = Exclude +export type PartnerLoginType = Exclude + +export const partnerLoginTypes: PartnerLoginType[] = [LoginTypeEnum.eurobonus] + +export function isPartnerLoginType( + loginType: LoginType +): loginType is PartnerLoginType { + return partnerLoginTypes.includes(loginType as PartnerLoginType) +} diff --git a/packages/trpc/lib/routers/booking/mutation/create/index.ts b/packages/trpc/lib/routers/booking/mutation/create/index.ts index 77e924365..0e5eb1e21 100644 --- a/packages/trpc/lib/routers/booking/mutation/create/index.ts +++ b/packages/trpc/lib/routers/booking/mutation/create/index.ts @@ -24,6 +24,7 @@ export const create = safeProtectedServiceProcedure const createBookingCounter = createCounter("trpc.booking", "create") const user = await ctx.getScandicUser() + const metricsCreateBooking = createBookingCounter.init({ membershipNumber: user?.membershipNumber, language, @@ -36,7 +37,7 @@ export const create = safeProtectedServiceProcedure metricsCreateBooking.start() const headers = { - Authorization: `Bearer ${ctx.token || ctx.serviceToken}`, + Authorization: `Bearer ${ctx.token ?? ctx.serviceToken}`, } const apiResponse = await api.post( diff --git a/packages/trpc/lib/routers/booking/mutation/index.ts b/packages/trpc/lib/routers/booking/mutation/index.ts index 78b308938..2878db24f 100644 --- a/packages/trpc/lib/routers/booking/mutation/index.ts +++ b/packages/trpc/lib/routers/booking/mutation/index.ts @@ -5,7 +5,6 @@ import { router } from "../../.." import * as api from "../../../api" import { createRefIdPlugin } from "../../../plugins/refIdToConfirmationNumber" import { safeProtectedServiceProcedure } from "../../../procedures" -import { isValidSession } from "../../../utils/session" import { addPackageInput, cancelBookingsInput, @@ -14,7 +13,7 @@ import { updateBookingInput, } from "../input" import { bookingConfirmationSchema } from "../output" -import { cancelBooking, isPartnerLoggedInUser } from "../utils" +import { cancelBooking } from "../utils" import { createBookingSchema } from "./create/schema" import { create } from "./create" @@ -26,10 +25,7 @@ export const bookingMutationRouter = router({ priceChange: safeProtectedServiceProcedure .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -38,13 +34,14 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx }) { - const { confirmationNumber, token } = ctx + const { confirmationNumber } = ctx const priceChangeCounter = createCounter("trpc.booking", "price-change") const metricsPriceChange = priceChangeCounter.init({ confirmationNumber }) metricsPriceChange.start() + const token = ctx.token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${token}`, } @@ -76,10 +73,7 @@ export const bookingMutationRouter = router({ .input(cancelBookingsInput) .concat(refIdPlugin.toConfirmationNumbers) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -88,9 +82,10 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx, input }) { - const { confirmationNumbers, token } = ctx + const { confirmationNumbers } = ctx const { language } = input + const token = ctx.token ?? ctx.serviceToken const responses = await Promise.allSettled( confirmationNumbers.map((confirmationNumber) => cancelBooking(confirmationNumber, language, token) @@ -120,11 +115,7 @@ export const bookingMutationRouter = router({ .input(addPackageInput) .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken - + const token = await ctx.getScandicUserToken() return next({ ctx: { token, @@ -132,7 +123,7 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx, input }) { - const { confirmationNumber, token } = ctx + const { confirmationNumber } = ctx const { language, refId, ...body } = input const addPackageCounter = createCounter("trpc.booking", "package.add") @@ -143,6 +134,7 @@ export const bookingMutationRouter = router({ metricsAddPackage.start() + const token = ctx.token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${token}`, } @@ -176,10 +168,7 @@ export const bookingMutationRouter = router({ .input(guaranteeBookingInput) .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -188,7 +177,7 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx, input }) { - const { confirmationNumber, token } = ctx + const { confirmationNumber } = ctx const { language, refId, ...body } = input const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee") @@ -199,6 +188,7 @@ export const bookingMutationRouter = router({ metricsGuaranteeBooking.start() + const token = ctx.token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${token}`, } @@ -232,10 +222,7 @@ export const bookingMutationRouter = router({ .input(updateBookingInput) .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -244,7 +231,7 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx, input }) { - const { confirmationNumber, token } = ctx + const { confirmationNumber } = ctx const { language, refId, ...body } = input const updateBookingCounter = createCounter("trpc.booking", "update") @@ -254,7 +241,7 @@ export const bookingMutationRouter = router({ }) metricsUpdateBooking.start() - + const token = ctx.token ?? ctx.serviceToken const apiResponse = await api.put( api.endpoints.v1.Booking.booking(confirmationNumber), { @@ -287,10 +274,7 @@ export const bookingMutationRouter = router({ .input(removePackageInput) .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, next }) => { - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -299,7 +283,7 @@ export const bookingMutationRouter = router({ }) }) .mutation(async function ({ ctx, input }) { - const { confirmationNumber, token } = ctx + const { confirmationNumber } = ctx const { codes, language } = input const removePackageCounter = createCounter( @@ -314,6 +298,7 @@ export const bookingMutationRouter = router({ metricsRemovePackage.start() + const token = ctx.token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${token}`, } diff --git a/packages/trpc/lib/routers/booking/query.ts b/packages/trpc/lib/routers/booking/query.ts index 2e3f77a01..9f175f65e 100644 --- a/packages/trpc/lib/routers/booking/query.ts +++ b/packages/trpc/lib/routers/booking/query.ts @@ -21,7 +21,7 @@ import { getBookingStatusInput, getLinkedReservationsInput, } from "./input" -import { findBooking, getBooking, isPartnerLoggedInUser } from "./utils" +import { findBooking, getBooking } from "./utils" const refIdPlugin = createRefIdPlugin() @@ -31,10 +31,7 @@ export const bookingQueryRouter = router({ .concat(refIdPlugin.toConfirmationNumber) .use(async ({ ctx, input, next }) => { const lang = input.lang ?? ctx.lang - const token = - isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session) - ? ctx.session.token.access_token - : ctx.serviceToken + const token = await ctx.getScandicUserToken() return next({ ctx: { @@ -51,7 +48,11 @@ export const bookingQueryRouter = router({ metricsGetBooking.start() - const booking = await getBooking(confirmationNumber, lang, token) + const booking = await getBooking( + confirmationNumber, + lang, + token ?? serviceToken + ) if (!booking) { metricsGetBooking.dataError( diff --git a/packages/trpc/lib/routers/booking/utils.ts b/packages/trpc/lib/routers/booking/utils.ts index bac5360c7..3b159309c 100644 --- a/packages/trpc/lib/routers/booking/utils.ts +++ b/packages/trpc/lib/routers/booking/utils.ts @@ -1,4 +1,3 @@ -import { LoginTypeEnum } from "@scandic-hotels/common/constants/loginType" import { createCounter } from "@scandic-hotels/common/telemetry" import * as api from "../../api" @@ -8,7 +7,6 @@ import { createBookingSchema } from "./mutation/create/schema" import { bookingConfirmationSchema } from "./output" import type { Lang } from "@scandic-hotels/common/constants/language" -import type { Session } from "next-auth" export async function getBooking( confirmationNumber: string, @@ -159,9 +157,3 @@ export async function cancelBooking( return verifiedData.data } - -// ToDo - Update the function to return true for Scandic site and -// in case of Partner sites fetch the Scandic Curity token for linked user and service token for unlinked user -export function isPartnerLoggedInUser(session: Session) { - return session.token.loginType === LoginTypeEnum.eurobonus -} diff --git a/packages/trpc/lib/utils/session.ts b/packages/trpc/lib/utils/session.ts index f89362a83..0b283e585 100644 --- a/packages/trpc/lib/utils/session.ts +++ b/packages/trpc/lib/utils/session.ts @@ -9,6 +9,7 @@ export function isValidSession(session: Session | null): session is Session { if (!session) { return false } + if (session.error) { sessionLogger.error(`Session error: ${session.error}`) return false