Add /client launcher page (auth-gated SSO ticket + embed)

Faithful to AtomCMS's NitroController: requires a session (redirects to /login),
issues + persists the SSO ticket via issueSsoTicket (auth_ticket + ip_current
from x-forwarded-for), and embeds the configured client URL with ?sso=. Ties
auth + SSO + settings together.

Verified: tsc exit 0, next build exit 0 (/client route).
This commit is contained in:
Simo committed 2026-06-27 16:44:30 +02:00
1 parent 6e34d485d4
commit d9a8ce532f
1 file changed
+49
+49
View File
@@ -0,0 +1,49 @@
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { issueSsoTicket } from "@/lib/auth/sso-ticket";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
export const dynamic = "force-dynamic";
export default async function ClientPage() {
const session = await auth();
if (!session?.user?.id) redirect("/login");
const userId = Number(session.user.id);
const [hotelName, clientUrl] = await Promise.all([
siteSettings.get("hotel_name", "Atom"),
siteSettings.get("nitro_client_url", ""),
]);
const hdrs = await headers();
const ip =
hdrs.get("x-forwarded-for")?.split(",")[0]?.trim() ?? hdrs.get("x-real-ip") ?? "0.0.0.0";
// Faithful to AtomCMS's NitroController: issue (and persist) the SSO ticket on
// the launcher render, then hand it to the client.
const ticket = await issueSsoTicket(prisma, userId, hotelName ?? "Atom", ip);
const src = clientUrl
? `${clientUrl}${clientUrl.includes("?") ? "&" : "?"}sso=${encodeURIComponent(ticket)}`
: "";
return (
<main>
<h1>Launching {hotelName ?? "Atom"}…</h1>
{src ? (
<iframe
title="Hotel client"
src={src}
style={{ width: "100%", height: "80vh", border: 0, borderRadius: 10 }}
/>
) : (
<p className="muted">
No client URL configured (set <code>nitro_client_url</code> in settings). SSO ticket
issued for this session: <code>{ticket}</code>
</p>
)}
</main>
);
}