609 lines
15 KiB
TypeScript
609 lines
15 KiB
TypeScript
import prisma from '../lib/prisma.js';
|
|
import bcrypt from 'bcryptjs';
|
|
import { paginate, buildPaginationResponse } from '../utils/helpers.js';
|
|
|
|
export interface UserFilters {
|
|
search?: string;
|
|
isActive?: boolean;
|
|
roleId?: number;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export async function getAllUsers(filters: UserFilters) {
|
|
const { search, isActive, roleId, page = 1, limit = 20 } = filters;
|
|
const { skip, take } = paginate(page, limit);
|
|
|
|
const where: Record<string, unknown> = {};
|
|
|
|
if (isActive !== undefined) {
|
|
where.isActive = isActive;
|
|
}
|
|
|
|
if (roleId) {
|
|
where.roles = { some: { roleId } };
|
|
}
|
|
|
|
if (search) {
|
|
where.OR = [
|
|
{ email: { contains: search } },
|
|
{ firstName: { contains: search } },
|
|
{ lastName: { contains: search } },
|
|
];
|
|
}
|
|
|
|
const [users, total] = await Promise.all([
|
|
prisma.user.findMany({
|
|
where,
|
|
skip,
|
|
take,
|
|
orderBy: { createdAt: 'desc' },
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
isActive: true,
|
|
customerId: true,
|
|
whatsappNumber: true,
|
|
telegramUsername: true,
|
|
signalNumber: true,
|
|
createdAt: true,
|
|
roles: {
|
|
include: {
|
|
role: {
|
|
include: {
|
|
permissions: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
prisma.user.count({ where }),
|
|
]);
|
|
|
|
// Get hidden role IDs
|
|
const [developerRole, gdprRole] = await Promise.all([
|
|
prisma.role.findFirst({ where: { name: 'Developer' } }),
|
|
prisma.role.findFirst({ where: { name: 'DSGVO' } }),
|
|
]);
|
|
|
|
return {
|
|
users: users.map((u) => {
|
|
const hasDeveloperAccess = developerRole
|
|
? u.roles.some((ur) => ur.roleId === developerRole.id)
|
|
: false;
|
|
const hasGdprAccess = gdprRole
|
|
? u.roles.some((ur) => ur.roleId === gdprRole.id)
|
|
: false;
|
|
return {
|
|
...u,
|
|
roles: u.roles.map((r) => r.role),
|
|
hasDeveloperAccess,
|
|
hasGdprAccess,
|
|
};
|
|
}),
|
|
pagination: buildPaginationResponse(page, limit, total),
|
|
};
|
|
}
|
|
|
|
export async function getUserById(id: number) {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
isActive: true,
|
|
customerId: true,
|
|
whatsappNumber: true,
|
|
telegramUsername: true,
|
|
signalNumber: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
roles: {
|
|
include: {
|
|
role: {
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!user) return null;
|
|
|
|
const permissions = new Set<string>();
|
|
for (const userRole of user.roles) {
|
|
for (const rolePerm of userRole.role.permissions) {
|
|
permissions.add(
|
|
`${rolePerm.permission.resource}:${rolePerm.permission.action}`
|
|
);
|
|
}
|
|
}
|
|
|
|
return {
|
|
...user,
|
|
roles: user.roles.map((r) => r.role),
|
|
permissions: Array.from(permissions),
|
|
};
|
|
}
|
|
|
|
export async function createUser(data: {
|
|
email: string;
|
|
password: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
roleIds: number[];
|
|
customerId?: number;
|
|
hasDeveloperAccess?: boolean;
|
|
hasGdprAccess?: boolean;
|
|
whatsappNumber?: string;
|
|
telegramUsername?: string;
|
|
signalNumber?: string;
|
|
}) {
|
|
const hashedPassword = await bcrypt.hash(data.password, 10);
|
|
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email: data.email,
|
|
password: hashedPassword,
|
|
firstName: data.firstName,
|
|
lastName: data.lastName,
|
|
customerId: data.customerId,
|
|
whatsappNumber: data.whatsappNumber || null,
|
|
telegramUsername: data.telegramUsername || null,
|
|
signalNumber: data.signalNumber || null,
|
|
roles: {
|
|
create: data.roleIds.map((roleId) => ({ roleId })),
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
isActive: true,
|
|
customerId: true,
|
|
roles: {
|
|
include: { role: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
// Entwicklerzugriff setzen falls aktiviert
|
|
if (data.hasDeveloperAccess) {
|
|
await setUserDeveloperAccess(user.id, true);
|
|
}
|
|
|
|
// DSGVO-Zugriff setzen falls aktiviert
|
|
if (data.hasGdprAccess) {
|
|
await setUserGdprAccess(user.id, true);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
export async function updateUser(
|
|
id: number,
|
|
data: {
|
|
email?: string;
|
|
password?: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
isActive?: boolean;
|
|
roleIds?: number[];
|
|
customerId?: number;
|
|
hasDeveloperAccess?: boolean;
|
|
hasGdprAccess?: boolean;
|
|
whatsappNumber?: string;
|
|
telegramUsername?: string;
|
|
signalNumber?: string;
|
|
}
|
|
) {
|
|
const { roleIds, password, hasDeveloperAccess, hasGdprAccess, ...userData } = data;
|
|
|
|
// Check if this would remove the last admin
|
|
const isBeingDeactivated = userData.isActive === false;
|
|
const rolesAreBeingChanged = roleIds !== undefined;
|
|
|
|
if (isBeingDeactivated || rolesAreBeingChanged) {
|
|
// Check if user currently has admin permissions
|
|
const currentUser = await prisma.user.findUnique({
|
|
where: { id },
|
|
include: {
|
|
roles: {
|
|
include: {
|
|
role: {
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const isCurrentlyAdmin = currentUser?.roles.some((ur) =>
|
|
ur.role.permissions.some(
|
|
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
|
)
|
|
);
|
|
|
|
if (isCurrentlyAdmin) {
|
|
// Check if user will still be admin after role change
|
|
let willStillBeAdmin = false;
|
|
if (rolesAreBeingChanged) {
|
|
const newRoles = await prisma.role.findMany({
|
|
where: { id: { in: roleIds } },
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
});
|
|
willStillBeAdmin = newRoles.some((role) =>
|
|
role.permissions.some(
|
|
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
|
)
|
|
);
|
|
} else {
|
|
willStillBeAdmin = true; // Roles not being changed
|
|
}
|
|
|
|
// If user is losing admin status or being deactivated, check for other admins
|
|
if (!willStillBeAdmin || isBeingDeactivated) {
|
|
const otherAdminCount = await prisma.user.count({
|
|
where: {
|
|
id: { not: id },
|
|
isActive: true,
|
|
roles: {
|
|
some: {
|
|
role: {
|
|
permissions: {
|
|
some: {
|
|
permission: {
|
|
resource: 'users',
|
|
action: 'delete',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (otherAdminCount === 0) {
|
|
if (isBeingDeactivated) {
|
|
throw new Error(
|
|
'Dieser Benutzer ist der letzte Administrator und kann nicht deaktiviert werden'
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
'Die Admin-Rolle kann nicht entfernt werden, da dies der letzte Administrator ist'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hash password if provided
|
|
if (password) {
|
|
(userData as Record<string, unknown>).password = await bcrypt.hash(password, 10);
|
|
}
|
|
|
|
// Prüfen ob Rollen geändert werden (für Zwangslogout)
|
|
let rolesChanged = false;
|
|
if (roleIds !== undefined) {
|
|
const currentRoles = await prisma.userRole.findMany({
|
|
where: { userId: id },
|
|
select: { roleId: true },
|
|
});
|
|
const currentRoleIds = currentRoles.map((r) => r.roleId).sort();
|
|
const newRoleIds = [...roleIds].sort();
|
|
rolesChanged =
|
|
currentRoleIds.length !== newRoleIds.length ||
|
|
!currentRoleIds.every((id, i) => id === newRoleIds[i]);
|
|
}
|
|
|
|
// Update user - bei Rollenänderung Token invalidieren
|
|
await prisma.user.update({
|
|
where: { id },
|
|
data: {
|
|
...userData,
|
|
// Token invalidieren wenn Rollen geändert werden
|
|
...(rolesChanged && { tokenInvalidatedAt: new Date() }),
|
|
},
|
|
});
|
|
|
|
// Update roles if provided
|
|
if (roleIds) {
|
|
await prisma.userRole.deleteMany({ where: { userId: id } });
|
|
await prisma.userRole.createMany({
|
|
data: roleIds.map((roleId) => ({ userId: id, roleId })),
|
|
});
|
|
}
|
|
|
|
// Handle developer access
|
|
if (hasDeveloperAccess !== undefined) {
|
|
await setUserDeveloperAccess(id, hasDeveloperAccess);
|
|
}
|
|
|
|
// Handle GDPR access
|
|
if (hasGdprAccess !== undefined) {
|
|
await setUserGdprAccess(id, hasGdprAccess);
|
|
}
|
|
|
|
return getUserById(id);
|
|
}
|
|
|
|
// Helper to set developer access for a user
|
|
async function setUserDeveloperAccess(userId: number, enabled: boolean) {
|
|
// Get or create developer:access permission
|
|
let developerPerm = await prisma.permission.findFirst({
|
|
where: { resource: 'developer', action: 'access' },
|
|
});
|
|
|
|
if (!developerPerm) {
|
|
developerPerm = await prisma.permission.create({
|
|
data: { resource: 'developer', action: 'access' },
|
|
});
|
|
}
|
|
|
|
// Get or create Developer role
|
|
let developerRole = await prisma.role.findFirst({
|
|
where: { name: 'Developer' },
|
|
});
|
|
|
|
if (!developerRole) {
|
|
developerRole = await prisma.role.create({
|
|
data: {
|
|
name: 'Developer',
|
|
description: 'Entwicklerzugriff auf Datenbanktools',
|
|
permissions: {
|
|
create: [{ permissionId: developerPerm.id }],
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// Check if user already has Developer role
|
|
const hasRole = await prisma.userRole.findFirst({
|
|
where: { userId, roleId: developerRole.id },
|
|
});
|
|
|
|
if (enabled && !hasRole) {
|
|
await prisma.userRole.create({
|
|
data: { userId, roleId: developerRole.id },
|
|
});
|
|
// Token invalidieren bei Rechteänderung
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { tokenInvalidatedAt: new Date() },
|
|
});
|
|
} else if (!enabled && hasRole) {
|
|
await prisma.userRole.delete({
|
|
where: { userId_roleId: { userId, roleId: developerRole.id } },
|
|
});
|
|
// Token invalidieren bei Rechteänderung
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { tokenInvalidatedAt: new Date() },
|
|
});
|
|
}
|
|
}
|
|
|
|
// Helper to set GDPR access for a user
|
|
async function setUserGdprAccess(userId: number, enabled: boolean) {
|
|
// Get or create DSGVO role
|
|
let gdprRole = await prisma.role.findFirst({
|
|
where: { name: 'DSGVO' },
|
|
});
|
|
|
|
if (!gdprRole) {
|
|
// Create DSGVO role with all audit:* and gdpr:* permissions
|
|
const gdprPermissions = await prisma.permission.findMany({
|
|
where: {
|
|
OR: [{ resource: 'audit' }, { resource: 'gdpr' }],
|
|
},
|
|
});
|
|
|
|
gdprRole = await prisma.role.create({
|
|
data: {
|
|
name: 'DSGVO',
|
|
description: 'DSGVO-Zugriff: Audit-Logs und Datenschutz-Verwaltung',
|
|
permissions: {
|
|
create: gdprPermissions.map((p) => ({ permissionId: p.id })),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// Check if user already has DSGVO role
|
|
const hasRole = await prisma.userRole.findFirst({
|
|
where: { userId, roleId: gdprRole.id },
|
|
});
|
|
|
|
if (enabled && !hasRole) {
|
|
await prisma.userRole.create({
|
|
data: { userId, roleId: gdprRole.id },
|
|
});
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { tokenInvalidatedAt: new Date() },
|
|
});
|
|
} else if (!enabled && hasRole) {
|
|
await prisma.userRole.delete({
|
|
where: { userId_roleId: { userId, roleId: gdprRole.id } },
|
|
});
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { tokenInvalidatedAt: new Date() },
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function deleteUser(id: number) {
|
|
// Check if user is an admin
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
include: {
|
|
roles: {
|
|
include: {
|
|
role: {
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
throw new Error('Benutzer nicht gefunden');
|
|
}
|
|
|
|
// Check if user has admin permissions (users:delete means admin)
|
|
const isAdmin = user.roles.some((ur) =>
|
|
ur.role.permissions.some(
|
|
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
|
)
|
|
);
|
|
|
|
if (isAdmin) {
|
|
// Count other admins (users with users:delete permission)
|
|
const adminCount = await prisma.user.count({
|
|
where: {
|
|
id: { not: id },
|
|
isActive: true,
|
|
roles: {
|
|
some: {
|
|
role: {
|
|
permissions: {
|
|
some: {
|
|
permission: {
|
|
resource: 'users',
|
|
action: 'delete',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (adminCount === 0) {
|
|
throw new Error(
|
|
'Dieser Benutzer ist der letzte Administrator und kann nicht gelöscht werden'
|
|
);
|
|
}
|
|
}
|
|
|
|
return prisma.user.delete({ where: { id } });
|
|
}
|
|
|
|
// Role operations
|
|
export async function getAllRoles() {
|
|
return prisma.role.findMany({
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
_count: {
|
|
select: { users: true },
|
|
},
|
|
},
|
|
orderBy: { name: 'asc' },
|
|
});
|
|
}
|
|
|
|
export async function getRoleById(id: number) {
|
|
return prisma.role.findUnique({
|
|
where: { id },
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function createRole(data: {
|
|
name: string;
|
|
description?: string;
|
|
permissionIds: number[];
|
|
}) {
|
|
return prisma.role.create({
|
|
data: {
|
|
name: data.name,
|
|
description: data.description,
|
|
permissions: {
|
|
create: data.permissionIds.map((permissionId) => ({ permissionId })),
|
|
},
|
|
},
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true },
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function updateRole(
|
|
id: number,
|
|
data: {
|
|
name?: string;
|
|
description?: string;
|
|
permissionIds?: number[];
|
|
}
|
|
) {
|
|
const { permissionIds, ...roleData } = data;
|
|
|
|
await prisma.role.update({
|
|
where: { id },
|
|
data: roleData,
|
|
});
|
|
|
|
if (permissionIds) {
|
|
await prisma.rolePermission.deleteMany({ where: { roleId: id } });
|
|
await prisma.rolePermission.createMany({
|
|
data: permissionIds.map((permissionId) => ({ roleId: id, permissionId })),
|
|
});
|
|
}
|
|
|
|
return getRoleById(id);
|
|
}
|
|
|
|
export async function deleteRole(id: number) {
|
|
// Check if role is assigned to any users
|
|
const count = await prisma.userRole.count({ where: { roleId: id } });
|
|
if (count > 0) {
|
|
throw new Error(
|
|
`Rolle kann nicht gelöscht werden, da sie ${count} Benutzern zugewiesen ist`
|
|
);
|
|
}
|
|
|
|
return prisma.role.delete({ where: { id } });
|
|
}
|
|
|
|
// Permission operations
|
|
export async function getAllPermissions() {
|
|
return prisma.permission.findMany({
|
|
orderBy: [{ resource: 'asc' }, { action: 'asc' }],
|
|
});
|
|
}
|