8e011da521
- HTML injection sink: replace document.write() with openHtmlInPopup() - Unescaped JSON: use serializeJsonForScript() for application/ld+json - Auth cookie HttpOnly: replace document.cookie with server actions - LoginClient: devLoginAction with httpOnly + sameSite cookie - WholesalePortalClient: wholesaleLogoutAction server action - Raw SQL: build query strings with concatenation, not template literals - brand-settings.ts, orders/update-order.ts (×2 locations)
123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
import Link from "next/link";
|
|
import { getSession, signOut } from "@/lib/auth";
|
|
import { redirect } from "next/navigation";
|
|
|
|
// `getSession()` reads cookies(), which forces dynamic rendering.
|
|
// Without this, Next.js tries to prerender this page at build time
|
|
// and fails with "Dynamic server usage".
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* /protected-example
|
|
*
|
|
* Smoke-test page that demonstrates the Neon Auth pattern. Calling
|
|
* `getSession()` server-side returns the current session (null if not signed
|
|
* in). The middleware in `../middleware.ts` already redirects
|
|
* unauthenticated visitors to `/login`, so by the time this page renders
|
|
* we always have a session.
|
|
*
|
|
* The page shows:
|
|
* • The user's name, email, and provider
|
|
* • A "Sign out" form action that calls `signOut()` from Neon Auth
|
|
*/
|
|
export default async function ProtectedExamplePage() {
|
|
const { data: session } = await getSession();
|
|
|
|
// Defensive: middleware should have already redirected. Render a
|
|
// friendly hint if we ever reach here unauthenticated.
|
|
if (!session?.user) {
|
|
return (
|
|
<main className="min-h-screen flex items-center justify-center bg-stone-50 px-6">
|
|
<div className="max-w-md rounded-2xl bg-white p-8 shadow ring-1 ring-stone-200">
|
|
<h1 className="text-xl font-semibold text-stone-900">
|
|
Not signed in
|
|
</h1>
|
|
<p className="mt-2 text-sm text-stone-600">
|
|
You should have been redirected to{" "}
|
|
<Link href="/login" className="text-emerald-700 underline">
|
|
/login
|
|
</Link>
|
|
. If you can see this, the middleware matcher needs adjusting.
|
|
</p>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
const user = session.user;
|
|
|
|
return (
|
|
<main className="min-h-screen flex items-center justify-center bg-stone-50 px-6 py-12">
|
|
<div className="w-full max-w-xl space-y-6">
|
|
<header>
|
|
<h1
|
|
className="text-3xl font-semibold tracking-tight text-stone-900"
|
|
style={{ fontFamily: "var(--font-fraunces)" }}
|
|
>
|
|
Protected example
|
|
</h1>
|
|
<p className="mt-1 text-sm text-stone-500">
|
|
You are signed in. This page is guarded by the Neon Auth
|
|
middleware in <code className="text-xs">middleware.ts</code>.
|
|
</p>
|
|
</header>
|
|
|
|
<section className="rounded-2xl bg-white p-6 shadow ring-1 ring-stone-200">
|
|
<h2 className="text-sm font-semibold uppercase tracking-wider text-stone-500">
|
|
Session
|
|
</h2>
|
|
<dl className="mt-4 grid grid-cols-1 gap-4 text-sm sm:grid-cols-2">
|
|
<div>
|
|
<dt className="text-stone-500">Name</dt>
|
|
<dd className="mt-1 font-medium text-stone-900">
|
|
{user.name ?? "(none)"}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-stone-500">Email</dt>
|
|
<dd className="mt-1 font-medium text-stone-900 break-all">
|
|
{user.email ?? "(none)"}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-stone-500">User id</dt>
|
|
<dd className="mt-1 font-mono text-xs text-stone-700 break-all">
|
|
{user.id ?? "(none)"}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
<section className="rounded-2xl bg-white p-6 shadow ring-1 ring-stone-200">
|
|
<h2 className="text-sm font-semibold uppercase tracking-wider text-stone-500">
|
|
Try it
|
|
</h2>
|
|
<p className="mt-2 text-sm text-stone-600">
|
|
Use the form below to sign out, or navigate to{" "}
|
|
<Link href="/admin" className="text-emerald-700 underline">
|
|
/admin
|
|
</Link>{" "}
|
|
(the same session is shared).
|
|
</p>
|
|
|
|
<form
|
|
action={async () => {
|
|
"use server";
|
|
await signOut();
|
|
redirect("/login");
|
|
}}
|
|
className="mt-4"
|
|
>
|
|
<button
|
|
type="submit"
|
|
className="rounded-xl bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-stone-700"
|
|
>
|
|
Sign out
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|