Merged in feat/SW-3461-setup-auth-with-sas-eurobonus (pull request #2825)
Feat/SW-3461 setup auth with sas eurobonus * feat(SW-3461): Setup auth for sas eurobonus * . * feat: setup auth towards SAS * Fix auth via SAS and add logout route * . * merge * auth via SAS * fix powered by scandic logo * Merge branch 'master' of bitbucket.org:scandic-swap/web into feat/SW-3461-setup-auth-with-sas-eurobonus * Include access_token in jwt after successful login * merge Approved-by: Anton Gunnarsson
This commit is contained in:
100
apps/partner-sas/app/[lang]/(auth)/login/route.ts
Normal file
100
apps/partner-sas/app/[lang]/(auth)/login/route.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { AuthError } from "next-auth"
|
||||
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
|
||||
import { internalServerError } from "@/server/errors/next"
|
||||
import { getPublicURL } from "@/server/utils"
|
||||
|
||||
import { signIn } from "@/auth"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ lang: Lang }> }
|
||||
) {
|
||||
const contextParams = await context.params
|
||||
const publicURL = getPublicURL(request)
|
||||
|
||||
let redirectHeaders: Headers | undefined = undefined
|
||||
let redirectTo: string
|
||||
|
||||
const redirectToCookieValue = request.cookies.get("redirectTo")?.value // Cookie gets set by authRequired middleware
|
||||
const redirectToSearchParamValue =
|
||||
request.nextUrl.searchParams.get("redirectTo")
|
||||
const redirectToFallback = "/"
|
||||
|
||||
logger.debug(`[login] redirectTo cookie value: ${redirectToCookieValue}`)
|
||||
logger.debug(
|
||||
`[login] redirectTo search param value: ${redirectToSearchParamValue}`
|
||||
)
|
||||
|
||||
redirectTo =
|
||||
redirectToCookieValue || redirectToSearchParamValue || redirectToFallback
|
||||
|
||||
// Make relative URL to absolute URL
|
||||
if (redirectTo.startsWith("/")) {
|
||||
logger.debug(`[login] make redirectTo absolute, from ${redirectTo}`)
|
||||
redirectTo = new URL(redirectTo, publicURL).href
|
||||
logger.debug(`[login] make redirectTo absolute, to ${redirectTo}`)
|
||||
}
|
||||
|
||||
// Clean up cookie from authRequired middleware
|
||||
redirectHeaders = new Headers()
|
||||
redirectHeaders.append(
|
||||
"set-cookie",
|
||||
"redirectTo=; Expires=Thu, 01 Jan 1970 00:00:00 UTC; Path=/; HttpOnly; SameSite=Lax"
|
||||
)
|
||||
|
||||
const SAS_LANGUAGE_MAP: Record<Lang, string> = {
|
||||
no: "nb",
|
||||
sv: "sv",
|
||||
fi: "fi",
|
||||
da: "da",
|
||||
en: "en",
|
||||
de: "de",
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug(`[login] final redirectUrl: ${redirectTo}`)
|
||||
|
||||
/** Record<string, any> is next-auth typings */
|
||||
const params = {
|
||||
ui_locales: SAS_LANGUAGE_MAP[contextParams.lang],
|
||||
scope: ["openid", "profile", "email"].join(" "),
|
||||
} satisfies Record<string, string>
|
||||
|
||||
/**
|
||||
* Passing `redirect: false` to `signIn` will return the URL instead of
|
||||
* automatically redirecting to it inside of `signIn`.
|
||||
* https://github.com/nextauthjs/next-auth/blob/3c035ec/packages/next-auth/src/lib/actions.ts#L76
|
||||
*/
|
||||
const redirectUrl = await signIn(
|
||||
"sas",
|
||||
{
|
||||
redirectTo,
|
||||
redirect: false,
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (redirectUrl) {
|
||||
const redirectOpts = {
|
||||
headers: redirectHeaders,
|
||||
}
|
||||
logger.debug(`[login] redirecting to: ${redirectUrl}`, redirectOpts)
|
||||
return NextResponse.redirect(redirectUrl, redirectOpts)
|
||||
} else {
|
||||
logger.error(`[login] missing redirectUrl reponse from signIn()`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AuthError) {
|
||||
logger.error("signInAuthError", { signInAuthError: error })
|
||||
} else {
|
||||
logger.error("signInError", { signInError: error })
|
||||
}
|
||||
}
|
||||
|
||||
return internalServerError()
|
||||
}
|
||||
17
apps/partner-sas/app/[lang]/(auth)/logout/route.ts
Normal file
17
apps/partner-sas/app/[lang]/(auth)/logout/route.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { type NextRequest } from "next/server"
|
||||
|
||||
import { getPublicURL } from "@/server/utils"
|
||||
|
||||
import { signOut } from "@/auth"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
_context: { params: Promise<{ lang: Lang }> }
|
||||
) {
|
||||
const publicURL = getPublicURL(request)
|
||||
|
||||
const redirectTo: string = publicURL
|
||||
await signOut({ redirectTo, redirect: true })
|
||||
}
|
||||
@@ -6,10 +6,9 @@ import "../../globals.css"
|
||||
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
import Script from "next/script"
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
|
||||
import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig"
|
||||
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
||||
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
||||
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
|
||||
@@ -25,20 +24,9 @@ import { getMessages } from "@/i18n"
|
||||
import ClientIntlProvider from "@/i18n/Provider"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { BookingFlowProviders } from "../../components/BookingFlowProviders"
|
||||
import { Footer } from "../../components/Footer/Footer"
|
||||
import { Header } from "../../components/Header/Header"
|
||||
import {
|
||||
trackAccordionItemOpen,
|
||||
trackBedSelection,
|
||||
trackBookingSearchClick,
|
||||
trackBreakfastSelection,
|
||||
trackGenericEvent,
|
||||
trackGlaSaveCardAttempt,
|
||||
trackLoginClick,
|
||||
trackOpenSidePeek,
|
||||
trackPaymentEvent,
|
||||
trackUpdatePaymentMethod,
|
||||
} from "../utils/tracking"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
@@ -82,35 +70,18 @@ export default async function RootLayout(props: RootLayoutProps) {
|
||||
</head>
|
||||
<body className="scandic">
|
||||
<div className="root">
|
||||
<ClientIntlProvider
|
||||
defaultLocale={Lang.en}
|
||||
locale={parsedLanguage}
|
||||
messages={messages}
|
||||
>
|
||||
<NuqsAdapter>
|
||||
<TrpcProvider>
|
||||
<RACRouterProvider>
|
||||
<BookingFlowConfig config={bookingFlowConfig}>
|
||||
<BookingFlowContextProvider
|
||||
data={{
|
||||
// TODO
|
||||
isLoggedIn: false,
|
||||
}}
|
||||
>
|
||||
<BookingFlowTrackingProvider
|
||||
trackingFunctions={{
|
||||
trackBookingSearchClick,
|
||||
trackAccordionItemOpen,
|
||||
trackOpenSidePeek,
|
||||
trackGenericEvent,
|
||||
trackGlaSaveCardAttempt,
|
||||
trackLoginClick,
|
||||
trackPaymentEvent,
|
||||
trackUpdatePaymentMethod,
|
||||
trackBreakfastSelection,
|
||||
trackBedSelection,
|
||||
}}
|
||||
>
|
||||
<SessionProvider basePath="/api/web/auth">
|
||||
<ClientIntlProvider
|
||||
defaultLocale={Lang.en}
|
||||
locale={parsedLanguage}
|
||||
messages={messages}
|
||||
>
|
||||
<NuqsAdapter>
|
||||
{/* TODO handle onError */}
|
||||
<TrpcProvider>
|
||||
<RACRouterProvider>
|
||||
<BookingFlowConfig config={bookingFlowConfig}>
|
||||
<BookingFlowProviders>
|
||||
<SiteWideAlert />
|
||||
<Header />
|
||||
{props.bookingwidget}
|
||||
@@ -119,13 +90,13 @@ export default async function RootLayout(props: RootLayoutProps) {
|
||||
<ToastHandler />
|
||||
<CookieBotConsent />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</BookingFlowTrackingProvider>
|
||||
</BookingFlowContextProvider>
|
||||
</BookingFlowConfig>
|
||||
</RACRouterProvider>
|
||||
</TrpcProvider>
|
||||
</NuqsAdapter>
|
||||
</ClientIntlProvider>
|
||||
</BookingFlowProviders>
|
||||
</BookingFlowConfig>
|
||||
</RACRouterProvider>
|
||||
</TrpcProvider>
|
||||
</NuqsAdapter>
|
||||
</ClientIntlProvider>
|
||||
</SessionProvider>
|
||||
</div>
|
||||
|
||||
<Script
|
||||
|
||||
11
apps/partner-sas/app/api/web/auth/[...nextauth]/route.ts
Normal file
11
apps/partner-sas/app/api/web/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { GET as DEFAULT_GET, POST as DEFAULT_POST } from "@/auth"
|
||||
|
||||
import type { NextRequest } from "next/server"
|
||||
|
||||
export function GET(req: NextRequest) {
|
||||
return DEFAULT_GET(req)
|
||||
}
|
||||
|
||||
export function POST(req: NextRequest) {
|
||||
return DEFAULT_POST(req)
|
||||
}
|
||||
127
apps/partner-sas/auth.ts
Normal file
127
apps/partner-sas/auth.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import NextAuth, { type NextAuthConfig } from "next-auth"
|
||||
import Auth0Provider from "next-auth/providers/auth0"
|
||||
|
||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
const authLogger = createLogger("auth")
|
||||
/*
|
||||
TODO: Get info for SAS token timeout and accordingly adjust pre-refresh time move to common/contants
|
||||
Do we need to handle refresh tokens at all, isn't that handled by the Auth0 provider?
|
||||
Needs to be verified
|
||||
*/
|
||||
|
||||
const sasProvider = Auth0Provider({
|
||||
id: "sas",
|
||||
name: "SAS",
|
||||
clientId: env.SAS_AUTH_CLIENTID,
|
||||
issuer: env.SAS_AUTH_ENDPOINT,
|
||||
checks: ["state"],
|
||||
authorization: {
|
||||
params: {
|
||||
audience: "eb-partner-api",
|
||||
},
|
||||
},
|
||||
client: {
|
||||
token_endpoint_auth_method: "none",
|
||||
},
|
||||
})
|
||||
|
||||
const config: NextAuthConfig = {
|
||||
basePath: "/api/web/auth",
|
||||
providers: [sasProvider],
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: "sas-session",
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
async signIn() {
|
||||
return true
|
||||
},
|
||||
async jwt(params) {
|
||||
if (params.trigger === "signIn") {
|
||||
const accessToken = params.account?.access_token
|
||||
const expiresAt = params.account?.expires_at
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error("AuthError: Missing access token")
|
||||
}
|
||||
|
||||
if (!expiresAt) {
|
||||
throw new Error("AuthError: Missing expiry time")
|
||||
}
|
||||
|
||||
return {
|
||||
...params.token,
|
||||
loginType: "sas",
|
||||
access_token: accessToken,
|
||||
expires_at: expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
return params.token
|
||||
},
|
||||
async session({ session, token }) {
|
||||
return {
|
||||
...session,
|
||||
error: token.error,
|
||||
user: session.user
|
||||
? {
|
||||
...session.user,
|
||||
id: token.sub,
|
||||
}
|
||||
: undefined,
|
||||
token: {
|
||||
access_token: token.access_token,
|
||||
expires_at: token.expires_at,
|
||||
error: token.error,
|
||||
},
|
||||
}
|
||||
},
|
||||
async redirect({ baseUrl, url }) {
|
||||
authLogger.debug(`[auth] deciding redirect URL`, { baseUrl, url })
|
||||
if (url.startsWith("/")) {
|
||||
authLogger.debug(
|
||||
`[auth] relative URL accepted, returning: ${baseUrl}${url}`
|
||||
)
|
||||
// Allows relative callback URLs
|
||||
return `${baseUrl}${url}`
|
||||
} else {
|
||||
// Assume absolute URL
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
if (/\.scandichotels\.com$/.test(parsedUrl.hostname)) {
|
||||
authLogger.debug(`[auth] subdomain URL accepted, returning: ${url}`)
|
||||
// Allows any subdomains on all top level domains above
|
||||
return url
|
||||
} else if (parsedUrl.origin === baseUrl) {
|
||||
// Allows callback URLs on the same origin
|
||||
authLogger.debug(`[auth] origin URL accepted, returning: ${url}`)
|
||||
return url
|
||||
}
|
||||
} catch (e) {
|
||||
authLogger.error(
|
||||
`[auth] error parsing incoming URL for redirection`,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
authLogger.debug(`[auth] URL denied, returning base URL: ${baseUrl}`)
|
||||
|
||||
return baseUrl
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
signIn,
|
||||
signOut,
|
||||
} = NextAuth(config)
|
||||
|
||||
export const { auth } = NextAuth(config)
|
||||
45
apps/partner-sas/components/BookingFlowProviders.tsx
Normal file
45
apps/partner-sas/components/BookingFlowProviders.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client"
|
||||
|
||||
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
||||
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
||||
|
||||
import { useIsUserLoggedIn } from "../hooks/useIsUserLoggedIn"
|
||||
import {
|
||||
trackAccordionItemOpen,
|
||||
trackBedSelection,
|
||||
trackBookingSearchClick,
|
||||
trackBreakfastSelection,
|
||||
trackGenericEvent,
|
||||
trackGlaSaveCardAttempt,
|
||||
trackLoginClick,
|
||||
trackOpenSidePeek,
|
||||
trackPaymentEvent,
|
||||
trackUpdatePaymentMethod,
|
||||
} from "../utils/tracking"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
export function BookingFlowProviders({ children }: { children: ReactNode }) {
|
||||
const isLoggedIn = useIsUserLoggedIn()
|
||||
|
||||
return (
|
||||
<BookingFlowContextProvider data={{ isLoggedIn }}>
|
||||
<BookingFlowTrackingProvider
|
||||
trackingFunctions={{
|
||||
trackBookingSearchClick,
|
||||
trackAccordionItemOpen,
|
||||
trackOpenSidePeek,
|
||||
trackGenericEvent,
|
||||
trackGlaSaveCardAttempt,
|
||||
trackLoginClick,
|
||||
trackPaymentEvent,
|
||||
trackUpdatePaymentMethod,
|
||||
trackBreakfastSelection,
|
||||
trackBedSelection,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BookingFlowTrackingProvider>
|
||||
</BookingFlowContextProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import { PoweredByScandic } from "../PoweredByScandic/PoweredByScandic"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
export function Header() {
|
||||
const lang = useLang()
|
||||
const session = useSession()
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
@@ -16,6 +28,29 @@ export function Header() {
|
||||
width={90}
|
||||
sizes="100vw"
|
||||
/>
|
||||
{session.status === "loading" && (
|
||||
<SkeletonShimmer width={"12ch"} height={"1ch"} />
|
||||
)}
|
||||
{session.status === "unauthenticated" && (
|
||||
/** For some reason it complains about RSC-payload if using <Link /> */
|
||||
<a href={`/${lang}/login?redirectTo=${window?.location.href}`}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{"Login here"}
|
||||
</a>
|
||||
)}
|
||||
{session.status === "authenticated" && (
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{session.data?.user && <>{session.data.user.email}</>}
|
||||
</span>
|
||||
</Typography>
|
||||
<Link color={"white"} href={`/${lang}/logout`} prefetch={false}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{"Logout"}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<div className={styles.poweredBy}>
|
||||
<PoweredByScandic />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
justify-content: space-between;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 20px 40px;
|
||||
|
||||
@@ -8,6 +8,5 @@
|
||||
.logo {
|
||||
max-height: 14px;
|
||||
max-width: 65px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
10
apps/partner-sas/env/server.ts
vendored
10
apps/partner-sas/env/server.ts
vendored
@@ -18,6 +18,13 @@ export const env = createEnv({
|
||||
.transform((s) => s === "true")
|
||||
.default("false"),
|
||||
PUBLIC_URL: z.string().default(""),
|
||||
SAS_AUTH_ENDPOINT: z.string().default(""),
|
||||
SAS_AUTH_CLIENTID: z.string().default(""),
|
||||
NEXTAUTH_DEBUG: z
|
||||
.string()
|
||||
.refine((s) => s === "true" || s === "false")
|
||||
.transform((s) => s === "true")
|
||||
.default("false"),
|
||||
SENTRY_ENVIRONMENT: z.string().default("development"),
|
||||
SENTRY_SERVER_SAMPLERATE: z.coerce.number().default(0.001),
|
||||
},
|
||||
@@ -26,6 +33,9 @@ export const env = createEnv({
|
||||
ADOBE_SDK_SCRIPT_SRC: process.env.ADOBE_SDK_SCRIPT_SRC,
|
||||
ENABLE_GTMSCRIPT: process.env.ENABLE_GTMSCRIPT,
|
||||
PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL,
|
||||
SAS_AUTH_ENDPOINT: process.env.SAS_AUTH_ENDPOINT,
|
||||
SAS_AUTH_CLIENTID: process.env.SAS_AUTH_CLIENTID,
|
||||
NEXTAUTH_DEBUG: process.env.NEXTAUTH_DEBUG,
|
||||
SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
|
||||
SENTRY_SERVER_SAMPLERATE: process.env.SENTRY_SERVER_SAMPLERATE,
|
||||
},
|
||||
|
||||
32
apps/partner-sas/hooks/useIsUserLoggedIn.ts
Normal file
32
apps/partner-sas/hooks/useIsUserLoggedIn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
|
||||
import type { Session } from "next-auth"
|
||||
|
||||
export function useIsUserLoggedIn() {
|
||||
const { data: session } = useSession()
|
||||
const isUserLoggedIn = isValidClientSession(session)
|
||||
return isUserLoggedIn
|
||||
}
|
||||
|
||||
function isValidClientSession(session: Session | null) {
|
||||
if (!session) {
|
||||
return false
|
||||
}
|
||||
if (session.error) {
|
||||
logger.error(`Session error: ${session.error}`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (session.token.error) {
|
||||
logger.error(`Session token error: ${session.token.error}`)
|
||||
return false
|
||||
}
|
||||
if (session.token.expires_at && session.token.expires_at < Date.now()) {
|
||||
logger.error(`Session expired: ${session.token.expires_at}`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const nextConfig: NextConfig = {
|
||||
],
|
||||
},
|
||||
|
||||
webpack: function (config: any) {
|
||||
webpack: function (config) {
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: /\.(graphql|gql)/,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"lint": "next lint --max-warnings 0 && tsc --noEmit",
|
||||
"lint:fix": "next lint --fix && tsc --noEmit",
|
||||
"check-types": "tsc --noEmit",
|
||||
"clean": "rm -rf .next",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
@@ -28,6 +29,7 @@
|
||||
"@swc/plugin-formatjs": "^3.2.2",
|
||||
"@tanstack/react-query-devtools": "^5.75.5",
|
||||
"next": "15.3.4",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-intl": "^7.1.11",
|
||||
|
||||
57
apps/partner-sas/server/errors/next.ts
Normal file
57
apps/partner-sas/server/errors/next.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export function badRequest(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 400,
|
||||
statusText: "Bad request",
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
export function notFound(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 404,
|
||||
statusText: "Not found",
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
export function internalServerError(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 500,
|
||||
statusText: "Internal Server Error",
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
export function serviceUnavailable(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 503,
|
||||
statusText: "Service Unavailable",
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"lint": { "dependsOn": [] },
|
||||
"build": { "dependsOn": ["include:shared"] },
|
||||
"dev": { "dependsOn": ["include:shared"] },
|
||||
"build": { "dependsOn": ["clean", "include:shared"] },
|
||||
"dev": { "dependsOn": ["clean", "include:shared"] },
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"@scandic-hotels/trpc#test",
|
||||
@@ -11,6 +12,9 @@
|
||||
"@scandic-hotels/booking-flow#test"
|
||||
]
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
},
|
||||
"include:shared": {
|
||||
"outputs": ["public/_static/shared/**"]
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ export const middleware: NextMiddleware = async (request) => {
|
||||
? pathWithoutTrailingSlash.replace("/preview", "")
|
||||
: pathWithoutTrailingSlash
|
||||
|
||||
let { contentType, uid, error } =
|
||||
await getUidAndContentTypeByPath(incomingPathName)
|
||||
if (error) {
|
||||
throw internalServerError(error)
|
||||
const uidAndContent = await getUidAndContentTypeByPath(incomingPathName)
|
||||
if (uidAndContent.error) {
|
||||
throw internalServerError(uidAndContent.error)
|
||||
}
|
||||
|
||||
let { contentType, uid } = uidAndContent
|
||||
const searchParams = new URLSearchParams(request.nextUrl.searchParams)
|
||||
|
||||
if (!contentType || !uid) {
|
||||
@@ -40,7 +40,7 @@ export const middleware: NextMiddleware = async (request) => {
|
||||
if (incomingPathNameParts.length >= 2) {
|
||||
const subpage = incomingPathNameParts.pop()
|
||||
if (subpage) {
|
||||
let { contentType: parentContentType, uid: parentUid } =
|
||||
const { contentType: parentContentType, uid: parentUid } =
|
||||
await getUidAndContentTypeByPath(incomingPathNameParts.join("/"))
|
||||
|
||||
if (parentUid) {
|
||||
|
||||
@@ -92,6 +92,7 @@ const nextConfig = {
|
||||
|
||||
output: "standalone",
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
webpack: function (config: any) {
|
||||
config.module.rules.push(
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "yarn clean && next build",
|
||||
"build": "next build",
|
||||
"dev": "NODE_OPTIONS=--openssl-legacy-provider PORT=3000 NEXT_PUBLIC_PORT=3000 next dev",
|
||||
"lint": "yarn clean && next lint --max-warnings 0 && tsc",
|
||||
"lint:fix": "yarn clean && next lint --fix --max-warnings 0 && tsc",
|
||||
@@ -78,7 +78,7 @@
|
||||
"motion": "^12.10.0",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.3.4",
|
||||
"next-auth": "5.0.0-beta.27",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"react": "19.1.0",
|
||||
"react-aria-components": "^1.8.0",
|
||||
"react-day-picker": "^9.6.7",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"lint": { "dependsOn": [] },
|
||||
"build": { "dependsOn": ["include:shared"] },
|
||||
"dev": { "dependsOn": ["include:shared"] },
|
||||
"build": { "dependsOn": ["clean", "include:shared"] },
|
||||
"dev": { "dependsOn": ["clean", "include:shared"] },
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"@scandic-hotels/trpc#test",
|
||||
@@ -13,6 +14,9 @@
|
||||
},
|
||||
"include:shared": {
|
||||
"outputs": ["public/_static/shared/**"]
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function redeemLocationIsOnSite(
|
||||
return location === "On-site"
|
||||
}
|
||||
|
||||
function isTierType(type: string): type is "Tier" {
|
||||
export function isTierType(type: string): type is "Tier" {
|
||||
return type === "Tier"
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ export enum LoginTypeEnum {
|
||||
"membership number" = "membership number",
|
||||
"email link" = "email link",
|
||||
"dtmc" = "dtmc",
|
||||
"sas" = "sas",
|
||||
}
|
||||
export type LoginType = keyof typeof LoginTypeEnum
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"graphql-request": "^7.1.2",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"next-auth": "5.0.0-beta.27",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"server-only": "^0.0.1",
|
||||
"slugify": "^1.6.6",
|
||||
"superjson": "^2.2.2",
|
||||
|
||||
23
yarn.lock
23
yarn.lock
@@ -134,9 +134,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@auth/core@npm:0.39.0":
|
||||
version: 0.39.0
|
||||
resolution: "@auth/core@npm:0.39.0"
|
||||
"@auth/core@npm:0.40.0":
|
||||
version: 0.40.0
|
||||
resolution: "@auth/core@npm:0.40.0"
|
||||
dependencies:
|
||||
"@panva/hkdf": "npm:^1.2.1"
|
||||
jose: "npm:^6.0.6"
|
||||
@@ -154,7 +154,7 @@ __metadata:
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 10c0/551891eddfcf29bdd4bf614bb0b700a76dbf148af7ead740ecd518d27618a7558a44703edd70ff16298cb54ec2912d7921586a71cb7e233b5d456899345d730b
|
||||
checksum: 10c0/25cd12f22611eedc21c17dc1908fa9428ae5f0e32eb32c1ab009642276c37099cce58f49ffbb7f8e8d6d6488d5101a24fb9808ec662eee5aca19d520750acaa3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6052,6 +6052,7 @@ __metadata:
|
||||
eslint-plugin-simple-import-sort: "npm:^12.1.1"
|
||||
graphql-tag: "npm:^2.12.6"
|
||||
next: "npm:15.3.4"
|
||||
next-auth: "npm:5.0.0-beta.29"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
react-intl: "npm:^7.1.11"
|
||||
@@ -6189,7 +6190,7 @@ __metadata:
|
||||
nanoid: "npm:^5.1.5"
|
||||
netlify-plugin-cypress: "npm:^2.2.1"
|
||||
next: "npm:15.3.4"
|
||||
next-auth: "npm:5.0.0-beta.27"
|
||||
next-auth: "npm:5.0.0-beta.29"
|
||||
prettier: "npm:^3.5.3"
|
||||
react: "npm:19.1.0"
|
||||
react-aria-components: "npm:^1.8.0"
|
||||
@@ -6268,7 +6269,7 @@ __metadata:
|
||||
graphql-request: "npm:^7.1.2"
|
||||
graphql-tag: "npm:^2.12.6"
|
||||
json-stable-stringify-without-jsonify: "npm:^1.0.1"
|
||||
next-auth: "npm:5.0.0-beta.27"
|
||||
next-auth: "npm:5.0.0-beta.29"
|
||||
server-only: "npm:^0.0.1"
|
||||
slugify: "npm:^1.6.6"
|
||||
superjson: "npm:^2.2.2"
|
||||
@@ -15550,11 +15551,11 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-auth@npm:5.0.0-beta.27":
|
||||
version: 5.0.0-beta.27
|
||||
resolution: "next-auth@npm:5.0.0-beta.27"
|
||||
"next-auth@npm:5.0.0-beta.29":
|
||||
version: 5.0.0-beta.29
|
||||
resolution: "next-auth@npm:5.0.0-beta.29"
|
||||
dependencies:
|
||||
"@auth/core": "npm:0.39.0"
|
||||
"@auth/core": "npm:0.40.0"
|
||||
peerDependencies:
|
||||
"@simplewebauthn/browser": ^9.0.1
|
||||
"@simplewebauthn/server": ^9.0.2
|
||||
@@ -15568,7 +15569,7 @@ __metadata:
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 10c0/6f13e4d7d44bf0327cea017d7adb8cf9e9bca32bf030dd147ed5dd8301bcd1cf521b577615454b969643a08389b260347cc83c3e3c421dfa593f3ab7b126295a
|
||||
checksum: 10c0/2c6bada9a5f28a9a172d3ad295bfb05b648a4fced01f9988154df1ebca712cf460fb49173ada4c26de4c7ab180256f40ac19d16e2147c1c68f2a7475ab5d5ea8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user