From 8e2fa896de6c058045cb970f27dc9c6c85fe0e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Mon, 27 Apr 2026 10:24:29 +0200 Subject: [PATCH] =?UTF-8?q?V=202.1.0=20=20Verbesserungen=20von=20Claude=20?= =?UTF-8?q?Code=20einge=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/werte/[id]/route.ts | 40 ++++++++++++++++++++++++------------- app/api/werte/route.ts | 6 +++--- app/login/actions.ts | 2 +- app/page.tsx | 2 ++ components/ChartsClient.tsx | 1 + components/WerteForm.tsx | 15 +++++++++----- lib/auth.ts | 22 +++----------------- lib/db.ts | 4 ++-- next.config.ts | 14 +++++++++++++ package-lock.json | 22 ++++++++++++++++++-- package.json | 4 +++- 11 files changed, 85 insertions(+), 47 deletions(-) diff --git a/app/api/werte/[id]/route.ts b/app/api/werte/[id]/route.ts index d83d673..0c71ec4 100644 --- a/app/api/werte/[id]/route.ts +++ b/app/api/werte/[id]/route.ts @@ -12,18 +12,30 @@ export async function PUT( const { id } = await context.params; const body: CreateWerteEntry = await request.json(); - const sql = `UPDATE ${TABLE} SET - Datum = '${body.Datum}', - Zeit = '${body.Zeit}', - Zucker = ${body.Zucker || 'NULL'}, - Essen = ${body.Essen ? `'${body.Essen.replace(/'/g, "''")}'` : 'NULL'}, - Gewicht = ${body.Gewicht || 'NULL'}, - DruckS = ${body.DruckS || 'NULL'}, - DruckD = ${body.DruckD || 'NULL'}, - Puls = ${body.Puls || 'NULL'} - WHERE ID = ${parseInt(id, 10)}`; - - const result = await query(sql); + const sql = `UPDATE ${TABLE} SET + Datum = ?, + Zeit = ?, + Zucker = ?, + Essen = ?, + Gewicht = ?, + DruckS = ?, + DruckD = ?, + Puls = ? + WHERE ID = ?`; + + const params = [ + body.Datum, + body.Zeit, + body.Zucker || null, + body.Essen || null, + body.Gewicht ? parseFloat(parseFloat(String(body.Gewicht)).toFixed(1)) : null, + body.DruckS || null, + body.DruckD || null, + body.Puls || null, + parseInt(id, 10), + ]; + + const result = await query(sql, params); return NextResponse.json({ success: true, data: result }); } catch (error) { @@ -42,8 +54,8 @@ export async function DELETE( try { const { id } = await context.params; - const sql = `DELETE FROM ${TABLE} WHERE ID = ${parseInt(id, 10)}`; - const result = await query(sql); + const sql = `DELETE FROM ${TABLE} WHERE ID = ?`; + const result = await query(sql, [parseInt(id, 10)]); return NextResponse.json({ success: true, data: result }); } catch (error) { diff --git a/app/api/werte/route.ts b/app/api/werte/route.ts index f143023..1891c83 100644 --- a/app/api/werte/route.ts +++ b/app/api/werte/route.ts @@ -16,10 +16,10 @@ export async function GET(request: NextRequest) { let params: (string | number)[] = []; if (from && to) { - sql = `SELECT ID, DATE_FORMAT(Datum, '%Y-%m-%d') as Datum, Zeit, Zucker, Essen, Gewicht, DruckD, DruckS, Puls FROM ${TABLE} WHERE Datum BETWEEN ? AND ? ORDER BY Datum ASC, Zeit ASC`; + sql = `SELECT ID, DATE_FORMAT(Datum, '%Y-%m-%d') as Datum, Zeit, Zucker, Essen, Gewicht, DruckS, DruckD, Puls FROM ${TABLE} WHERE Datum BETWEEN ? AND ? ORDER BY Datum ASC, Zeit ASC`; params = [from, to]; } else { - sql = `SELECT ID, DATE_FORMAT(Datum, '%Y-%m-%d') as Datum, Zeit, Zucker, Essen, Gewicht, DruckD, DruckS, Puls FROM ${TABLE} ORDER BY Datum DESC, Zeit DESC LIMIT ${limit}`; + sql = `SELECT ID, DATE_FORMAT(Datum, '%Y-%m-%d') as Datum, Zeit, Zucker, Essen, Gewicht, DruckS, DruckD, Puls FROM ${TABLE} ORDER BY Datum DESC, Zeit DESC LIMIT ${limit}`; } const rows = await query(sql, params); @@ -55,7 +55,7 @@ export async function POST(request: NextRequest) { body.Zeit, body.Zucker || null, body.Essen || null, - body.Gewicht || null, + body.Gewicht ? parseFloat(parseFloat(String(body.Gewicht)).toFixed(1)) : null, body.DruckS || null, body.DruckD || null, body.Puls || null, diff --git a/app/login/actions.ts b/app/login/actions.ts index 12030d9..5438a3c 100644 --- a/app/login/actions.ts +++ b/app/login/actions.ts @@ -12,7 +12,7 @@ export async function login(prevState: any, formData: FormData) { return { error: 'Bitte Benutzername und Passwort eingeben' }; } - const isValid = verifyCredentials(username, password); + const isValid = await verifyCredentials(username, password); if (!isValid) { return { error: 'Ungültige Anmeldedaten' }; diff --git a/app/page.tsx b/app/page.tsx index 25be3fe..37362bb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -25,6 +25,7 @@ export default function Home() { 'Cache-Control': 'no-cache', }, }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (data.success && isMounted) { setEntries(data.data); @@ -50,6 +51,7 @@ export default function Home() { 'Cache-Control': 'no-cache', }, }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (data.success) { setEntries(data.data); diff --git a/components/ChartsClient.tsx b/components/ChartsClient.tsx index 9a97dc6..75da2d7 100644 --- a/components/ChartsClient.tsx +++ b/components/ChartsClient.tsx @@ -82,6 +82,7 @@ export default function ChartsClient() { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' }, }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (data.success) { setEntries(data.data); diff --git a/components/WerteForm.tsx b/components/WerteForm.tsx index 3c753dd..bedbc99 100644 --- a/components/WerteForm.tsx +++ b/components/WerteForm.tsx @@ -197,9 +197,10 @@ export default function WerteForm({ onSuccess, selectedEntry }: WerteFormProps) setFormData(prev => ({ ...prev, Zucker: e.target.value }))} - maxLength={4} /> @@ -216,36 +217,40 @@ export default function WerteForm({ onSuccess, selectedEntry }: WerteFormProps) type="number" step="0.1" className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + min={0} + max={300} value={formData.Gewicht} onChange={(e) => setFormData(prev => ({ ...prev, Gewicht: e.target.value }))} - maxLength={4} /> setFormData(prev => ({ ...prev, DruckS: e.target.value }))} - maxLength={4} /> setFormData(prev => ({ ...prev, DruckD: e.target.value }))} - maxLength={4} /> setFormData(prev => ({ ...prev, Puls: e.target.value }))} - maxLength={4} /> diff --git a/lib/auth.ts b/lib/auth.ts index 7b1fc5b..b494be3 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,20 +1,10 @@ -/** - * Reusable authentication library - * Configure users via environment variables in .env: - * AUTH_USERS=user1:$2a$10$hash1,user2:$2a$10$hash2 - * - * Use scripts/generate-password.js to generate password hashes - */ +import bcrypt from 'bcryptjs'; export interface User { username: string; password: string; } -/** - * Parse users from environment variable - * Format: username:password,username2:password2 - */ export function getUsers(): User[] { const usersString = process.env.AUTH_USERS || ''; if (!usersString) { @@ -30,21 +20,15 @@ export function getUsers(): User[] { .filter((user) => user.username && user.password); } -/** - * Verify user credentials - */ -export function verifyCredentials(username: string, password: string): boolean { +export async function verifyCredentials(username: string, password: string): Promise { const users = getUsers(); const user = users.find(u => u.username === username); if (!user) { return false; } - return user.password === password; + return bcrypt.compare(password, user.password); } -/** - * Check if authentication is enabled - */ export function isAuthEnabled(): boolean { return !!process.env.AUTH_USERS; } diff --git a/lib/db.ts b/lib/db.ts index ec45c18..bea1be7 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -4,8 +4,8 @@ import type { QueryResult } from 'mysql2/promise'; // Database configuration const dbConfig = { host: process.env.DB_HOST || 'mydbase_mysql', - user: process.env.DB_USER || 'root', - password: process.env.DB_PASS || 'SFluorit', + user: process.env.DB_USER, + password: process.env.DB_PASS, database: process.env.DB_NAME || 'RXF', waitForConnections: true, connectionLimit: 10, diff --git a/next.config.ts b/next.config.ts index 0ea3461..e19be29 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,6 +6,20 @@ const nextConfig: NextConfig = { turbopack: { root: path.resolve(__dirname), }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Content-Type-Options', value: 'nosniff' }, + { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, + { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' }, + { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, + ], + }, + ]; + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index cecd2fd..616c156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { "name": "werte_next", - "version": "1.2.0", + "version": "2.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "werte_next", - "version": "1.2.0", + "version": "2.0.3", "dependencies": { + "bcryptjs": "^3.0.3", "highcharts": "^12.5.0", "highcharts-react-official": "^3.2.3", "jose": "^6.1.3", @@ -18,6 +19,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1529,6 +1531,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2461,6 +2470,15 @@ "node": ">=6.0.0" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", diff --git a/package.json b/package.json index a80fc9e..c756289 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "werte_next", - "version": "2.0.3", + "version": "2.1.0", "private": true, "scripts": { "dev": "next dev", @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "bcryptjs": "^3.0.3", "highcharts": "^12.5.0", "highcharts-react-official": "^3.2.3", "jose": "^6.1.3", @@ -19,6 +20,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19",