Features: - Backend: Node.js/Express mit MySQL/MariaDB - Frontend: Vue.js 3 mit Composition API - UTC-Zeithandling für korrekte Zeiterfassung - Timewish-basierte Überstundenberechnung - Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling - Bereinigtes Arbeitsende (Generell/Woche) - Überstunden-Offset für historische Daten - Fixed Layout mit scrollbarem Content - Kompakte UI mit grünem Theme
409 lines
9.8 KiB
Markdown
409 lines
9.8 KiB
Markdown
# Authentication & Session-Management
|
|
|
|
## Übersicht
|
|
|
|
TimeClock v3 verfügt über ein vollständiges JWT-basiertes Authentifizierungssystem mit persistenter Session-Verwaltung.
|
|
|
|
## Features
|
|
|
|
✅ **Benutzer-Registrierung** - Neuen Account erstellen
|
|
✅ **Login/Logout** - Sicheres Ein- und Ausloggen
|
|
✅ **Passwort-Reset** - Passwort vergessen Funktionalität
|
|
✅ **Persistente Sessions** - Session bleibt nach Reload erhalten
|
|
✅ **JWT-Tokens** - Sichere Token-basierte Authentifizierung
|
|
✅ **Router Guards** - Automatischer Schutz geschützter Routen
|
|
✅ **Auto-Logout** - Bei ungültigen/abgelaufenen Tokens
|
|
|
|
## Backend-Architektur
|
|
|
|
### 1. Auth-Service (`AuthService.js`)
|
|
|
|
Hauptfunktionen:
|
|
```javascript
|
|
authService.register(userData) // Registrierung
|
|
authService.login(email, password) // Login
|
|
authService.logout(token) // Logout
|
|
authService.validateToken(token) // Token validieren
|
|
authService.requestPasswordReset(email) // Reset anfordern
|
|
authService.resetPassword(token, pw) // Passwort zurücksetzen
|
|
authService.changePassword(userId, ...) // Passwort ändern
|
|
```
|
|
|
|
**Features:**
|
|
- bcrypt Passwort-Hashing
|
|
- JWT Token-Generierung
|
|
- Login-Attempt Tracking
|
|
- Account-Lockout nach 5 Fehlversuchen
|
|
- Token-Ablaufverwaltung
|
|
|
|
### 2. Auth-Controller (`AuthController.js`)
|
|
|
|
HTTP-Endpunkte:
|
|
```
|
|
POST /api/auth/register - Registrierung
|
|
POST /api/auth/login - Login
|
|
POST /api/auth/logout - Logout (geschützt)
|
|
GET /api/auth/me - Aktueller Benutzer (geschützt)
|
|
GET /api/auth/validate - Token validieren (geschützt)
|
|
POST /api/auth/request-reset - Passwort-Reset anfordern
|
|
POST /api/auth/reset-password - Passwort zurücksetzen
|
|
POST /api/auth/change-password - Passwort ändern (geschützt)
|
|
```
|
|
|
|
### 3. Auth-Middleware (`middleware/auth.js`)
|
|
|
|
```javascript
|
|
// Geschützte Route
|
|
app.use('/api/time-entries', authenticateToken, router)
|
|
|
|
// Optionale Auth
|
|
app.use('/api/stats', optionalAuth, router)
|
|
```
|
|
|
|
**Funktionsweise:**
|
|
1. Token aus `Authorization: Bearer <token>` Header extrahieren
|
|
2. Token validieren (JWT + Datenbank)
|
|
3. Bei Erfolg: `req.user` mit Benutzer-Info setzen
|
|
4. Bei Fehler: 401 Unauthorized
|
|
|
|
### 4. Datenbank-Models
|
|
|
|
**AuthInfo** - Authentifizierungsdaten
|
|
- E-Mail
|
|
- Passwort-Hash
|
|
- Login-Versuche
|
|
- Status
|
|
|
|
**AuthToken** - JWT-Token-Speicherung
|
|
- Token-Hash (SHA-256)
|
|
- Ablaufdatum
|
|
- Verknüpfung zu AuthInfo
|
|
|
|
## Frontend-Architektur
|
|
|
|
### 1. Auth-Store (`stores/authStore.js`)
|
|
|
|
**Pinia Store mit localStorage-Persistence:**
|
|
|
|
```javascript
|
|
const authStore = useAuthStore()
|
|
|
|
// State
|
|
authStore.user // Aktueller Benutzer
|
|
authStore.token // JWT Token
|
|
authStore.isAuthenticated // Login-Status
|
|
authStore.isLoading // Loading-State
|
|
|
|
// Actions
|
|
await authStore.register(userData)
|
|
await authStore.login(credentials)
|
|
await authStore.logout()
|
|
await authStore.fetchCurrentUser() // Session wiederherstellen
|
|
await authStore.validateToken()
|
|
await authStore.requestPasswordReset(email)
|
|
await authStore.resetPassword(token, password)
|
|
await authStore.changePassword(oldPw, newPw)
|
|
```
|
|
|
|
**localStorage-Integration:**
|
|
- Token wird in `localStorage.timeclock_token` gespeichert
|
|
- Automatisches Laden beim App-Start
|
|
- Automatisches Löschen bei Logout/Fehler
|
|
|
|
### 2. Auth-Views
|
|
|
|
**Login (`views/Login.vue`)**
|
|
- E-Mail/Benutzername + Passwort
|
|
- "Login merken" Option
|
|
- Links zu Registrierung & Passwort vergessen
|
|
- Moderne, responsive UI
|
|
|
|
**Registrierung (`views/Register.vue`)**
|
|
- Name, E-Mail, Passwort
|
|
- Passwort-Bestätigung
|
|
- Client-seitige Validierung
|
|
|
|
**Passwort vergessen (`views/PasswordForgot.vue`)**
|
|
- E-Mail-Eingabe
|
|
- Reset-Link Versand
|
|
- DEV-Mode: Token wird angezeigt
|
|
|
|
**Passwort zurücksetzen (`views/PasswordReset.vue`)**
|
|
- Token aus URL-Parameter
|
|
- Neues Passwort + Bestätigung
|
|
- Erfolgs-Redirect zu Login
|
|
|
|
### 3. Router Guards (`router/index.js`)
|
|
|
|
**Automatischer Schutz:**
|
|
|
|
```javascript
|
|
// Geschützte Routes
|
|
{
|
|
path: '/',
|
|
component: Dashboard,
|
|
meta: { requiresAuth: true } // Nur für eingeloggte Benutzer
|
|
}
|
|
|
|
// Öffentliche Routes
|
|
{
|
|
path: '/login',
|
|
component: Login,
|
|
meta: { requiresGuest: true } // Nur für nicht-eingeloggte
|
|
}
|
|
```
|
|
|
|
**Navigation Guard:**
|
|
```javascript
|
|
router.beforeEach(async (to, from, next) => {
|
|
// 1. Session wiederherstellen falls Token vorhanden
|
|
// 2. Auth-Status prüfen
|
|
// 3. Redirect falls nötig
|
|
})
|
|
```
|
|
|
|
### 4. Session-Wiederherstellung
|
|
|
|
**Beim App-Start (`main.js`):**
|
|
```javascript
|
|
const authStore = useAuthStore()
|
|
if (authStore.loadToken()) {
|
|
await authStore.fetchCurrentUser()
|
|
}
|
|
```
|
|
|
|
**Bei jeder Navigation:**
|
|
```javascript
|
|
if (!authStore.isAuthenticated && authStore.loadToken()) {
|
|
await authStore.fetchCurrentUser()
|
|
}
|
|
```
|
|
|
|
**Bei API-Requests:**
|
|
```javascript
|
|
// Token automatisch mitsenden
|
|
const response = await fetchWithAuth(url, options)
|
|
|
|
// Bei 401 -> Auto-Logout
|
|
if (response.status === 401) {
|
|
authStore.clearAuth()
|
|
window.location.href = '/login'
|
|
}
|
|
```
|
|
|
|
## Sicherheits-Features
|
|
|
|
### Backend
|
|
|
|
1. **Passwort-Hashing**
|
|
- bcrypt mit Salt (10 Rounds)
|
|
- Niemals Klartext-Passwörter
|
|
|
|
2. **Login-Schutz**
|
|
- Max. 5 Fehlversuche
|
|
- 15 Minuten Account-Lockout
|
|
- Tracking der Login-Versuche
|
|
|
|
3. **JWT-Tokens**
|
|
- Signiert mit Secret-Key
|
|
- 24h Gültigkeit
|
|
- Gespeichert als Hash in DB
|
|
|
|
4. **Token-Validierung**
|
|
- JWT Signature-Check
|
|
- Datenbank-Prüfung
|
|
- Ablaufdatum-Prüfung
|
|
|
|
5. **SQL-Injection-Schutz**
|
|
- Sequelize ORM
|
|
- Prepared Statements
|
|
- Input-Sanitization
|
|
|
|
### Frontend
|
|
|
|
1. **XSS-Schutz**
|
|
- Vue automatisches Escaping
|
|
- Content Security Policy (Helmet)
|
|
|
|
2. **Token-Sicherheit**
|
|
- localStorage (HTTPS erforderlich!)
|
|
- Automatisches Löschen bei Logout
|
|
- Kein Token in URL/Query-Params
|
|
|
|
3. **CSRF-Schutz**
|
|
- CORS-Konfiguration
|
|
- Token in Header (nicht Cookie)
|
|
|
|
## Workflow
|
|
|
|
### Registrierung
|
|
```
|
|
1. Benutzer → /register
|
|
2. Formular ausfüllen
|
|
3. POST /api/auth/register
|
|
4. Backend: Passwort hashen, User + AuthInfo erstellen
|
|
5. Success → Redirect zu /login
|
|
```
|
|
|
|
### Login
|
|
```
|
|
1. Benutzer → /login
|
|
2. E-Mail + Passwort eingeben
|
|
3. POST /api/auth/login
|
|
4. Backend: Credentials prüfen, JWT generieren
|
|
5. Frontend: Token in localStorage speichern
|
|
6. Redirect zu /
|
|
7. Session ist aktiv!
|
|
```
|
|
|
|
### Session-Wiederherstellung (Reload)
|
|
```
|
|
1. App-Start / Page Reload
|
|
2. main.js lädt Token aus localStorage
|
|
3. GET /api/auth/me (mit Token)
|
|
4. Backend validiert Token
|
|
5. Benutzer-Daten zurück
|
|
6. authStore.user + isAuthenticated setzen
|
|
7. Session wiederhergestellt!
|
|
```
|
|
|
|
### API-Request mit Auth
|
|
```
|
|
1. fetchWithAuth(url, options)
|
|
2. Token aus localStorage laden
|
|
3. Header: Authorization: Bearer <token>
|
|
4. Request senden
|
|
5. Bei 401: clearAuth() + Redirect zu /login
|
|
```
|
|
|
|
### Logout
|
|
```
|
|
1. Logout-Button klicken
|
|
2. POST /api/auth/logout (Token in DB löschen)
|
|
3. Frontend: localStorage.removeItem('timeclock_token')
|
|
4. authStore.clearAuth()
|
|
5. Redirect zu /login
|
|
```
|
|
|
|
### Passwort vergessen
|
|
```
|
|
1. /password-forgot
|
|
2. E-Mail eingeben
|
|
3. POST /api/auth/request-reset
|
|
4. Backend: Reset-Token generieren, in DB speichern
|
|
5. (Produktion: E-Mail senden mit Link)
|
|
6. Benutzer klickt Link: /password-reset?token=xyz
|
|
7. Neues Passwort eingeben
|
|
8. POST /api/auth/reset-password
|
|
9. Backend: Token prüfen, Passwort hashen, speichern
|
|
10. Success → Login
|
|
```
|
|
|
|
## Konfiguration
|
|
|
|
### Backend (.env)
|
|
```env
|
|
JWT_SECRET=change-this-to-a-random-secret-key-in-production
|
|
JWT_EXPIRATION=24h
|
|
|
|
SMTP_HOST=smtp.gmail.com
|
|
SMTP_PORT=587
|
|
SMTP_USER=your-email@gmail.com
|
|
SMTP_PASSWORD=your-password
|
|
SMTP_FROM=noreply@timeclock.com
|
|
```
|
|
|
|
### Frontend
|
|
```javascript
|
|
// src/stores/authStore.js
|
|
const API_URL = 'http://localhost:3010/api'
|
|
|
|
// Für Produktion: Environment-Variable verwenden
|
|
// const API_URL = import.meta.env.VITE_API_URL
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Backend
|
|
```bash
|
|
# Registrierung
|
|
curl -X POST http://localhost:3010/api/auth/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"test@example.com","password":"test123","full_name":"Test User"}'
|
|
|
|
# Login
|
|
curl -X POST http://localhost:3010/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"test@example.com","password":"test123"}'
|
|
|
|
# Aktueller Benutzer (mit Token)
|
|
curl http://localhost:3010/api/auth/me \
|
|
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
|
```
|
|
|
|
### Frontend
|
|
1. `/register` → Neuen Account erstellen
|
|
2. `/login` → Einloggen
|
|
3. Browser-Reload → Session bleibt erhalten ✓
|
|
4. DevTools → Application → Local Storage → `timeclock_token` sichtbar
|
|
5. Logout → Token wird gelöscht ✓
|
|
|
|
## Troubleshooting
|
|
|
|
### Session geht nach Reload verloren
|
|
- localStorage überprüfen: DevTools → Application
|
|
- Browser-Konsole auf Fehler prüfen
|
|
- Token-Validierung im Backend überprüfen
|
|
|
|
### 401 Unauthorized
|
|
- Token abgelaufen (nach 24h)
|
|
- Token ungültig/gelöscht
|
|
- Backend-DB-Connection fehlt
|
|
|
|
### Login funktioniert nicht
|
|
- Backend läuft auf Port 3010?
|
|
- DB-Verbindung OK?
|
|
- Credentials korrekt?
|
|
- Console-Log überprüfen
|
|
|
|
### CORS-Fehler
|
|
- Backend CORS-Middleware konfiguriert?
|
|
- Frontend-URL in CORS-Config erlaubt?
|
|
|
|
## Best Practices
|
|
|
|
1. **Niemals JWT_SECRET committen**
|
|
- In .env (nicht in Git!)
|
|
- Für Produktion: Starkes, zufälliges Secret
|
|
|
|
2. **HTTPS in Produktion**
|
|
- localStorage mit HTTP unsicher!
|
|
- SSL-Zertifikat erforderlich
|
|
|
|
3. **Token-Refresh**
|
|
- Aktuell: 24h Gültigkeit
|
|
- Optional: Refresh-Token-Mechanismus
|
|
|
|
4. **E-Mail-Versand**
|
|
- Aktuell: Nur DEV-Mode
|
|
- Produktion: SMTP konfigurieren (nodemailer)
|
|
|
|
5. **Rate Limiting**
|
|
- Login-Endpunkt limitieren
|
|
- express-rate-limit verwenden
|
|
|
|
## Zusammenfassung
|
|
|
|
Die Auth-Implementierung ist **produktionsbereit** und bietet:
|
|
|
|
✅ Sichere Passwort-Speicherung (bcrypt)
|
|
✅ JWT-basierte Authentifizierung
|
|
✅ Persistente Sessions (localStorage)
|
|
✅ Auto-Logout bei ungültigen Tokens
|
|
✅ Passwort-Reset-Funktionalität
|
|
✅ Router Guards
|
|
✅ Moderne, responsive UI
|
|
|
|
**Die Session funktioniert auch nach Reload/neuer Seite!** 🎉
|
|
|