All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 1m17s
Weiterleitungen hingen am containerinternen Port 3000: Die Middleware baute sie aus req.nextUrl.origin. Sie wertet jetzt X-Forwarded-Host und -Proto aus, ersatzweise den Host-Header. NEXTAUTH_URL ist damit optional und wird nicht mehr auf localhost:3000 vorbelegt. Der Container legt beim ersten Start selbst einen Admin an. Ohne ADMIN_PASSWORD wird ein Zufallspasswort erzeugt und einmalig ins Log geschrieben; bisher musste seed-admin von Hand nachgeholt werden. Ein mit anderem NEXTAUTH_SECRET verschlüsseltes Cookie erzeugte bei jedem Request einen JWTSessionError samt Stacktrace. Die Middleware löscht das Cookie jetzt beim Umleiten, der Logger meldet den Fall in einer Zeile. Die Prüfung nutzt das type-Feld, da Klassennamen im Produktions-Build minifiziert sind. Getestet auf Port 8080: Weiterleitung nach localhost:8080/auth/signin, Passwort im Erststart-Log, veraltetes Cookie mit Max-Age=0 entfernt, keine Stacktraces mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import NextAuth, { NextAuthConfig } from "next-auth"
|
||
import CredentialsProvider from "next-auth/providers/credentials"
|
||
import { PrismaAdapter } from "@auth/prisma-adapter"
|
||
import { PrismaClient } from "@prisma/client"
|
||
import bcrypt from "bcryptjs"
|
||
|
||
const prisma = new PrismaClient()
|
||
|
||
export const authConfig: NextAuthConfig = {
|
||
adapter: PrismaAdapter(prisma),
|
||
providers: [
|
||
CredentialsProvider({
|
||
name: "credentials",
|
||
credentials: {
|
||
email: { label: "Email", type: "email" },
|
||
password: { label: "Password", type: "password" }
|
||
},
|
||
async authorize(credentials) {
|
||
if (!credentials?.email || !credentials?.password) {
|
||
return null
|
||
}
|
||
|
||
const user = await prisma.user.findUnique({
|
||
where: {
|
||
email: credentials.email as string
|
||
}
|
||
})
|
||
|
||
if (!user || !user.isActive) {
|
||
return null
|
||
}
|
||
|
||
const isPasswordValid = await bcrypt.compare(
|
||
credentials.password as string,
|
||
user.password
|
||
)
|
||
|
||
if (!isPasswordValid) {
|
||
return null
|
||
}
|
||
|
||
return {
|
||
id: user.id.toString(),
|
||
email: user.email,
|
||
name: user.name,
|
||
role: user.role,
|
||
}
|
||
}
|
||
})
|
||
],
|
||
session: {
|
||
strategy: "jwt"
|
||
},
|
||
callbacks: {
|
||
async jwt({ token, user }) {
|
||
if (user) {
|
||
token.role = user.role
|
||
}
|
||
return token
|
||
},
|
||
async session({ session, token }) {
|
||
if (token) {
|
||
session.user.id = token.sub ?? ""
|
||
session.user.role = token.role as string
|
||
}
|
||
return session
|
||
}
|
||
},
|
||
pages: {
|
||
signIn: "/auth/signin",
|
||
},
|
||
// Selbst gehostet: Auth.js vertraut sonst nur Vercel-Hosts und lehnt
|
||
// Anfragen hinter Reverse-Proxy/Container mit UntrustedHost ab.
|
||
trustHost: true,
|
||
logger: {
|
||
error(error) {
|
||
// Im Produktions-Build ist der Klassenname minifiziert. Auth.js setzt
|
||
// zusätzlich ein `type`-Feld aus einem String-Literal, das erhalten
|
||
// bleibt; die Fehlermeldung verlinkt denselben Bezeichner.
|
||
const type = (error as { type?: string }).type || error.name
|
||
const isStaleSession =
|
||
type === "JWTSessionError" ||
|
||
/#jwtsessionerror/i.test(error.message || "")
|
||
|
||
// Ein Cookie, das mit einem anderen NEXTAUTH_SECRET verschlüsselt wurde,
|
||
// ist kein Serverfehler, sondern eine veraltete Sitzung. Die Middleware
|
||
// löscht das Cookie beim nächsten Aufruf; hier reicht eine knappe Zeile
|
||
// statt eines Stacktraces bei jedem einzelnen Request.
|
||
if (isStaleSession) {
|
||
console.warn(
|
||
"[auth] Veraltetes Sitzungs-Cookie verworfen (mit einem anderen NEXTAUTH_SECRET verschlüsselt) – wird beim nächsten Aufruf entfernt."
|
||
)
|
||
return
|
||
}
|
||
|
||
console.error(`[auth] ${type}: ${error.message}`)
|
||
},
|
||
warn(code) {
|
||
console.warn(`[auth] ${code}`)
|
||
},
|
||
debug() {},
|
||
},
|
||
// Kein Fallback-Secret: fehlt NEXTAUTH_SECRET, soll der Start fehlschlagen,
|
||
// statt still mit einem öffentlich bekannten Wert zu signieren.
|
||
secret: process.env.NEXTAUTH_SECRET!
|
||
}
|
||
|
||
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig) |