feat(SW-3541): Do social login after login to SAS * feat(auth): wip social login via curity * Setup social login auth flow * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login * Added support for getting scandic tokens and refresh them * feat: Enhance social login and session management with auto-refresh and improved error handling * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login * wrap layout in suspense * revert app/layout.tsx * fix import * cleanup * merge * merge * dont pass client_secret in the url to curity * add state validation when doing social login through /authorize * remove debug logging Approved-by: Anton Gunnarsson
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
|
|
|
import { env } from "../../../../env/server"
|
|
import { protectedProcedure } from "../../../procedures"
|
|
|
|
import type { LoginType } from "@scandic-hotels/common/constants/loginType"
|
|
|
|
const outputSchema = z.object({
|
|
eurobonusNumber: z.string(),
|
|
firstName: z.string().optional(),
|
|
lastName: z.string().optional(),
|
|
linkStatus: z.enum(["LINKED", "PENDING", "UNLINKED"]),
|
|
isBlocked: z.boolean(),
|
|
enrollmentDate: z.string(),
|
|
birthdate: z.string(),
|
|
mobileNumber: z.string(),
|
|
email: z.string().email(),
|
|
points: z.object({
|
|
balances: z.array(
|
|
z.object({
|
|
expirationDate: z.string(),
|
|
points: z.number(),
|
|
})
|
|
),
|
|
total: z.number(),
|
|
}),
|
|
tier: z.string(),
|
|
tierStartDate: z.string(),
|
|
/* `null` if customer is (i.) Lifetime Gold and tier is Gold or (ii.) tier is Basic. If customer is Lifetime Gold and tier is above Gold (e.g., Diamond), there will be a tier end date */
|
|
tierEndDate: z.string().nullable(),
|
|
lifeTimeGold: z.boolean(),
|
|
tierBoostRequestedByScandic: z.boolean(),
|
|
whoBoosted: z
|
|
.enum(["NO-BOOST", "SAS", "SCANDIC", "UNKNOWN"])
|
|
.default("UNKNOWN")
|
|
.catch((ctx) => {
|
|
sasLogger.warn(`Unknown whoBoosted value received: ${ctx.input}`)
|
|
return "UNKNOWN"
|
|
}),
|
|
tierBoostDescription: z.string().optional(),
|
|
tierMatchStatus: z
|
|
.enum(["MATCHED", "FAILED", "PENDING", "UNMATCHED"])
|
|
.optional(),
|
|
})
|
|
|
|
const sasLogger = createLogger("SAS")
|
|
const url = new URL("/api/scandic-partnership/v1/profile", env.SAS_API_ENDPOINT)
|
|
|
|
const requiredLoginType: LoginType[] = ["sas"]
|
|
|
|
export const getEuroBonusProfile = protectedProcedure
|
|
.output(outputSchema)
|
|
.query(async function ({ ctx }) {
|
|
return await getEuroBonusProfileData({
|
|
accessToken: ctx.session.token.access_token,
|
|
loginType: ctx.session.token.loginType,
|
|
})
|
|
})
|
|
|
|
export async function getEuroBonusProfileData({
|
|
accessToken,
|
|
loginType,
|
|
}: {
|
|
loginType: LoginType
|
|
accessToken: string
|
|
}) {
|
|
if (!accessToken) {
|
|
throw new Error("Access token is required to fetch EuroBonus profile")
|
|
}
|
|
|
|
if (!requiredLoginType.includes(loginType)) {
|
|
throw new Error(
|
|
`Failed to fetch EuroBonus profile, expected loginType to be "${requiredLoginType}" but was "${loginType}"`
|
|
)
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Ocp-Apim-Subscription-Key": env.SAS_OCP_APIM,
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
sasLogger.error(
|
|
`Failed to get EuroBonus profile, status: ${response.status}, statusText: ${response.statusText}`
|
|
)
|
|
throw new Error("Failed to fetch EuroBonus profile", {
|
|
cause: { status: response.status, statusText: response.statusText },
|
|
})
|
|
}
|
|
|
|
const responseJson = await response.json()
|
|
const data = outputSchema.safeParse(responseJson)
|
|
if (!data.success) {
|
|
sasLogger.error(
|
|
`Failed to parse EuroBonus profile, cause: ${data.error.cause}, message: ${data.error.message}`
|
|
)
|
|
throw new Error(`Failed to parse EuroBonus profile: ${data.error.message}`)
|
|
}
|
|
|
|
return data.data
|
|
}
|