diff --git a/app/portal/account/page.tsx b/app/portal/account/page.tsx index 9cd048ed..57de52d2 100644 --- a/app/portal/account/page.tsx +++ b/app/portal/account/page.tsx @@ -2,7 +2,7 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { dxGET } from "@/lib/directus"; -import AccountClient from "./AccountClient"; // client component below +import AccountClient from "./AccountClient"; type Me = { id: string; @@ -14,7 +14,6 @@ type Me = { avatar?: { id: string; filename_download?: string } | string | null; }; -// Next 15 cookies() is async export default async function Page() { const jar = await cookies(); const token = jar.get("ma_at")?.value; @@ -23,7 +22,6 @@ export default async function Page() { } const bearer = `Bearer ${token}`; - // READ-ONLY fields only; no password calls here, ever. const fields = "id,username,first_name,last_name,email,location,avatar.id,avatar.filename_download"; @@ -34,13 +32,13 @@ export default async function Page() { const res = await dxGET(`/users/me?fields=${encodeURIComponent(fields)}`, bearer); me = (res?.data ?? res) as Me; } catch (e: any) { - // If token is stale, push to sign-in; otherwise show a friendly message + const status = e?.status ?? 0; const msg = String(e?.message || ""); - if (/401|403|unauth|expired|credential/i.test(msg)) { - redirect(`/auth/sign-in?next=${encodeURIComponent("/portal/account")}`); - } else { - loadError = "Couldn’t load your profile. Please try again."; + // Only force reauth on auth errors + if (status === 401 || status === 403 || /unauth|expired|credential/i.test(msg)) { + redirect(`/auth/sign-in?reauth=1&next=${encodeURIComponent("/portal/account")}`); } + loadError = "Couldn’t load your profile. Please try again."; } return ( diff --git a/middleware.ts b/middleware.ts index a6abe711..5b726e74 100644 --- a/middleware.ts +++ b/middleware.ts @@ -86,6 +86,12 @@ import { NextResponse, NextRequest } from "next/server"; const isAuthRoute = pathname.startsWith("/auth/"); const isProtected = !isPublicPath(pathname); + // NEW: allow explicit reauth flow even if a (possibly stale) token cookie exists + const forceAuth = + isAuthRoute && + (url.searchParams.get("reauth") === "1" || + url.searchParams.get("force") === "1"); + // If unauthenticated and the route is protected, send to sign-in if (!token && isProtected) { return kickToSignIn(req); @@ -97,8 +103,8 @@ import { NextResponse, NextRequest } from "next/server"; const expired = !exp || exp * 1000 <= Date.now(); // If it's an auth route and token looks valid, keep your existing UX: - // bounce away from auth pages. - if (isAuthRoute && !expired) { + // bounce away from auth pages — unless this is a forced reauth. + if (isAuthRoute && !expired && !forceAuth) { url.pathname = "/portal"; url.search = ""; return NextResponse.redirect(url); @@ -149,7 +155,7 @@ import { NextResponse, NextRequest } from "next/server"; } } - // If signed-in and visiting /auth/* but token is expired/invalid, fall through (let them sign in). + // If signed-in and visiting /auth/* but token is expired/invalid or reauth was requested, fall through (let them sign in). // If public or already validated, proceed. return NextResponse.next(); }