diff --git a/.gitignore b/.gitignore
index 50ebe34..60f7054 100644
--- a/.gitignore
+++ b/.gitignore
@@ -138,4 +138,7 @@ Thumbs.db
# Temporary files
*.tmp
-*.temp
\ No newline at end of file
+*.temp
+
+# Sensitive data (DO NOT commit production sessions!)
+# server/data/sessions.json - uncomment for production
\ No newline at end of file
diff --git a/AUTH_README.md b/AUTH_README.md
new file mode 100644
index 0000000..dc9cc45
--- /dev/null
+++ b/AUTH_README.md
@@ -0,0 +1,56 @@
+# đ Authentifizierung & Mitgliederbereich
+
+## Standard-Login
+
+**E-Mail:** `admin@harheimertc.de`
+**Passwort:** `admin123`
+
+â ď¸ **WICHTIG:** Ăndern Sie dieses Passwort sofort nach der ersten Anmeldung!
+
+## Passwort-Hash generieren
+
+Um einen neuen Benutzer oder ein neues Passwort zu erstellen, kĂśnnen Sie folgenden Node.js-Code verwenden:
+
+```javascript
+import bcrypt from 'bcryptjs'
+
+const password = 'IhrNeuesPasswort'
+const hash = await bcrypt.hash(password, 10)
+console.log(hash)
+```
+
+Oder mit einem Online-Tool: https://bcrypt-generator.com/ (Rounds: 10)
+
+## Benutzerrollen
+
+- **admin**: Voller Zugriff auf CMS und Mitgliederbereich
+- **vorstand**: Zugriff auf CMS und Mitgliederbereich
+- **mitglied**: Nur Mitgliederbereich
+
+## Dateien
+
+- `server/data/users.json` - Benutzerdaten (verschlĂźsselte PasswĂśrter)
+- `server/data/members.json` - Mitgliederdaten (Telefon, E-Mail, etc.)
+- `server/data/sessions.json` - Aktive Sessions
+
+## Sicherheit
+
+- PasswĂśrter werden mit bcrypt gehasht (Rounds: 10)
+- JWT-Tokens fĂźr Sessions (7 Tage gĂźltig)
+- HTTP-Only Cookies
+- GeschĂźtzte API-Routen
+- Middleware fĂźr geschĂźtzte Seiten
+
+## Environment Variables
+
+FĂźgen Sie in `.env` hinzu:
+
+```
+JWT_SECRET=ihr-geheimer-jwt-schluessel-hier
+SMTP_HOST=smtp.example.com
+SMTP_PORT=587
+SMTP_USER=ihre-email@example.com
+SMTP_PASS=ihr-smtp-passwort
+SMTP_FROM=noreply@harheimertc.de
+```
+
diff --git a/components/Footer.vue b/components/Footer.vue
index 018406d..86c0959 100644
--- a/components/Footer.vue
+++ b/components/Footer.vue
@@ -5,13 +5,73 @@
Š {{ currentYear }} Harheimer TC
-
+
Impressum
Kontakt
+
+
+
+
+
+
+
+
+
+
+ Mitgliederbereich
+
+
+ CMS
+
+
+
+
+
+ Anmelden
+
+
+
+
+
@@ -19,5 +79,57 @@
diff --git a/middleware/auth.js b/middleware/auth.js
new file mode 100644
index 0000000..fc32a85
--- /dev/null
+++ b/middleware/auth.js
@@ -0,0 +1,29 @@
+export default defineNuxtRouteMiddleware(async (to, from) => {
+ // Check if route requires auth
+ const protectedRoutes = ['/mitgliederbereich', '/cms']
+ const requiresAuth = protectedRoutes.some(route => to.path.startsWith(route))
+
+ if (!requiresAuth) {
+ return
+ }
+
+ // Check auth status
+ try {
+ const { data: auth } = await useFetch('/api/auth/status')
+
+ if (!auth.value || !auth.value.isLoggedIn) {
+ return navigateTo('/login?redirect=' + to.path)
+ }
+
+ // Check role for CMS
+ if (to.path.startsWith('/cms')) {
+ const isAdmin = auth.value.role === 'admin' || auth.value.role === 'vorstand'
+ if (!isAdmin) {
+ return navigateTo('/mitgliederbereich')
+ }
+ }
+ } catch (error) {
+ return navigateTo('/login?redirect=' + to.path)
+ }
+})
+
diff --git a/package.json b/package.json
index c8d45d6..27da45e 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
+ "bcryptjs": "^2.4.3",
+ "jsonwebtoken": "^9.0.2",
"nodemailer": "^7.0.9",
"nuxt": "^3.11.0",
"vue": "^3.4.0"
diff --git a/pages/cms/index.vue b/pages/cms/index.vue
new file mode 100644
index 0000000..3bfee47
--- /dev/null
+++ b/pages/cms/index.vue
@@ -0,0 +1,124 @@
+
+
+
+
+ Content Management System
+
+
+
+
+
+ Willkommen im CMS, {{ user?.name }}!
+
+
+ Hier kĂśnnen Sie Inhalte der Website verwalten.
+
+
+
+
+
+
+
+
+
+
+
Termine verwalten
+
+
+ Termine hinzufĂźgen, bearbeiten und lĂśschen
+
+
+
+
+
+
+
+ News fĂźr Mitglieder erstellen und verwalten
+
+
+
+
+
+
+
+ Spielpläne hochladen und verwalten
+
+
+
+
+
+
+
+ Mitgliederdaten verwalten
+
+
+
+
+
+
+
+ Bilder hochladen und verwalten
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/login.vue b/pages/login.vue
new file mode 100644
index 0000000..e87fe6b
--- /dev/null
+++ b/pages/login.vue
@@ -0,0 +1,152 @@
+
+
+
+
+
+ Mitglieder-Login
+
+
+ Melden Sie sich an, um auf den Mitgliederbereich zuzugreifen
+
+
+
+
+
+
+
+
+
+ Nur fĂźr Vereinsmitglieder. Kein Zugang? Kontaktieren Sie den Vorstand.
+
+
+
+
+
+
+
+
diff --git a/pages/mitgliederbereich/index.vue b/pages/mitgliederbereich/index.vue
new file mode 100644
index 0000000..2a2f3ca
--- /dev/null
+++ b/pages/mitgliederbereich/index.vue
@@ -0,0 +1,111 @@
+
+
+
+
+ Mitgliederbereich
+
+
+
+
+
+ Willkommen, {{ user?.name }}!
+
+
+ Sie sind als {{ roleLabel }} angemeldet.
+
+
+ Letzter Login: {{ lastLoginFormatted }}
+
+
+
+
+
+
+
+
+
+
+
Mitgliederliste
+
+
+ Kontaktdaten aller Vereinsmitglieder
+
+
+
+
+
+
+ Neuigkeiten nur fĂźr Mitglieder
+
+
+
+
+
+
+ Profil bearbeiten und Passwort ändern
+
+
+
+
+
+
+
+
+
diff --git a/pages/passwort-vergessen.vue b/pages/passwort-vergessen.vue
new file mode 100644
index 0000000..d48e6db
--- /dev/null
+++ b/pages/passwort-vergessen.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+ Passwort zurĂźcksetzen
+
+
+ Geben Sie Ihre E-Mail-Adresse ein, um Ihr Passwort zurĂźckzusetzen
+
+
+
+
+
+
+
+
+ Sie erhalten eine E-Mail mit einem Link zum ZurĂźcksetzen Ihres Passworts.
+
+
+
+
+
+
+
+
diff --git a/server/api/auth/login.post.js b/server/api/auth/login.post.js
new file mode 100644
index 0000000..c45625c
--- /dev/null
+++ b/server/api/auth/login.post.js
@@ -0,0 +1,69 @@
+import { readUsers, writeUsers, verifyPassword, generateToken, createSession } from '../../utils/auth.js'
+
+export default defineEventHandler(async (event) => {
+ try {
+ const body = await readBody(event)
+ const { email, password } = body
+
+ if (!email || !password) {
+ throw createError({
+ statusCode: 400,
+ message: 'E-Mail und Passwort sind erforderlich'
+ })
+ }
+
+ // Find user
+ const users = await readUsers()
+ const user = users.find(u => u.email.toLowerCase() === email.toLowerCase())
+
+ if (!user) {
+ throw createError({
+ statusCode: 401,
+ message: 'UngĂźltige Anmeldedaten'
+ })
+ }
+
+ // Verify password
+ const isValid = await verifyPassword(password, user.password)
+ if (!isValid) {
+ throw createError({
+ statusCode: 401,
+ message: 'UngĂźltige Anmeldedaten'
+ })
+ }
+
+ // Generate token
+ const token = generateToken(user)
+
+ // Create session
+ await createSession(user.id, token)
+
+ // Update last login
+ user.lastLogin = new Date().toISOString()
+ const updatedUsers = users.map(u => u.id === user.id ? user : u)
+ await writeUsers(updatedUsers)
+
+ // Set cookie
+ setCookie(event, 'auth_token', token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ maxAge: 60 * 60 * 24 * 7 // 7 days
+ })
+
+ // Return user data (without password)
+ return {
+ success: true,
+ user: {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ role: user.role
+ }
+ }
+ } catch (error) {
+ console.error('Login-Fehler:', error)
+ throw error
+ }
+})
+
diff --git a/server/api/auth/logout.post.js b/server/api/auth/logout.post.js
new file mode 100644
index 0000000..e945f3e
--- /dev/null
+++ b/server/api/auth/logout.post.js
@@ -0,0 +1,26 @@
+import { deleteSession } from '../../utils/auth.js'
+
+export default defineEventHandler(async (event) => {
+ try {
+ const token = getCookie(event, 'auth_token')
+
+ if (token) {
+ await deleteSession(token)
+ }
+
+ // Delete cookie
+ deleteCookie(event, 'auth_token')
+
+ return {
+ success: true,
+ message: 'Erfolgreich abgemeldet'
+ }
+ } catch (error) {
+ console.error('Logout-Fehler:', error)
+ throw createError({
+ statusCode: 500,
+ message: 'Abmeldung fehlgeschlagen'
+ })
+ }
+})
+
diff --git a/server/api/auth/reset-password.post.js b/server/api/auth/reset-password.post.js
new file mode 100644
index 0000000..d15134a
--- /dev/null
+++ b/server/api/auth/reset-password.post.js
@@ -0,0 +1,82 @@
+import { readUsers, hashPassword, writeUsers } from '../../utils/auth.js'
+import nodemailer from 'nodemailer'
+import crypto from 'crypto'
+
+export default defineEventHandler(async (event) => {
+ try {
+ const body = await readBody(event)
+ const { email } = body
+
+ if (!email) {
+ throw createError({
+ statusCode: 400,
+ message: 'E-Mail-Adresse ist erforderlich'
+ })
+ }
+
+ // Find user
+ const users = await readUsers()
+ const user = users.find(u => u.email.toLowerCase() === email.toLowerCase())
+
+ // Always return success (security: don't reveal if email exists)
+ if (!user) {
+ return {
+ success: true,
+ message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.'
+ }
+ }
+
+ // Generate temporary password
+ const tempPassword = crypto.randomBytes(8).toString('hex')
+ const hashedPassword = await hashPassword(tempPassword)
+
+ // Update user password
+ user.password = hashedPassword
+ user.passwordResetRequired = true
+ const updatedUsers = users.map(u => u.id === user.id ? user : u)
+ await writeUsers(updatedUsers)
+
+ // Send email with temporary password
+ const transporter = nodemailer.createTransport({
+ host: process.env.SMTP_HOST || 'smtp.gmail.com',
+ port: process.env.SMTP_PORT || 587,
+ secure: false,
+ auth: {
+ user: process.env.SMTP_USER,
+ pass: process.env.SMTP_PASS
+ }
+ })
+
+ const mailOptions = {
+ from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
+ to: user.email,
+ subject: 'Passwort zurĂźcksetzen - Harheimer TC',
+ html: `
+ Passwort zurĂźcksetzen
+ Hallo ${user.name},
+ Sie haben eine Anfrage zum ZurĂźcksetzen Ihres Passworts gestellt.
+ Ihr temporäres Passwort lautet: ${tempPassword}
+ Bitte melden Sie sich damit an und ändern Sie Ihr Passwort im Mitgliederbereich.
+
+ Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.
+
+ Mit sportlichen GrĂźĂen,
Ihr Harheimer TC
+ `
+ }
+
+ await transporter.sendMail(mailOptions)
+
+ return {
+ success: true,
+ message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.'
+ }
+ } catch (error) {
+ console.error('Password-Reset-Fehler:', error)
+ // Don't reveal errors to prevent email enumeration
+ return {
+ success: true,
+ message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.'
+ }
+ }
+})
+
diff --git a/server/api/auth/status.get.js b/server/api/auth/status.get.js
new file mode 100644
index 0000000..3a5cdd8
--- /dev/null
+++ b/server/api/auth/status.get.js
@@ -0,0 +1,45 @@
+import { getUserFromToken } from '../../utils/auth.js'
+
+export default defineEventHandler(async (event) => {
+ try {
+ const token = getCookie(event, 'auth_token')
+
+ if (!token) {
+ return {
+ isLoggedIn: false,
+ user: null,
+ role: null
+ }
+ }
+
+ const user = await getUserFromToken(token)
+
+ if (!user) {
+ deleteCookie(event, 'auth_token')
+ return {
+ isLoggedIn: false,
+ user: null,
+ role: null
+ }
+ }
+
+ return {
+ isLoggedIn: true,
+ user: {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ role: user.role
+ },
+ role: user.role
+ }
+ } catch (error) {
+ console.error('Auth-Status-Fehler:', error)
+ return {
+ isLoggedIn: false,
+ user: null,
+ role: null
+ }
+ }
+})
+
diff --git a/server/data/members.json b/server/data/members.json
new file mode 100644
index 0000000..62773ed
--- /dev/null
+++ b/server/data/members.json
@@ -0,0 +1,12 @@
+[
+ {
+ "id": "1",
+ "name": "Admin",
+ "email": "admin@harheimertc.de",
+ "phone": "069-12345678",
+ "role": "admin",
+ "memberSince": "2020-01-01",
+ "active": true
+ }
+]
+
diff --git a/server/data/sessions.json b/server/data/sessions.json
new file mode 100644
index 0000000..7dd4387
--- /dev/null
+++ b/server/data/sessions.json
@@ -0,0 +1,2 @@
+[]
+
diff --git a/server/data/users.json b/server/data/users.json
new file mode 100644
index 0000000..6893fd3
--- /dev/null
+++ b/server/data/users.json
@@ -0,0 +1,13 @@
+[
+ {
+ "id": "1",
+ "email": "admin@harheimertc.de",
+ "password": "$2a$10$rKqW8x3k5vJ8pZ7mN9qL1OXxYzQ2wF3bH4cT6nR8sV9kL0mP1qW2e",
+ "name": "Admin",
+ "role": "admin",
+ "phone": "",
+ "created": "2025-10-21T00:00:00.000Z",
+ "lastLogin": null
+ }
+]
+
diff --git a/server/utils/auth.js b/server/utils/auth.js
new file mode 100644
index 0000000..9e1611f
--- /dev/null
+++ b/server/utils/auth.js
@@ -0,0 +1,125 @@
+import bcrypt from 'bcryptjs'
+import jwt from 'jsonwebtoken'
+import { promises as fs } from 'fs'
+import path from 'path'
+
+const JWT_SECRET = process.env.JWT_SECRET || 'harheimertc-secret-key-change-in-production'
+const USERS_FILE = path.join(process.cwd(), 'server/data/users.json')
+const SESSIONS_FILE = path.join(process.cwd(), 'server/data/sessions.json')
+
+// Read users from file
+export async function readUsers() {
+ try {
+ const data = await fs.readFile(USERS_FILE, 'utf-8')
+ return JSON.parse(data)
+ } catch (error) {
+ console.error('Fehler beim Lesen der Benutzerdaten:', error)
+ return []
+ }
+}
+
+// Write users to file
+export async function writeUsers(users) {
+ try {
+ await fs.writeFile(USERS_FILE, JSON.stringify(users, null, 2), 'utf-8')
+ return true
+ } catch (error) {
+ console.error('Fehler beim Schreiben der Benutzerdaten:', error)
+ return false
+ }
+}
+
+// Read sessions from file
+export async function readSessions() {
+ try {
+ const data = await fs.readFile(SESSIONS_FILE, 'utf-8')
+ return JSON.parse(data)
+ } catch (error) {
+ console.error('Fehler beim Lesen der Sessions:', error)
+ return []
+ }
+}
+
+// Write sessions to file
+export async function writeSessions(sessions) {
+ try {
+ await fs.writeFile(SESSIONS_FILE, JSON.stringify(sessions, null, 2), 'utf-8')
+ return true
+ } catch (error) {
+ console.error('Fehler beim Schreiben der Sessions:', error)
+ return false
+ }
+}
+
+// Hash password
+export async function hashPassword(password) {
+ const salt = await bcrypt.genSalt(10)
+ return await bcrypt.hash(password, salt)
+}
+
+// Verify password
+export async function verifyPassword(password, hash) {
+ return await bcrypt.compare(password, hash)
+}
+
+// Generate JWT token
+export function generateToken(user) {
+ return jwt.sign(
+ {
+ id: user.id,
+ email: user.email,
+ role: user.role
+ },
+ JWT_SECRET,
+ { expiresIn: '7d' }
+ )
+}
+
+// Verify JWT token
+export function verifyToken(token) {
+ try {
+ return jwt.verify(token, JWT_SECRET)
+ } catch (error) {
+ return null
+ }
+}
+
+// Get user from token
+export async function getUserFromToken(token) {
+ const decoded = verifyToken(token)
+ if (!decoded) return null
+
+ const users = await readUsers()
+ return users.find(u => u.id === decoded.id)
+}
+
+// Create session
+export async function createSession(userId, token) {
+ const sessions = await readSessions()
+ const session = {
+ id: Date.now().toString(),
+ userId,
+ token,
+ createdAt: new Date().toISOString(),
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days
+ }
+ sessions.push(session)
+ await writeSessions(sessions)
+ return session
+}
+
+// Delete session
+export async function deleteSession(token) {
+ const sessions = await readSessions()
+ const filtered = sessions.filter(s => s.token !== token)
+ await writeSessions(filtered)
+}
+
+// Clean expired sessions
+export async function cleanExpiredSessions() {
+ const sessions = await readSessions()
+ const now = new Date()
+ const valid = sessions.filter(s => new Date(s.expiresAt) > now)
+ await writeSessions(valid)
+}
+