feat: update AUTH_SECRET handling and improve session management
Build and Push Docker Image / build (push) Successful in 1m23s
Build and Push Docker Image / build (push) Successful in 1m23s
fix: add credentials to POST request in NewCompanyPage fix: update Docker image pull policy for app service
This commit is contained in:
+5
-2
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function NewCompanyPage() {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
|
||||
+29
-11
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user