Merged in feat/SW-3549-handle-unlinked-account (pull request #3019)

fix(SW-3549): update social session management functions for clarity and consistency

* refactor(SW-3549): rename session management functions for clarity and consistency

* merge


Approved-by: Hrishikesh Vaipurkar
This commit is contained in:
Joakim Jäderberg
2025-10-28 09:51:30 +00:00
parent 4a6c64f921
commit a4f1a55e56
15 changed files with 105 additions and 90 deletions

37
.vscode/launch.json vendored
View File

@@ -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": ["<node_internals>/**"]
}
]
"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": ["<node_internals>/**"]
},
{
"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": ["<node_internals>/**"]
}
]
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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()

View File

@@ -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,

View File

@@ -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<typeof socialSessionResponseSchema>
export async function GET(): Promise<NextResponse<SocialSessionResponse>> {
try {
const session = await getSession()
if (!session || !session.access_token) {
const session = await getSocialSession()
if (!session) {
return createResponse({ status: "no_session", user: null })
}

View File

@@ -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()

View File

@@ -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
},
})

View File

@@ -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
},
})

View File

@@ -6,3 +6,13 @@ export enum LoginTypeEnum {
"eurobonus" = "eurobonus",
}
export type LoginType = keyof typeof LoginTypeEnum
export type ScandicLoginType = Exclude<LoginType, "eurobonus">
export type PartnerLoginType = Exclude<LoginType, ScandicLoginType>
export const partnerLoginTypes: PartnerLoginType[] = [LoginTypeEnum.eurobonus]
export function isPartnerLoginType(
loginType: LoginType
): loginType is PartnerLoginType {
return partnerLoginTypes.includes(loginType as PartnerLoginType)
}

View File

@@ -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(

View File

@@ -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}`,
}

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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