Merged in feat/sw-3192-no-user (pull request #2680)

feat(SW-3192): Checks if user exists, otherwise logout and show error

* feat(SW-3192): Checks if user exists, otherwise logout and show error
This commit is contained in:
Linus Flood
2025-08-22 09:47:54 +00:00
parent caffa1821f
commit e2544f9f89
10 changed files with 238 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
import { type NextRequest,NextResponse } from "next/server"
import { AuthError } from "next-auth"
import { logger } from "@scandic-hotels/common/logger"
import { env } from "@/env/server"
import { internalServerError } from "@/server/errors/next"
import { getPublicURL } from "@/server/utils"
import { signOut } from "@/auth"
export async function GET(request: NextRequest) {
const publicURL = getPublicURL(request)
const redirectToSearchParamValue =
request.nextUrl.searchParams.get("redirectTo")
const redirectToFallback = "/"
let redirectTo: string = redirectToSearchParamValue || redirectToFallback
// Make relative URL to absolute URL
if (redirectTo.startsWith("/")) {
redirectTo = new URL(redirectTo, publicURL).href
}
try {
redirectTo = `${env.CURITY_ISSUER_USER}/authn/authenticate/logout?redirect_uri=${encodeURIComponent(redirectTo)}`
logger.debug(`[logoutSafely] final redirectUrl: ${redirectTo}`)
const redirectUrlObj = await signOut({
redirectTo,
redirect: false,
})
return NextResponse.redirect(redirectUrlObj.redirect)
} catch (error) {
if (error instanceof AuthError) {
logger.error("signOutSafelyAuthError", { signOutAuthError: error })
} else {
logger.error("signOutSafelyError", { signOutError: error })
}
}
return internalServerError()
}

View File

@@ -0,0 +1,14 @@
import { UserNotFound } from "@/components/UserNotFound/UserNotFound"
import type { Metadata } from "next"
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
},
}
export default function UserNotFoundPage() {
return <UserNotFound />
}

View File

@@ -24,6 +24,7 @@ import SitewideAlert from "@/components/SitewideAlert"
import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript"
import GTMScript from "@/components/TrackingSDK/GTMScript"
import { UserExists } from "@/components/UserExists"
import { FontPreload } from "@/fonts/font-preloading"
import { getMessages } from "@/i18n"
import ClientIntlProvider from "@/i18n/Provider"
@@ -87,6 +88,7 @@ export default async function RootLayout(
<SessionRefresher />
<StorageCleaner />
<CookieBotConsent />
<UserExists />
<ReactQueryDevtools initialIsOpen={false} />
</BookingFlowTrackingProvider>
</RACRouterProvider>

View File

@@ -0,0 +1,43 @@
"use client"
import { redirect } from "next/navigation"
import { useSession } from "next-auth/react"
import { trpc } from "@scandic-hotels/trpc/client"
import { userNotFound } from "@/constants/routes/errorPages"
import { logoutSafely } from "@/constants/routes/handleAuth"
import useLang from "@/hooks/useLang"
import { isValidClientSession } from "@/utils/clientSession"
export function UserExists() {
const { data: session } = useSession()
const isUserLoggedIn = isValidClientSession(session)
const lang = useLang()
const { data, isLoading: isLoadingUser } = trpc.user.get.useQuery(undefined, {
enabled: isUserLoggedIn,
})
if (!isUserLoggedIn) {
return null
}
if (isLoadingUser) {
return null
}
if (data && "error" in data && data.error) {
switch (data.cause) {
case "notfound":
redirect(
`${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}`
)
default:
break
}
}
return null
}

View File

@@ -0,0 +1,65 @@
.container {
margin-top: 0;
background: var(--Main-Grey-White);
position: relative;
border-top: 50px solid var(--Main-Grey-White);
background-clip: content-box;
}
.content {
position: relative;
text-align: center;
box-sizing: content-box;
max-width: var(--max-width-page);
margin: 0 auto;
padding: 70px 30px;
}
.header {
font-family:
brandon text,
Arial,
Helvetica,
sans-serif;
font-size: 32px;
line-height: 1;
margin: 0;
text-transform: uppercase;
font-weight: 400;
color: #483729;
}
.pitch {
margin-top: 32px;
margin-bottom: 16px;
}
.text {
font-family:
Helvetica Neue,
Roboto,
Helvetica,
Arial,
sans-serif;
font-weight: 300;
line-height: normal;
text-transform: none;
font-size: 20px;
color: #333;
}
@media screen and (min-width: 740px) {
.content {
text-align: left;
}
}
@media screen and (min-width: 950px) {
.header {
font-size: 46px;
}
.text {
font-size: 24px;
}
}

View File

@@ -0,0 +1,28 @@
"use client"
import { useIntl } from "react-intl"
import styles from "./UserNotFound.module.css"
export function UserNotFound() {
const intl = useIntl()
return (
<div className={styles.container}>
<div className={styles.content}>
<h1 className={styles.header}>
{intl.formatMessage({
defaultMessage: "User not found",
})}
</h1>
<div className={styles.pitch}>
<p className={styles.text}>
{intl.formatMessage({
defaultMessage: "Please try again or contact customer service!",
})}
</p>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,10 @@
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
export const userNotFound: LangRoute = {
en: "/en/error/user-not-found",
sv: "/sv/error/user-not-found",
no: "/no/error/user-not-found",
fi: "/fi/error/user-not-found",
da: "/da/error/user-not-found",
de: "/de/error/user-not-found",
}

View File

@@ -32,6 +32,15 @@ export const logout = {
sv: "/sv/logga-ut",
}
export const logoutSafely = {
da: "/da/logoutSafely",
de: "/de/logoutSafely",
en: "/en/logoutSafely",
fi: "/fi/logoutSafely",
no: "/no/logoutSafely",
sv: "/sv/logoutSafely",
}
/** @type {import('@scandic-hotels/common/constants/routes/langRoute').LangRoute} */
export const logoutUnLocalized = {
da: "/da/logout",
@@ -58,4 +67,5 @@ export const handleAuth = [
...Object.values(verifymagiclink),
...Object.values(loginUnLocalized),
...Object.values(logoutUnLocalized),
...Object.values(logoutSafely),
]

View File

@@ -12,6 +12,7 @@ import * as currentWebLogin from "@/middlewares/currentWebLogin"
import * as currentWebLoginEmail from "@/middlewares/currentWebLoginEmail"
import * as currentWebLogout from "@/middlewares/currentWebLogout"
import * as dateQueryParams from "@/middlewares/dateQueryParams"
import * as errorPages from "@/middlewares/errorPages"
import * as familyAndFriends from "@/middlewares/familyAndFriends"
import * as handleAuth from "@/middlewares/handleAuth"
import * as handleDTMC from "@/middlewares/handleDTMC"
@@ -67,6 +68,7 @@ export const middleware: NextMiddleware = async (request, event) => {
dateQueryParams,
legacySearchParams,
bookingFlow,
errorPages,
familyAndFriends,
sasXScandic,
cmsContent,

View File

@@ -0,0 +1,20 @@
import { type NextMiddleware, NextResponse } from "next/server"
import { getDefaultRequestHeaders } from "./utils"
import type { MiddlewareMatcher } from "@/types/middleware"
export const middleware: NextMiddleware = async (request) => {
const headers = getDefaultRequestHeaders(request)
headers.set("X-Robots-Tag", "noindex, nofollow")
return NextResponse.next({
request: {
headers,
},
})
}
export const matcher: MiddlewareMatcher = (request) => {
return !!request.nextUrl.pathname.match(/^\/(da|de|en|fi|no|sv)\/(error)/)
}