opencrm/backend/src/controllers/auth.controller.ts

237 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}