feat(security): alle Daten auf den Besitzer einschraenken
Bisher sah jede Anfrage alles. Jetzt traegt jede Abfrage eine Besitzerbedingung, und zwar an erster Stelle in der where-Klausel statt als nachtraeglicher Vergleich - so kann sie nicht versehentlich uebersprungen werden. - Lesen: findFirst statt findUnique, immer mit ownerId - Schreiben und Loeschen: updateMany/deleteMany mit ownerId in der where-Klausel. Fremde Datensaetze werden dadurch gar nicht erst getroffen, und die betroffene Zeilenzahl sagt, ob es den Datensatz gab. - Notizen haengen ueber project.ownerId am Besitzer - Beim Verknuepfen eines Projekts wird geprueft, dass die Git-Verbindung dem Aufrufer gehoert - sonst liesse sich eine fremde Verbindung und damit ein fremder Token unterschieben - Fremde IDs liefern 404 statt 403: die Antwort bestaetigt nicht einmal, dass es den Datensatz gibt - Ausgehende Endpunkte (Sync, Verbindungstest, Repo-Liste) zusaetzlich ratenbegrenzt, Basis-URLs gegen den SSRF-Schutz geprueft - Slug und Verbindungsname werden je Besitzer auf Eindeutigkeit geprueft Nachgewiesen: ein zweites Konto erhaelt auf GET, PATCH, DELETE, Notiz anlegen und Sync eines fremden Projekts durchgaengig 404, auf die Admin-Endpunkte 403, und das Zielprojekt bleibt unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import { FileText } from "lucide-react";
|
||||
import { GitPanel } from "@/components/GitPanel";
|
||||
import { NotesPanel } from "@/components/NotesPanel";
|
||||
import { ProjectHeader } from "@/components/ProjectHeader";
|
||||
import { requireUserPage } from "@/lib/auth/guard";
|
||||
import { getSessionUser } from "@/lib/auth/session";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { connectionSafeSelect, projectDetailInclude } from "@/lib/projects";
|
||||
import type { ConnectionSafe, ProjectDetail } from "@/types";
|
||||
@@ -13,9 +15,12 @@ export const dynamic = "force-dynamic";
|
||||
type PageProps = { params: Promise<{ id: string }> };
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const user = await getSessionUser();
|
||||
if (!user) return { title: "Projekt" };
|
||||
|
||||
const { id } = await params;
|
||||
const project = await prisma.project.findUnique({
|
||||
where: { id },
|
||||
const project = await prisma.project.findFirst({
|
||||
where: { id, ownerId: user.id },
|
||||
select: { name: true, summary: true },
|
||||
});
|
||||
return {
|
||||
@@ -25,11 +30,21 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
}
|
||||
|
||||
export default async function ProjectPage({ params }: PageProps) {
|
||||
const user = await requireUserPage();
|
||||
const { id } = await params;
|
||||
|
||||
const [project, connections] = await Promise.all([
|
||||
prisma.project.findUnique({ where: { id }, include: projectDetailInclude }),
|
||||
prisma.gitConnection.findMany({ select: connectionSafeSelect, orderBy: { name: "asc" } }),
|
||||
// findFirst mit Besitzerbedingung: ein fremdes Projekt fuehrt zu 404,
|
||||
// nicht zu einer Fehlermeldung, die seine Existenz bestaetigt.
|
||||
prisma.project.findFirst({
|
||||
where: { id, ownerId: user.id },
|
||||
include: projectDetailInclude,
|
||||
}),
|
||||
prisma.gitConnection.findMany({
|
||||
where: { ownerId: user.id },
|
||||
select: connectionSafeSelect,
|
||||
orderBy: { name: "asc" },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!project) notFound();
|
||||
|
||||
Reference in New Issue
Block a user