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",