237 lines
6.8 KiB
TypeScript
237 lines
6.8 KiB
TypeScript
import { Request, Response } from 'express';
|
||
import * as authService from '../services/auth.service.js';
|
||
import { AuthRequest, ApiResponse } from '../types/index.js';
|
||
import prisma from '../lib/prisma.js';
|
||
|
||
// Mitarbeiter-Login
|
||
export async function login(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { email, password } = req.body;
|
||
|
||
if (!email || !password) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: 'E-Mail und Passwort erforderlich',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
const result = await authService.login(email, password);
|
||
res.json({ success: true, data: result } as ApiResponse);
|
||
} catch (error) {
|
||
res.status(401).json({
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Anmeldung fehlgeschlagen',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
// Kundenportal-Login
|
||
export async function customerLogin(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { email, password } = req.body;
|
||
|
||
if (!email || !password) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: 'E-Mail und Passwort erforderlich',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
const result = await authService.customerLogin(email, password);
|
||
res.json({ success: true, data: result } as ApiResponse);
|
||
} catch (error) {
|
||
res.status(401).json({
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Anmeldung fehlgeschlagen',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
export async function me(req: AuthRequest, res: Response): Promise<void> {
|
||
try {
|
||
if (!req.user) {
|
||
res.status(401).json({
|
||
success: false,
|
||
error: 'Nicht authentifiziert',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
// Kundenportal-Login
|
||
if (req.user.isCustomerPortal && req.user.customerId) {
|
||
const customer = await authService.getCustomerPortalUser(req.user.customerId);
|
||
if (!customer) {
|
||
res.status(404).json({
|
||
success: false,
|
||
error: 'Kunde nicht gefunden',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
res.json({ success: true, data: customer } as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
// Mitarbeiter-Login
|
||
if (!req.user.userId) {
|
||
res.status(401).json({
|
||
success: false,
|
||
error: 'Ungültige Authentifizierung',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
const user = await authService.getUserById(req.user.userId);
|
||
if (!user) {
|
||
res.status(404).json({
|
||
success: false,
|
||
error: 'Benutzer nicht gefunden',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
res.json({ success: true, data: user } as ApiResponse);
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
success: false,
|
||
error: 'Fehler beim Laden der Benutzerdaten',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Passwort-Reset anfordern (Email + Token per Mail).
|
||
* Immer 200 OK zurückgeben um Email-Existenz nicht preiszugeben (User-Enumeration-Schutz).
|
||
*/
|
||
export async function requestPasswordReset(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { email, userType } = req.body; // userType: 'admin' | 'portal'
|
||
|
||
if (!email) {
|
||
res.status(400).json({ success: false, error: 'E-Mail erforderlich' } as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
await authService.requestPasswordReset(email, userType === 'portal' ? 'portal' : 'admin');
|
||
|
||
// IMMER success senden, damit Angreifer nicht herausfinden kann welche Emails existieren
|
||
res.json({
|
||
success: true,
|
||
message: 'Wenn ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.',
|
||
} as ApiResponse);
|
||
} catch (error) {
|
||
console.error('Password reset request error:', error);
|
||
// Auch bei Fehlern dieselbe Antwort
|
||
res.json({
|
||
success: true,
|
||
message: 'Wenn ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Passwort-Reset bestätigen (Token + neues Passwort).
|
||
*/
|
||
export async function confirmPasswordReset(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { token, password } = req.body;
|
||
|
||
if (!token || !password) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: 'Token und neues Passwort erforderlich',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
if (password.length < 6) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: 'Das Passwort muss mindestens 6 Zeichen lang sein',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
await authService.confirmPasswordReset(token, password);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Passwort erfolgreich zurückgesetzt. Du kannst dich jetzt einloggen.',
|
||
} as ApiResponse);
|
||
} catch (error) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Passwort-Reset fehlgeschlagen',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Logout: invalidiert den aktuellen JWT serverseitig durch Setzen von
|
||
* tokenInvalidatedAt / portalTokenInvalidatedAt auf jetzt. Auth-Middleware
|
||
* prüft dieses Feld und lehnt Tokens ab, deren `iat` davor liegt.
|
||
*
|
||
* Hinweis: Da JWTs stateless sind, gibt es keine echte Token-Revocation
|
||
* ohne dieses Pattern. Logout invalidiert ALLE aktiven Sessions des Users
|
||
* (auch andere Geräte) – akzeptabel für ein Sicherheits-Logout.
|
||
*/
|
||
export async function logout(req: AuthRequest, res: Response): Promise<void> {
|
||
try {
|
||
const user = req.user as any;
|
||
if (!user) {
|
||
res.json({ success: true, message: 'Bereits abgemeldet' } as ApiResponse);
|
||
return;
|
||
}
|
||
if (user.isCustomerPortal && user.customerId) {
|
||
await prisma.customer.update({
|
||
where: { id: user.customerId },
|
||
data: { portalTokenInvalidatedAt: new Date() },
|
||
});
|
||
} else if (user.userId) {
|
||
await prisma.user.update({
|
||
where: { id: user.userId },
|
||
data: { tokenInvalidatedAt: new Date() },
|
||
});
|
||
}
|
||
res.json({ success: true, message: 'Abgemeldet' } as ApiResponse);
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
success: false,
|
||
error: 'Fehler beim Abmelden',
|
||
} as ApiResponse);
|
||
}
|
||
}
|
||
|
||
export async function register(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { email, password, firstName, lastName, roleIds } = req.body;
|
||
|
||
if (!email || !password || !firstName || !lastName) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error: 'Alle Pflichtfelder müssen ausgefüllt sein',
|
||
} as ApiResponse);
|
||
return;
|
||
}
|
||
|
||
const user = await authService.createUser({
|
||
email,
|
||
password,
|
||
firstName,
|
||
lastName,
|
||
roleIds: roleIds || [2], // Default to employee role
|
||
});
|
||
|
||
res.status(201).json({ success: true, data: user } as ApiResponse);
|
||
} catch (error) {
|
||
res.status(400).json({
|
||
success: false,
|
||
error:
|
||
error instanceof Error
|
||
? error.message
|
||
: 'Benutzer konnte nicht erstellt werden',
|
||
} as ApiResponse);
|
||
}
|
||
}
|