/** * Authenticated HTTP smoke against a running dev server — WITHOUT typing passwords: * builds the session user via finalizeIdentityLogin (same code path as a real login after * password/MFA checks) and encodes the Auth.js session cookie with AUTH_SECRET. * * Usage (from repo root, dev server running): * BASE=http://localhost:3099 npx tsx scripts/smoke-auth.ts */ import "dotenv/config"; import { encode } from "next-auth/jwt"; import { prisma } from "../src/server/db"; import { finalizeIdentityLogin } from "../src/server/auth"; const BASE = process.env.BASE ?? "http://localhost:3099"; const COOKIE = BASE.startsWith("https") ? "__Secure-authjs.session-token" : "authjs.session-token"; type Check = { path: string; expect?: number[]; mustContain?: string }; const PLAN: Record = { "backoffice@demo.example": [ { path: "/dashboard", expect: [200] }, { path: "/work-orders", expect: [200] }, { path: "/imports", expect: [200] }, { path: "/customers", expect: [200] }, { path: "/sites", expect: [200] }, { path: "/teams", expect: [200] }, { path: "/reports", expect: [200] }, { path: "/documents", expect: [200] }, { path: "/notifications", expect: [200] }, { path: "/search?q=a", expect: [200] }, { path: "/work-orders/emergency-review", expect: [200] }, { path: "/settings/order-types", expect: [200] }, { path: "/settings/audit", expect: [200, 403, 307] }, ], "monteur@demo.example": [ { path: "/", expect: [307, 308] }, { path: "/m", expect: [200] }, { path: "/m/orders", expect: [200] }, { path: "/m/emergency", expect: [200] }, { path: "/m/sync", expect: [200] }, { path: "/m/profile", expect: [200] }, ], }; async function cookieFor(email: string): Promise { const identity = await prisma.identity.findUnique({ where: { email } }); if (!identity) throw new Error(`identity ${email} not found (seed?)`); const user = await finalizeIdentityLogin(identity.id, "demo"); if (!user) throw new Error(`no active membership for ${email}`); const token = { sub: user.id, name: user.name, email: user.email, userId: user.id, identityId: user.identityId, tenantId: user.tenantId, tenantSlug: user.tenantSlug, activeMembershipId: user.activeMembershipId, memberships: user.memberships, roles: user.roles, permissions: user.permissions, isPlatformAdmin: user.isPlatformAdmin, mfaEnrolled: user.mfaEnrolled, }; const value = await encode({ token, secret: process.env.AUTH_SECRET!, salt: COOKIE, maxAge: 60 * 30 }); return `${COOKIE}=${value}`; } async function main() { let failures = 0; for (const [email, checks] of Object.entries(PLAN)) { const cookie = await cookieFor(email); console.log(`\n== ${email}`); for (const c of checks) { const res = await fetch(BASE + c.path, { headers: { cookie }, redirect: "manual" }); const body = res.status === 200 ? await res.text() : ""; const errorPage = /Application error|Internal Server Error|NEXT_REDIRECT|Unhandled Runtime Error/i.test(body); const okStatus = !c.expect || c.expect.includes(res.status); const okText = !c.mustContain || body.includes(c.mustContain); const ok = okStatus && okText && !errorPage; if (!ok) failures++; const loc = res.headers.get("location"); console.log(`${ok ? "✓" : "✗"} ${String(res.status).padEnd(3)} ${c.path}${loc ? ` → ${loc}` : ""}${errorPage ? " [error page]" : ""}`); } } await prisma.$disconnect(); console.log(failures ? `\n${failures} Fehler` : "\nOK"); process.exit(failures ? 1 : 0); } main().catch((err) => { console.error(err); process.exit(1); });