From 38c8304336c47c31ebb629ceeba0622a7b93ad74 Mon Sep 17 00:00:00 2001 From: hwinkel Date: Sun, 3 May 2026 09:24:41 +0200 Subject: [PATCH] feat: update AUTH_SECRET handling and improve session management fix: add credentials to POST request in NewCompanyPage fix: update Docker image pull policy for app service --- .env.example | 7 +++-- app/components/invoice/invoice-form.tsx | 5 ++-- app/routes/companies.new.tsx | 1 + app/session.server.ts | 40 ++++++++++++++++++------- docker-compose.yml | 1 + 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index b3e1215..7a4ba17 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,11 @@ # Datenbank (für lokale Entwicklung) DATABASE_URL="mysql://user:password@localhost:3306/annas_rechnungsmanager" -# Session-Secret – zufälligen Wert generieren: openssl rand -base64 32 -AUTH_SECRET="HIER_ZUFAELLIGEN_WERT_EINSETZEN" +# Session-Secret – optional +# Wenn nicht gesetzt, wird ein zufälliger Wert generiert (empfohlen für Docker/Development) +# Bei Containerneustarts werden alle Sessions dann automatisch ungültig +# Für Production mit persistenter Session: openssl rand -base64 32 +AUTH_SECRET="" # Docker-Compose: Datenbank-Credentials DB_ROOT_PASSWORD="sicheres_root_passwort" diff --git a/app/components/invoice/invoice-form.tsx b/app/components/invoice/invoice-form.tsx index 55bfb25..58df6aa 100644 --- a/app/components/invoice/invoice-form.tsx +++ b/app/components/invoice/invoice-form.tsx @@ -8,7 +8,6 @@ import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { calcItemAmounts, calcItemAmountsKleinunternehmer, calcInvoiceTotals, formatCurrency, TAX_RATES } from "@/lib/tax"; import { Plus, Trash2 } from "lucide-react"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; interface Customer { id: string; @@ -301,7 +300,7 @@ export function InvoiceForm({ customers, companyId, onSubmit, defaultValues, def value={descValue} onChange={(e) => setValue(`items.${index}.description`, e.target.value)} onFocus={() => setOpenDropdown(index)} - onBlur={() => setOpenDropdown(null)} + onBlur={() => setTimeout(() => setOpenDropdown(null), 100)} placeholder="Leistungsbeschreibung" className="text-sm" autoComplete="off" @@ -315,7 +314,7 @@ export function InvoiceForm({ customers, companyId, onSubmit, defaultValues, def className="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 flex flex-col" onMouseDown={(e) => { e.preventDefault(); - setValue(`items.${index}.description`, s.description ?? s.name); + setValue(`items.${index}.description`, s.name); setValue(`items.${index}.unit`, s.unit ?? "Stück"); setValue(`items.${index}.unitPrice`, String(s.unitPrice)); setValue(`items.${index}.taxRate`, String(s.taxRate)); diff --git a/app/routes/companies.new.tsx b/app/routes/companies.new.tsx index 2792640..04cccce 100644 --- a/app/routes/companies.new.tsx +++ b/app/routes/companies.new.tsx @@ -19,6 +19,7 @@ export default function NewCompanyPage() { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), + credentials: "include", }); if (res.ok) { diff --git a/app/session.server.ts b/app/session.server.ts index b261466..bfafb89 100644 --- a/app/session.server.ts +++ b/app/session.server.ts @@ -1,10 +1,18 @@ import { createCookieSessionStorage, redirect } from "react-router"; import bcrypt from "bcryptjs"; +import { randomBytes } from "crypto"; import prisma from "@/lib/prisma.server"; import { log } from "@/lib/logger.server"; -if (!process.env.AUTH_SECRET) { - throw new Error("AUTH_SECRET environment variable is required"); +/** + * AUTH_SECRET wird nur aus .env gelesen, falls die Umgebungsvariable nicht existiert. + * Falls nicht gesetzt, wird eine zufällige generiert. + * Bei jedem Containerstart mit ephemerem Secret werden alle bestehenden Sessions invalidiert. + */ +const AUTH_SECRET = process.env.AUTH_SECRET || randomBytes(32).toString("base64"); + +if (!AUTH_SECRET) { + throw new Error("AUTH_SECRET could not be generated"); } const sessionStorage = createCookieSessionStorage({ @@ -14,7 +22,7 @@ const sessionStorage = createCookieSessionStorage({ maxAge: 60 * 60 * 4, // 4 Stunden path: "/", sameSite: "lax", - secrets: [process.env.AUTH_SECRET], + secrets: [AUTH_SECRET], secure: process.env.NODE_ENV === "production", }, }); @@ -64,14 +72,24 @@ export async function createUserSession( } export async function getUserSession(request: Request) { - const session = await sessionStorage.getSession( - request.headers.get("Cookie") - ); - return { - userId: session.get("userId") as string | undefined, - userName: session.get("userName") as string | undefined, - userRole: session.get("userRole") as string | undefined, - }; + try { + const session = await sessionStorage.getSession( + request.headers.get("Cookie") + ); + return { + userId: session.get("userId") as string | undefined, + userName: session.get("userName") as string | undefined, + userRole: session.get("userRole") as string | undefined, + }; + } catch (error) { + // Session-Cookie ist ungültig (z.B. nach Neustart mit neuem AUTH_SECRET) + // Gib eine leere Session zurück, damit der Nutzer zum Login weitergeleitet wird + return { + userId: undefined, + userName: undefined, + userRole: undefined, + }; + } } export async function requireUser(request: Request) { diff --git a/docker-compose.yml b/docker-compose.yml index 7551108..0868abb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: # registry.henryathome.home64.de/henry/annasrechnungsmanager:latest image: git.henryathome.home64.de/henry/annasrechnungsmanager:latest container_name: annas_app + pull_policy: always restart: unless-stopped ports: - "3000:3000"