fix(auth): make things work
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
"use client"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
import { SESSION_EXPIRED } from "@/server/errors/trpc"
|
||||
|
||||
import type { ErrorPage } from "@/types/next/error"
|
||||
import type { LangParams } from "@/types/params"
|
||||
|
||||
export default function ProtectedError({ error }: ErrorPage) {
|
||||
const params = useParams<LangParams>()
|
||||
|
||||
useEffect(() => {
|
||||
if (error.message === SESSION_EXPIRED) {
|
||||
const loginUrl = login[params.lang]
|
||||
window.location.assign(loginUrl)
|
||||
}
|
||||
}, [error.message, params.lang])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,23 +1,34 @@
|
||||
"use client" // Error components must be Client Components
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useParams, usePathname } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
|
||||
import { findLang } from "@/constants/languages"
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
import { SESSION_EXPIRED } from "@/server/errors/trpc"
|
||||
|
||||
import { firaMono, firaSans } from "@/app/[lang]/(live)/fonts"
|
||||
|
||||
import styles from "./error.module.css"
|
||||
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
}) {
|
||||
const params = useParams<LangParams>()
|
||||
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
if (error.message === SESSION_EXPIRED) {
|
||||
const loginUrl = login[params.lang]
|
||||
window.location.assign(loginUrl)
|
||||
}
|
||||
}, [error, params.lang])
|
||||
|
||||
const pathname = usePathname()
|
||||
const lang = findLang(pathname)
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { User } from "@/types/user"
|
||||
|
||||
export default function CopyButton({ membership }: Pick<User, "membership">) {
|
||||
function handleCopy() {
|
||||
console.log(`COPIED! (${membership.membershipNumber})`)
|
||||
console.log(`COPIED! (${membership ? membership.membershipNumber : "N/A"})`)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function Friend({ user }: FriendProps) {
|
||||
<h3 className={styles.name}>{user.name}</h3>
|
||||
<div className={styles.membershipContainer}>
|
||||
<p className={styles.membershipId}>
|
||||
{user.membership.membershipNumber}
|
||||
{user.membership ? user.membership.membershipNumber : "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -11,7 +11,9 @@ export default function TotalPoints({ user }: TotalPointsProps) {
|
||||
<div>
|
||||
<Title>Total Points</Title>
|
||||
<Divider className={styles.divider} variant="dotted" />
|
||||
<p className={styles.points}>{user.membership.currentPoints}</p>
|
||||
<p className={styles.points}>
|
||||
{user.membership ? user.membership.currentPoints : "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export namespace endpoints {
|
||||
profile = "profile/v0/Profile",
|
||||
}
|
||||
export const enum v1 {
|
||||
profile = "profile/v1/Profile",
|
||||
upcomingStays = "booking/v1/Stays/future",
|
||||
previousStays = "booking/v1/Stays/past",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#import "../Fragments/Refs/System.graphql"
|
||||
|
||||
query ResolveEntryByUrl($locale: String!, $url: String!) {
|
||||
all_account_page(where: { url: $url }, locale: $locale) {
|
||||
items {
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
all_content_page(where: { url: $url }, locale: $locale) {
|
||||
items {
|
||||
system {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from "@tanstack/react-query"
|
||||
import { httpBatchLink, loggerLink,TRPCClientError } from "@trpc/client"
|
||||
import { httpBatchLink, loggerLink, TRPCClientError } from "@trpc/client"
|
||||
import { AnyTRPCRouter } from "@trpc/server"
|
||||
import { useState } from "react"
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ export function serverClient() {
|
||||
if (error.code === "UNAUTHORIZED") {
|
||||
const lang = ctx?.lang || Lang.en
|
||||
const pathname = ctx?.pathname || "/"
|
||||
redirect(`/${lang}/login?redirectTo=${encodeURIComponent(pathname)}`)
|
||||
redirect(
|
||||
`/${lang}/login?redirectTo=${encodeURIComponent(`/${lang}/${pathname}`)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,13 +46,20 @@ export const middleware: NextMiddleware = async (request, event) => {
|
||||
|
||||
// We use x-lang as either Akamai or Netlify use x-language as the users preferred language
|
||||
result?.headers.set("x-lang", lang)
|
||||
result?.headers.set("x-pathname", request.nextUrl.pathname)
|
||||
result?.headers.set(
|
||||
"x-pathname",
|
||||
request.nextUrl.pathname.replace(`/${lang}`, "")
|
||||
)
|
||||
result?.headers.set("x-url", request.nextUrl.href)
|
||||
return result
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof NextResponse && e.status) {
|
||||
const cause = await e.json()
|
||||
console.error(`Error in middleware`)
|
||||
console.error(cause)
|
||||
|
||||
return NextResponse.rewrite(
|
||||
new URL(`/${lang}/middleware-error/${e.status}`, request.nextUrl),
|
||||
{
|
||||
|
||||
@@ -44,11 +44,7 @@ export const middleware = auth(async (request) => {
|
||||
const isLoggedIn = !!request.auth
|
||||
const hasError = request.auth?.error
|
||||
|
||||
if (hasError) {
|
||||
throw internalServerError(request.auth?.error)
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
if (isLoggedIn && !hasError) {
|
||||
const headers = new Headers(request.headers)
|
||||
headers.set("x-continue", "1")
|
||||
return NextResponse.next({
|
||||
|
||||
@@ -19,7 +19,9 @@ export const middleware: NextMiddleware = async (request) => {
|
||||
const { contentType, uid } = await resolveEntry(pathNameWithoutLang, lang)
|
||||
|
||||
if (!contentType || !uid) {
|
||||
throw notFound()
|
||||
throw notFound(
|
||||
`Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}`
|
||||
)
|
||||
}
|
||||
|
||||
const isCurrent = contentType ? contentType.indexOf("current") >= 0 : false
|
||||
|
||||
@@ -2,23 +2,53 @@ import { NextResponse } from "next/server"
|
||||
|
||||
import { findLang } from "@/constants/languages"
|
||||
import { myPages, overview } from "@/constants/routes/myPages"
|
||||
import { env } from "@/env/server"
|
||||
import { internalServerError, notFound } from "@/server/errors/next"
|
||||
|
||||
import { resolve as resolveEntry } from "@/utils/entry"
|
||||
|
||||
import type { NextMiddleware } from "next/server"
|
||||
|
||||
import type { MiddlewareMatcher } from "@/types/middleware"
|
||||
|
||||
export const middleware: NextMiddleware = (request) => {
|
||||
const lang = findLang(request.nextUrl.pathname)!
|
||||
return NextResponse.redirect(overview[lang])
|
||||
export const middleware: NextMiddleware = async (request) => {
|
||||
const { nextUrl } = request
|
||||
const lang = findLang(nextUrl.pathname)!
|
||||
|
||||
const myPagesRoot = myPages[lang]
|
||||
if (nextUrl.pathname === myPagesRoot) {
|
||||
if (!env.PUBLIC_URL) {
|
||||
throw internalServerError("Missing value for env.PUBLIC_URL")
|
||||
}
|
||||
|
||||
const publicUrl = new URL(env.PUBLIC_URL)
|
||||
const nextUrlClone = nextUrl.clone()
|
||||
nextUrlClone.host = publicUrl.host
|
||||
nextUrlClone.hostname = publicUrl.hostname
|
||||
|
||||
const overviewUrl = overview[lang]
|
||||
return NextResponse.redirect(new URL(overviewUrl, nextUrlClone))
|
||||
}
|
||||
|
||||
const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}`, "")
|
||||
const { uid } = await resolveEntry(pathNameWithoutLang, lang)
|
||||
|
||||
if (!uid) {
|
||||
throw notFound(
|
||||
`Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}`
|
||||
)
|
||||
}
|
||||
|
||||
const headers = new Headers(request.headers)
|
||||
headers.set("x-uid", uid)
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const matcher: MiddlewareMatcher = (request) => {
|
||||
return [
|
||||
myPages.da,
|
||||
myPages.de,
|
||||
myPages.en,
|
||||
myPages.fi,
|
||||
myPages.no,
|
||||
myPages.sv,
|
||||
].includes(request.nextUrl.pathname)
|
||||
const lang = findLang(request.nextUrl.pathname)!
|
||||
return request.nextUrl.pathname.startsWith(myPages[lang])
|
||||
}
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export function badRequest(body: unknown | string = "Bad request") {
|
||||
export function badRequest(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 400,
|
||||
statusText: "Bad request",
|
||||
}
|
||||
|
||||
if (typeof body === "string") {
|
||||
return new NextResponse(body, resInit)
|
||||
}
|
||||
|
||||
return NextResponse.json(body, resInit)
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
export function notFound(body: unknown | string = "Not found") {
|
||||
export function notFound(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 404,
|
||||
statusText: "Not found",
|
||||
}
|
||||
|
||||
if (typeof body === "string") {
|
||||
return new NextResponse(body, resInit)
|
||||
}
|
||||
|
||||
return NextResponse.json(body, resInit)
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
export function internalServerError(
|
||||
body: unknown | string = "Internal Server Error"
|
||||
) {
|
||||
export function internalServerError(cause?: unknown) {
|
||||
const resInit = {
|
||||
status: 500,
|
||||
statusText: "Internal Server Error",
|
||||
}
|
||||
|
||||
if (typeof body === "string") {
|
||||
return new NextResponse(body, resInit)
|
||||
}
|
||||
|
||||
return NextResponse.json(body, resInit)
|
||||
return NextResponse.json(
|
||||
{
|
||||
cause,
|
||||
},
|
||||
resInit
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ export const getUserSchema = z.object({
|
||||
name: z.string(),
|
||||
language: z.string(),
|
||||
lastName: z.string(),
|
||||
membership: z.object({
|
||||
currentPoints: z.number(),
|
||||
expirationDate: z.string(),
|
||||
membershipNumber: z.string(),
|
||||
memberSince: z.string(),
|
||||
}),
|
||||
membership: z
|
||||
.object({
|
||||
currentPoints: z.number(),
|
||||
expirationDate: z.string(),
|
||||
membershipNumber: z.string(),
|
||||
memberSince: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
phoneNumber: z.string(),
|
||||
profileId: z.string(),
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ function fakingRequest<T>(payload: T): Promise<T> {
|
||||
export const userQueryRouter = router({
|
||||
get: protectedProcedure.query(async function ({ ctx }) {
|
||||
const apiResponse = await api.get(api.endpoints.v0.profile, {
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
@@ -164,10 +165,6 @@ export const userQueryRouter = router({
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
if (!apiJson.data?.length) {
|
||||
throw notFound(apiJson)
|
||||
}
|
||||
|
||||
const verifiedData = getStaysSchema.safeParse(apiJson)
|
||||
if (!verifiedData.success) {
|
||||
throw internalServerError(verifiedData.error)
|
||||
|
||||
@@ -2,7 +2,11 @@ import { initTRPC } from "@trpc/server"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import { badRequestError, unauthorizedError } from "./errors/trpc"
|
||||
import {
|
||||
badRequestError,
|
||||
sessionExpiredError,
|
||||
unauthorizedError,
|
||||
} from "./errors/trpc"
|
||||
import { transformer } from "./transformer"
|
||||
|
||||
import type { Meta } from "@/types/trpc/meta"
|
||||
@@ -35,7 +39,7 @@ export const protectedProcedure = t.procedure.use(async function (opts) {
|
||||
}
|
||||
|
||||
if (session?.error === "RefreshAccessTokenError") {
|
||||
throw unauthorizedError()
|
||||
throw sessionExpiredError()
|
||||
}
|
||||
|
||||
if (!session?.user) {
|
||||
|
||||
@@ -13,6 +13,7 @@ const entryResolveSchema = z.object({
|
||||
})
|
||||
|
||||
export const validateEntryResolveSchema = z.object({
|
||||
all_account_page: entryResolveSchema,
|
||||
all_content_page: entryResolveSchema,
|
||||
all_loyalty_page: entryResolveSchema,
|
||||
all_current_blocks_page: entryResolveSchema,
|
||||
|
||||
@@ -28,9 +28,7 @@ export async function resolve(url: string, lang = Lang.en) {
|
||||
const validatedData = validateEntryResolveSchema.safeParse(data)
|
||||
|
||||
if (!validatedData.success) {
|
||||
console.error("Bad validation for `validateContentTypeDataSchema`")
|
||||
console.error(validatedData.error)
|
||||
throw internalServerError()
|
||||
throw internalServerError(validatedData.error)
|
||||
}
|
||||
|
||||
for (const value of Object.values(validatedData.data)) {
|
||||
|
||||
Reference in New Issue
Block a user