From 325c07b469adafbaf2df99d078274ba684710bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Mon, 25 May 2026 09:21:38 +0200 Subject: [PATCH] =?UTF-8?q?V=201.2.2=20=20Login-Seite:=20bcrypt-Passw?= =?UTF-8?q?=C3=B6rter,=20Passwort-Toggle,=20Footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 31 ++++++++++++++++++++++++ app/login/actions.ts | 4 ++-- app/login/page.tsx | 56 +++++++++++++++++++++++++++++++++++--------- lib/auth.ts | 14 +++++------ package-lock.json | 22 +++++++++++++++-- package.json | 4 +++- 6 files changed, 107 insertions(+), 24 deletions(-) diff --git a/app/globals.css b/app/globals.css index f109252..133b981 100644 --- a/app/globals.css +++ b/app/globals.css @@ -283,3 +283,34 @@ main { } .login-submit:hover:not(:disabled) { background-color: #6a9fc5; } .login-submit:disabled { opacity: 0.5; cursor: not-allowed; } + +.login-password-wrapper { + position: relative; +} +.login-input-password { + padding-right: 40px; +} +.login-eye-btn { + position: absolute; + top: 0; bottom: 0; right: 0; + padding: 0 12px; + display: flex; + align-items: center; + background: none; + border: none; + cursor: pointer; + color: #6b7280; +} +.login-eye-btn:hover { color: #1f2937; } + +.login-footer { + margin-top: 32px; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.875rem; + color: #4b5563; + padding: 0 16px; +} +.login-footer a { color: #2563eb; } +.login-footer a:hover { text-decoration: underline; } diff --git a/app/login/actions.ts b/app/login/actions.ts index 5dabed6..4ff702b 100644 --- a/app/login/actions.ts +++ b/app/login/actions.ts @@ -4,7 +4,7 @@ import { verifyCredentials } from '@/lib/auth'; import { createSession, deleteSession } from '@/lib/session'; import { redirect } from 'next/navigation'; -export async function login(prevState: any, formData: FormData) { +export async function login(prevState: { error: string } | null | undefined, formData: FormData) { const username = formData.get('username') as string; const password = formData.get('password') as string; @@ -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/login/page.tsx b/app/login/page.tsx index e4f3589..bfebb5a 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,10 +1,15 @@ 'use client'; -import { useActionState } from 'react'; +import { useActionState, useState } from 'react'; import { login } from './actions'; +import packageJson from '@/package.json'; export default function LoginPage() { const [state, loginAction, isPending] = useActionState(login, undefined); + const [showPassword, setShowPassword] = useState(false); + + const version = packageJson.version; + const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); return (
@@ -38,16 +43,36 @@ export default function LoginPage() { - +
+ + +
{state?.error && ( @@ -66,6 +91,15 @@ export default function LoginPage() { + + ); diff --git a/lib/auth.ts b/lib/auth.ts index d117b19..d7198ad 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,8 +1,4 @@ -/** - * Authentifizierungsbibliothek - * Benutzer via Umgebungsvariable konfigurieren: - * AUTH_USERS=user1:passwort1,user2:passwort2 - */ +import bcrypt from 'bcryptjs'; export interface User { username: string; @@ -24,11 +20,13 @@ export function getUsers(): User[] { .filter((user) => user.username && user.password); } -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; + if (!user) { + return false; + } + return bcrypt.compare(password, user.password); } export function isAuthEnabled(): boolean { diff --git a/package-lock.json b/package-lock.json index 9763992..8797da8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { "name": "tabletten_next", - "version": "1.0.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tabletten_next", - "version": "1.0.0", + "version": "1.2.1", "dependencies": { + "bcryptjs": "^3.0.3", "jose": "^6.2.1", "moment": "^2.30.1", "mysql2": "^3.19.1", @@ -18,6 +19,7 @@ "react-dom": "19.2.3" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/node-schedule": "^2.1.8", "@types/nodemailer": "^7.0.11", @@ -1257,6 +1259,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", @@ -2226,6 +2235,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 5fae618..2787af2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tabletten_next", - "version": "1.2.1", + "version": "1.2.2", "private": true, "scripts": { "dev": "next dev", @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "bcryptjs": "^3.0.3", "jose": "^6.2.1", "moment": "^2.30.1", "mysql2": "^3.19.1", @@ -19,6 +20,7 @@ "react-dom": "19.2.3" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/node-schedule": "^2.1.8", "@types/nodemailer": "^7.0.11",