Files
opencrm/backend/prisma/rolle-zuweisen.ts
T
duffyduckandClaude Opus 5 9f879759d8 CLI-Skripte: DATABASE_URL wieder aufbaubar machen
Die drei neuen Wartungsskripte bauten sich einen eigenen PrismaClient und
gingen damit an src/lib/prisma.ts vorbei. Dort wird DATABASE_URL aus den
DB_*-Teilen zusammengesetzt, falls sie fehlt - und genau das ist bei
`docker compose exec <dienst> npx tsx prisma/<skript>.ts` der Fall: Der
Entrypoint exportiert sie nur in den Serverprozess, eine neue Shell erbt
sie nicht. Ergebnis war "Environment variable not found: DATABASE_URL".

Der Kommentar in src/lib/prisma.ts beschreibt genau diesen Fall; die
Loesung war da, ich habe sie nur umgangen.

Betroffen war auch rolle-zuweisen.ts. Das ist der Weg, den die Startwache
im Klartext nennt, wenn kein Konto mehr die Pflichtrechte haelt - er
haette in dem Moment versagt, fuer den er gebaut ist.

Geprueft mit entzogener DATABASE_URL und nur DB_*-Teilen in der
Umgebung: rechte-report, rolle-zuweisen --liste und sync-roles laufen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 14:23:58 +02:00

147 lines
5.4 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.
/**
* Weist einem Konto eine Rolle zu oder nimmt sie ihm weg - von der
* Kommandozeile aus.
*
* npx tsx prisma/rolle-zuweisen.ts <email> <Rollenname>
* npx tsx prisma/rolle-zuweisen.ts <email> <Rollenname> --entfernen
* npx tsx prisma/rolle-zuweisen.ts --liste
*
* Im Container:
* docker compose exec backend npx tsx prisma/rolle-zuweisen.ts \
* name@firma.de Developer
*
* WOZU: Die Teilmengenregel verhindert, dass jemand ueber die Weboberflaeche
* Rechte vergibt, die er selbst nicht haelt. Das ist gewollt - es schliesst
* die Selbst-Erhoehung. Es hat aber eine Kehrseite: Nach einer
* Neuinstallation haelt niemand `developer:access` oder `audit:admin`, und
* dann kann diese Rechte auch niemand erstmalig vergeben.
*
* Dieses Skript ist der dokumentierte Ausweg. Es umgeht die Regel bewusst,
* denn die Vertrauensgrenze stimmt: Wer eine Shell auf dieser Maschine hat,
* hat ohnehin Zugriff auf die Datenbank. Ein gestohlener Web-Zugang hat das
* nicht - und genau das ist der Unterschied, den die Regel schuetzen soll.
*
* Der Vorgang wird protokolliert und meldet die Traeger ab, damit die
* Aenderung sofort wirkt und nicht spurlos bleibt.
*/
// Ueber src/lib/prisma.js, NICHT ueber einen eigenen PrismaClient: Dort wird
// DATABASE_URL aus den DB_*-Teilen zusammengesetzt, falls sie fehlt. Bei
// `docker compose exec <dienst> npx tsx prisma/<skript>.ts` ist genau das der
// Fall - der Entrypoint exportiert sie nur in den Serverprozess, eine neue
// Shell erbt sie nicht. Ein eigener Client scheitert dort mit
// "Environment variable not found: DATABASE_URL".
import prisma from '../src/lib/prisma.js';
import { createAuditLog } from '../src/services/audit.service.js';
async function main(): Promise<void> {
const args = process.argv.slice(2);
if (args.includes('--liste') || args.length === 0) {
const rollen = await prisma.role.findMany({
orderBy: { name: 'asc' },
include: { _count: { select: { users: true } } },
});
console.log('Vorhandene Rollen:');
for (const r of rollen) {
const kennz = [r.isSystem ? 'System' : null, r.isHidden ? 'versteckt' : null]
.filter(Boolean)
.join(', ');
console.log(` ${r.name}${kennz ? ` (${kennz})` : ''} ${r._count.users} Konten`);
}
if (args.length === 0) {
console.log('\nAufruf: npx tsx prisma/rolle-zuweisen.ts <email> <Rollenname> [--entfernen]');
}
return;
}
const entfernen = args.includes('--entfernen');
const [email, rollenName] = args.filter((a) => !a.startsWith('--'));
if (!email || !rollenName) {
console.error('Aufruf: npx tsx prisma/rolle-zuweisen.ts <email> <Rollenname> [--entfernen]');
process.exit(1);
}
const konto = await prisma.user.findUnique({ where: { email } });
if (!konto) {
console.error(`Kein Konto mit der Adresse "${email}".`);
process.exit(1);
}
const rolle = await prisma.role.findUnique({ where: { name: rollenName } });
if (!rolle) {
console.error(`Keine Rolle mit dem Namen "${rollenName}". Vorhandene mit --liste ansehen.`);
process.exit(1);
}
const vorhanden = await prisma.userRole.findUnique({
where: { userId_roleId: { userId: konto.id, roleId: rolle.id } },
});
if (entfernen) {
if (!vorhanden) {
console.log(`"${email}" hat die Rolle "${rollenName}" gar nicht. Nichts zu tun.`);
return;
}
await prisma.userRole.delete({
where: { userId_roleId: { userId: konto.id, roleId: rolle.id } },
});
} else {
if (vorhanden) {
console.log(`"${email}" hat die Rolle "${rollenName}" bereits. Nichts zu tun.`);
return;
}
await prisma.userRole.create({ data: { userId: konto.id, roleId: rolle.id } });
}
// Sofort wirksam machen: Die Rechte stehen im Zugangstoken, sonst behielte
// das Konto bis zu 15 Minuten den alten Stand.
await prisma.user.update({
where: { id: konto.id },
data: { tokenInvalidatedAt: new Date() },
});
// Nachvollziehbar machen. Ein Eingriff von der Kommandozeile ist berechtigt,
// aber er darf nicht unsichtbar sein - sonst waere das Skript selbst die
// Luecke, die es schliessen soll.
//
// Ueber createAuditLog, NICHT ueber prisma.auditLog.create: Das Protokoll
// ist eine Hash-Kette. Ein roh eingefuegter Datensatz haette kein `hash`
// und keinen `previousHash` und wuerde bei der naechsten Pruefung als
// Luecke erscheinen - das Skript wuerde also ausgerechnet dort Zweifel
// saeen, wo es Klarheit schaffen soll.
await createAuditLog({
userEmail: 'system (CLI)',
userRole: 'Kommandozeile',
action: entfernen ? 'DELETE' : 'CREATE',
sensitivity: 'CRITICAL',
resourceType: 'UserRole',
resourceId: `${konto.id}:${rolle.id}`,
resourceLabel: entfernen
? `Rolle "${rolle.name}" von ${email} entfernt (Kommandozeile)`
: `Rolle "${rolle.name}" an ${email} vergeben (Kommandozeile)`,
endpoint: 'prisma/rolle-zuweisen.ts',
httpMethod: 'CLI',
ipAddress: 'lokal',
changesAfter: { konto: email, rolle: rolle.name, entfernt: entfernen },
});
console.log(
entfernen
? `Rolle "${rolle.name}" von "${email}" entfernt.`
: `Rolle "${rolle.name}" an "${email}" vergeben.`,
);
console.log('Das Konto muss sich neu anmelden, damit die Änderung greift.');
}
main()
.catch((e) => {
console.error('[rolle-zuweisen] Fehler:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});