diff --git a/app/[lang]/webview/loyalty-page/page.tsx b/app/[lang]/webview/loyalty-page/page.tsx index 0c2ee7c00..d3e821aab 100644 --- a/app/[lang]/webview/loyalty-page/page.tsx +++ b/app/[lang]/webview/loyalty-page/page.tsx @@ -1,7 +1,6 @@ -import { notFound } from "next/navigation" - import { serverClient } from "@/lib/trpc/server" +import BackButton from "@/components/BackButton" import { Blocks } from "@/components/Loyalty/Blocks/WebView" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" @@ -19,6 +18,7 @@ export default async function AboutScandicFriends({ {loyaltyPage.sidebar ? : null} + diff --git a/app/[lang]/webview/my-pages/page.tsx b/app/[lang]/webview/my-pages/page.tsx index f6ef509a5..199ee2425 100644 --- a/app/[lang]/webview/my-pages/page.tsx +++ b/app/[lang]/webview/my-pages/page.tsx @@ -1,11 +1,11 @@ import "@/app/globals.css" import "@scandic-hotels/design-system/style.css" -import { notFound } from "next/navigation" - +import { overview } from "@/constants/routes/myPages" import { _ } from "@/lib/translation" import { serverClient } from "@/lib/trpc/server" +import BackButton from "@/components/BackButton" import MaxWidth from "@/components/MaxWidth" import Content from "@/components/MyPages/AccountPage/Webview/Content" @@ -16,8 +16,11 @@ import { LangParams, PageArgs } from "@/types/params" export default async function MyPages({ params }: PageArgs) { const accountPage = await serverClient().contentstack.accountPage.get() + const isNotOverviewPage = accountPage.url !== overview[params.lang] + return ( + {isNotOverviewPage ? : null} ) diff --git a/components/BackButton/index.tsx b/components/BackButton/index.tsx new file mode 100644 index 000000000..b9eebdddd --- /dev/null +++ b/components/BackButton/index.tsx @@ -0,0 +1,15 @@ +"use client" + +import { useRouter } from "next/navigation" + +import Button from "../TempDesignSystem/Button" + +export default function BackButton() { + const router = useRouter() + + function goBack() { + router.back() + } + + return +} diff --git a/components/Loyalty/Blocks/WebView/index.tsx b/components/Loyalty/Blocks/WebView/index.tsx index f5b5fea0f..fbf96d315 100644 --- a/components/Loyalty/Blocks/WebView/index.tsx +++ b/components/Loyalty/Blocks/WebView/index.tsx @@ -3,7 +3,7 @@ import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent" import Shortcuts from "@/components/MyPages/Blocks/Shortcuts" import { modWebviewLink } from "@/utils/webviews" -import CardGrid from "../CardGrid" +import CardsGrid from "../CardsGrid" import type { BlocksProps } from "@/types/components/loyalty/blocks" import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums" @@ -12,20 +12,8 @@ import { LangParams } from "@/types/params" export function Blocks({ lang, blocks }: BlocksProps & LangParams) { return blocks.map((block) => { switch (block.__typename) { - case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid: - const cardGrid = { - ...block.card_grid, - cards: block.card_grid.cards.map((card) => { - return { - ...card, - link: card.link - ? { ...card.link, href: modWebviewLink(card.link.href, lang) } - : undefined, - } - }), - } - - return + case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid: + return case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent: return (
diff --git a/lib/trpc/server.ts b/lib/trpc/server.ts index 765c5fbbe..7f3966979 100644 --- a/lib/trpc/server.ts +++ b/lib/trpc/server.ts @@ -25,7 +25,7 @@ export function serverClient() { const lang = ctx?.lang || Lang.en if (ctx?.webToken) { console.log({ ctx }) - const returnUrl = ctx.pathname || overview[lang] + const returnUrl = ctx.url const redirectUrl = `/${lang}/webview/refresh?returnurl=${encodeURIComponent(returnUrl)}` console.error( diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 209dbf5c8..feba8f258 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -7,7 +7,10 @@ export function getDefaultRequestHeaders(request: NextRequest) { const headers = new Headers(request.headers) headers.set("x-lang", lang) - headers.set("x-pathname", request.nextUrl.pathname.replace(`/${lang}`, "")) + headers.set( + "x-pathname", + request.nextUrl.pathname.replace(`/${lang}`, "").replace(`/webview`, "") + ) headers.set("x-url", request.nextUrl.href) return headers diff --git a/middlewares/webView.ts b/middlewares/webView.ts index d6ad52dac..6d62b9f74 100644 --- a/middlewares/webView.ts +++ b/middlewares/webView.ts @@ -1,4 +1,3 @@ -import { notFound } from "next/navigation" import { type NextMiddleware, NextResponse } from "next/server" import { findLang } from "@/constants/languages" @@ -9,9 +8,12 @@ import { webviews, } from "@/constants/routes/webviews" import { env } from "@/env/server" -import { badRequest } from "@/server/errors/next" +import { badRequest, notFound } from "@/server/errors/next" import { decryptData } from "@/utils/aes" +import { resolve as resolveEntry } from "@/utils/entry" + +import { getDefaultRequestHeaders } from "./utils" import type { MiddlewareMatcher } from "@/types/middleware" @@ -19,22 +21,25 @@ export const middleware: NextMiddleware = async (request) => { const { nextUrl } = request const lang = findLang(nextUrl.pathname) - const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}/webview`, "") - const headers = new Headers() - // If user is redirected to /lang/webview/refresh/, the webview token is invalid and we remove the cookie if (refreshWebviews.includes(nextUrl.pathname)) { - headers.set( - "Set-Cookie", - `webviewToken=0; Max-Age=0; Secure; HttpOnly; Path=/; SameSite=Strict;` - ) return NextResponse.rewrite(new URL(`/${lang}/webview/refresh`, nextUrl), { - headers, + headers: { + "Set-Cookie": `webviewToken=0; Max-Age=0; Secure; HttpOnly; Path=/; SameSite=Strict;`, + }, }) } - const searchParams = new URLSearchParams(request.nextUrl.searchParams) - searchParams.set("uri", pathNameWithoutLang) + const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}/webview`, "") + + const { uid } = await resolveEntry(pathNameWithoutLang, lang) + if (!uid) { + throw notFound( + `Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}` + ) + } + const headers = getDefaultRequestHeaders(request) + headers.set("x-uid", uid) const webviewToken = request.cookies.get("webviewToken") if (webviewToken) { @@ -42,14 +47,21 @@ export const middleware: NextMiddleware = async (request) => { // we're done, allow it if (myPagesWebviews.includes(nextUrl.pathname)) { return NextResponse.rewrite( - new URL(`/${lang}/webview/my-pages?${searchParams.toString()}`, nextUrl) + new URL(`/${lang}/webview/my-pages`, nextUrl), + { + request: { + headers, + }, + } ) } else if (loyaltyPagesWebviews.includes(nextUrl.pathname)) { return NextResponse.rewrite( - new URL( - `/${lang}/webview/loyalty-page?${searchParams.toString()}`, - nextUrl - ) + new URL(`/${lang}/webview/loyalty-page`, nextUrl), + { + request: { + headers, + }, + } ) } else { return notFound() @@ -77,32 +89,30 @@ export const middleware: NextMiddleware = async (request) => { authorization ) - headers.set( - "Set-Cookie", - `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;` - ) - headers.set("Cookie", `webviewToken=${decryptedData}`) - - console.log("IN WEBVIEW MIDDLEWARE", decryptedData) - if (myPagesWebviews.includes(nextUrl.pathname)) { return NextResponse.rewrite( - new URL( - `/${lang}/webview/my-pages?${searchParams.toString()}`, - nextUrl - ), + new URL(`/${lang}/webview/my-pages`, nextUrl), { - headers, + headers: { + "Set-Cookie": `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;`, + Cookie: `webviewToken=${decryptedData}`, + }, + request: { + headers, + }, } ) } else if (loyaltyPagesWebviews.includes(nextUrl.pathname)) { return NextResponse.rewrite( - new URL( - `/${lang}/webview/loyalty-page?${searchParams.toString()}`, - nextUrl - ), + new URL(`/${lang}/webview/loyalty-page`, nextUrl), { - headers, + headers: { + "Set-Cookie": `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;`, + Cookie: `webviewToken=${decryptedData}`, + }, + request: { + headers, + }, } ) } diff --git a/server/trpc.ts b/server/trpc.ts index 17e82607b..65e49fff4 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -41,7 +41,7 @@ export const protectedProcedure = t.procedure.use(async function (opts) { throw sessionExpiredError() } - if (!session?.user) { + if (!session?.user || !opts.ctx.webToken) { throw unauthorizedError() } diff --git a/test.js b/test.js new file mode 100644 index 000000000..2e6a6db89 --- /dev/null +++ b/test.js @@ -0,0 +1,82 @@ +function base64ToUint8Array(base64String) { + const binaryString = atob(base64String) + const byteArray = new Uint8Array(binaryString.length) + for (let i = 0; i < binaryString.length; i++) { + byteArray[i] = binaryString.charCodeAt(i) + } + return byteArray +} + +function utf8ToUint8Array(utf8String) { + return new TextEncoder().encode(utf8String) +} + +function uint8ArrayToBase64(uint8Array) { + let binaryString = "" + const len = uint8Array.byteLength + for (let i = 0; i < len; i++) { + binaryString += String.fromCharCode(uint8Array[i]) + } + return btoa(binaryString) +} +async function encryptData(keyBase64, ivBase64, data) { + const keyBuffer = await crypto.subtle.importKey( + "raw", + base64ToUint8Array(keyBase64), + "AES-CBC", + false, + ["encrypt"] + ) + + const dataBuffer = utf8ToUint8Array(data) + const ivBuffer = base64ToUint8Array(ivBase64) + const encryptedDataBuffer = await crypto.subtle.encrypt( + { name: "AES-CBC", iv: ivBuffer }, + keyBuffer, + dataBuffer + ) + + const encryptedData = uint8ArrayToBase64(new Uint8Array(encryptedDataBuffer)) + return encryptedData +} + +function uint8ArrayToUtf8(uint8Array) { + return new TextDecoder().decode(uint8Array) +} + +async function decryptData(keyBase64, ivBase64, encryptedDataBase64) { + const keyBuffer = await crypto.subtle.importKey( + "raw", + base64ToUint8Array(keyBase64), + "AES-CBC", + false, + ["decrypt"] + ) + + const encryptedDataBuffer = base64ToUint8Array(encryptedDataBase64) + const ivBuffer = base64ToUint8Array(ivBase64) + const decryptedDataBuffer = await crypto.subtle.decrypt( + { name: "AES-CBC", iv: ivBuffer }, + keyBuffer, + encryptedDataBuffer + ) + + const decryptedData = uint8ArrayToUtf8(new Uint8Array(decryptedDataBuffer)) + return decryptedData +} + +const data = "_0XBPWQQ_e81346b1-6e8f-44bf-ad9c-33fd2dcc1abd" +const iv = btoa("abcdefghijklmnop") +const tegwpjke = await encryptData( + "JYekSRT8YXWquXpxxukJR0GsELl5Nt4KdcCbaCvSzHE=", + iv, + data +) + +const decrypttionData = await decryptData( + "JYekSRT8YXWquXpxxukJR0GsELl5Nt4KdcCbaCvSzHE=", + iv, + tegwpjke +) + +console.log(tegwpjke, btoa("abcdefghijklmnop"), decrypttionData === data)