Angenommen, Alice gehört zum Arbeitsbereich alpha. Sie fragt /workspaces/alpha/documents/doc-b/ an und erhält ein Dokument aus beta. Der Server hat Alice erkannt und ihre Mitgliedschaft geprüft. Beide Prüfungen bestanden. Wo lief die Anfrage falsch?

Dieses Lehrbeispiel verwendet fiktive Dokumente und Identitäten. Die Ausführung benötigt Node.js 22 oder neuer; die Lesemethode gilt auch für andere Sprachen und Frameworks. Die konkrete Frage lautet: Welcher Arbeitsbereich wurde geprüft und welcher begrenzte die Dokumentsuche?

Mit einer Anfrage und ihrer Regel beginnen

Halten Sie Methode, Pfad, Identität und erwartetes Ergebnis zusammen fest:

GET /workspaces/alpha/documents/doc-b/
Demo identity: alice, a member of alpha only
Stored document: doc-b belongs to beta
Expected: 404, with no document in the response
Broken result: 200, containing Beta release notes

Die Beispielrichtlinie erlaubt Mitgliedern Zugriff auf alle Dokumente ihres Arbeitsbereichs. Es gibt keine Einzelfreigaben oder Admin-Ausnahmen. Für ein Mitglied von alpha erhält ein Dokument außerhalb von alpha denselben Status und Body wie ein fehlendes Dokument. Eine Anfrage an einen nicht beigetretenen Arbeitsbereich erhält 403, bevor der Store aufgerufen wird.

Sie müssen zwei Beziehungen verfolgen: Der angemeldete Benutzer gehört zum Arbeitsbereich in der URL, und das gelieferte Dokument gehört zu demselben Arbeitsbereich. Nach erfolgreicher erster Prüfung muss die Datensuche die zweite sicherstellen.

Notieren Sie in echten Berichten auch Build oder Quellcodeversion. Die Route Ihrer Arbeitskopie muss zum tatsächlich ausgeführten Code passen. Reproduzieren Sie lokal mit synthetischen Fixtures.

Vor dem Handler die Routenregistrierung finden

Laden Sie request-flow.mjs und request-flow.test.mjs in denselben Ordner. Suchen Sie mit der Textsuche des Editors nach markanten Pfadwörtern wie workspaces und documents. Mit ripgrep grenzen diese Befehle die nächsten Schritte ein:

rg -n 'workspaces|documents' request-flow.mjs
rg -n 'middleware|runMiddleware|requireWorkspaceMember|findById|findInWorkspace' request-flow.mjs

Öffnen Sie createApp. Sein Routenobjekt stellt Methode, Pfadmuster, Middleware und gewählten Handler nebeneinander:

const route = {
  method: 'GET',
  pattern: /^\/workspaces\/([a-z0-9-]+)\/documents\/([a-z0-9-]+)\/$/,
  middleware: [authenticate, requireWorkspaceMember],
  handler: lookup === 'unscoped' ? getDocumentUnsafe : getDocument,
};

Lesen Sie auch die folgenden Zeilen. handle gleicht new URL(request.url).pathname ab, prüft die Methode und weist die zwei erfassten Segmente workspaceId und documentId zu. Query-Parameter liefern keinen der Werte. Das Beispiel akzeptiert Kleinbuchstaben, Zahlen und Bindestriche und verlangt den gezeigten abschließenden Schrägstrich; es ist kein allgemeiner Router.

In Framework-Anwendungen kann sich der Pfad aus einem Eltern-Mount wie /workspaces/:workspaceId und einer Kindroute wie /documents/:documentId zusammensetzen. Lesen Sie den Mount und prüfen Sie die Weitergabe der Elternparameter. Ein Treffer im Handler zeigt nicht, welche Middleware seinen Aufrufer umgibt.

Middleware in tatsächlicher Reihenfolge lesen

runMiddleware umhüllt den Handler mit reduceRight. Bei [authenticate, requireWorkspaceMember] läuft zuerst Authentifizierung, dann Mitgliedschaft, dann der Handler, sofern jeder Schritt next() aufruft. Die Werte von return next() fließen zu handle zurück, dessen catch einen geworfenen HttpError in eine Antwort umwandelt.

Request → path and method match → authenticate
        → requireWorkspaceMember → document lookup → JSON Response
                                   ↘ thrown error → handle's catch → error Response

authenticate sucht den Authorization-Header in einer festen Map und legt das Ergebnis in ctx.user ab. demo-alice und demo-bob sind öffentliche Fixture-Eingaben als Ersatz für verifizierte Sitzungen. Das erklärt die Identitätsquelle dieses Beispiels; echte Sitzungs- oder Tokenprüfung muss separat gelesen werden.

Öffnen Sie nun die Mitgliedschafts-Middleware:

async function requireWorkspaceMember(ctx, next) {
  const { workspaceId } = ctx.params;
  const allowed = ctx.user.workspaceIds.includes(workspaceId);
  ctx.trace.push(`membership:${workspaceId}:${allowed ? 'allowed' : 'denied'}`);
  if (!allowed) throw new HttpError(403, 'Forbidden');
  return next();
}

Für Alice und einen Pfad mit alpha ist allowed wahr. Die Funktion prüft weder documentId noch lädt sie ein Dokument. Notieren Sie genau: „Alice darf die alpha-Route betreten.“ Nur „autorisiert“ würde leicht mehr suggerieren, als tatsächlich geprüft wurde.

Markieren Sie auch Abbruchstellen. Fehlende oder unbekannte Demo-Identität führt vor der Mitgliedschaft zu 401; Nichtmitglieder erhalten vor dem Handler 403. Ruft Middleware next() vor ihrer Entscheidung auf, kann der Handler bereits gelaufen sein. Die Position zählt ebenso wie der Name.

Benutzer, angefragten Arbeitsbereich und gespeichertes Dokument trennen

Eine nützliche Notiz zur fehlerhaften Anfrage passt in vier Zeilen:

Wert Herkunft Wert dieser Anfrage Weiter verfolgen zu
Benutzer und Mitgliedschaften authenticate liest die Identitäts-Map Alice, ['alpha'] requireWorkspaceMember
Angefragter Arbeitsbereich Erstes Routensegment alpha Mitgliedschaft, danach Suchargumente
Angefragtes Dokument Zweites Routensegment doc-b Dokumentsuche
Tatsächlicher Arbeitsbereich des Dokuments Gespeicherter Datensatz beta Suchprädikat und Antwort

Ergänzen Sie beim Prüfen jeder Quelle und Nutzung Datei- und Zeilenangaben. In größeren Anwendungen können dies verschiedene Typen in mehreren Paketen sein. Die Trennung bleibt nützlich: Die URL beschreibt die Anfrage, die Identität Mitgliedschaften und der gespeicherte Datensatz Eigentümerschaft.

Beide IDs kommen gemeinsam an. Prüfen Sie nun, ob sie den Handler gemeinsam verlassen.

Die Suche öffnen, auch wenn der Handler gewöhnlich aussieht

Der absichtlich unsichere Handler lautet:

async function getDocumentUnsafe(ctx) {
  ctx.trace.push(`lookup:unscoped:${ctx.params.documentId}`);
  const document = await ctx.store.findById(ctx.params.documentId);
  if (!document) throw new HttpError(404, 'Not found');
  return json(200, document);
}

Nur documentId erreicht den Store. Folgen Sie findById in createStore: Das Prädikat ist document.id === documentId. Vor der Serialisierung gibt es weder einen Arbeitsbereichsvergleich noch eine spätere Eigentumsprüfung. Die Middleware akzeptierte alpha; diese Suche kann einen Datensatz beliebiger Arbeitsbereiche liefern.

Der Fehler benötigt weder Rennen noch gefälschte Sitzung. Alice behält ihre Identität und den erlaubten Pfad alpha und übergibt die Fixture-ID doc-b. Die Anwendung prüft einen Ressourcenbereich und liest einen anderen.

Ein Helfername allein wäre im echten Projekt ein schwächerer Beleg. Eine Repository-Methode könnte intern nach Mandant filtern, eine eingeschränkte Datenbanksitzung erben oder Row-Level-Regeln nutzen. Lesen Sie Implementierung und Konfiguration, bevor Sie aus einem fehlenden Argument auf unbegrenzte Suche schließen. Hier ist das In-Memory-Prädikat die gesamte Suche und direkt prüfbar.

Das Gegenbeispiel durch die gesamte Handler-Kette ausführen

Führen Sie im Ordner mit beiden Downloads aus:

node request-flow.mjs
node --test request-flow.test.mjs

Installation, Konto und Dienst sind nicht nötig. makeRequest erstellt einen Web-Request; die Tests übergeben ihn an handle, das eine Web-Response liefert. Die URL example.invalid wird nur im Speicher geparst. Das Beispiel ruft nie fetch auf und öffnet keinen lauschenden Socket.

Die Demo gibt folgende Antworten aus, jeweils mit besuchten Schritten und aufgezeichneten Store-Aufrufen darunter:

normal: 200 {"id":"doc-a","workspaceId":"alpha","title":"Alpha release notes"}
reproduce: 200 {"id":"doc-b","workspaceId":"beta","title":"Beta release notes"}
fixed: 404 {"error":"Not found"}

Für die fehlerhafte Anfrage wurden folgende Schritte besucht:

request:GET -> route:alpha:doc-b -> authenticate:alice -> membership:alpha:allowed -> lookup:unscoped:doc-b -> response:200

Das Trace-Array ist zusätzliche Instrumentierung der Fixture und nicht Teil der Antwort. Der Test prüft auch das gelieferte Dokument und den Aufruf findById('doc-b'). Diese Assertions verbinden die Zweigfolge mit tatsächlich gelieferten Daten.

Der Test reproduce: besteht, wenn die unsichere Version das beta-Dokument liefert. Grün bestätigt hier die Reproduzierbarkeit des Defekts. Ein separater Test verlangt für dieselbe Anfrage von der korrigierten Version 404.

Den geprüften Arbeitsbereich in die Abfrage übernehmen

Der korrigierte Handler behält beide Routenwerte:

async function getDocument(ctx) {
  const { workspaceId, documentId } = ctx.params;
  ctx.trace.push(`lookup:scoped:${workspaceId}:${documentId}`);
  const document = await ctx.store.findInWorkspace(workspaceId, documentId);
  if (!document) throw new HttpError(404, 'Not found');
  return json(200, document);
}

In findInWorkspace verlangt das Prädikat document.workspaceId === workspaceId und document.id === documentId. Die Middleware hat bereits genau diese workspaceId geprüft. Die Fixture verändert ctx.params dazwischen nicht; Dokumente und Mitgliedschaften bleiben während jedes Laufs fest.

Für /workspaces/alpha/documents/doc-b/ gibt es jetzt keinen Treffer. Fehlende IDs und IDs fremder Arbeitsbereiche liefern beide 404 mit {"error":"Not found"}. Gleicher Status und Body verraten nicht ausdrücklich, welcher Fall vorliegt; über Timing oder andere Seitenkanäle sagen die Tests nichts aus.

Eine Prüfung von document.workspaceId nach globalem Lesen könnte dieses konkrete Antwortleck ebenfalls stoppen. Die eingeschränkte Suche macht die Grenze am Store-Aufruf sichtbar und liefert keine fremde Zeile an den Handler. Im Speicher prüft Array.find weiterhin Zeilen anhand des Prädikats. Aufgezeichnete Aufrufe zeigen Argumente, nicht Datenbank-I/O oder physische Isolation.

Bei Übertragung auf SQL oder ORM prüfen Sie erzeugte Bedingungen und Parameterbindung und testen mit der echten Datenschicht. Entzogene Mitgliedschaft, verschobene oder geteilte Dokumente und andere Routen brauchen eigene Regeln und Tests.

Erfolgspfad und notwendige Stopps testen

Die Suite enthält zehn Tests: eine absichtliche Reproduktion und neun Prüfungen der korrigierten App. Manche prüfen mehrere Eingaben:

Anfrage oder Bedingung Erwartete Antwort Store-Verhalten
Alice liest alpha/doc-a; Bob liest beta/doc-b 200, richtiges Dokument Beide IDs erreichen findInWorkspace
Alice liest alpha/doc-b 404, kein Dokument Suche bleibt auf alpha begrenzt
Alice liest alpha/missing Status und Body wie zuvor Kein passendes Dokument
Demo-Identität fehlt oder unbekannt 401 Kein Store-Aufruf; Mitgliedschaft nicht erreicht
Alice liest beta/doc-b 403 Kein Store-Aufruf
Alice liest alpha/doc-b?workspaceId=beta 404 Query-Parameter ersetzt Routenbereich nicht
Unbekannter Pfad; falsche Methode auf bekanntem Pfad 404; 405 mit Allow: GET Kein Store-Aufruf
Unerwarteter Store-Fehler 500, allgemeiner Fehler-Body Fehler erreicht Antwort-Wrapper

Die Kurzpfade stehen für /workspaces/{workspaceId}/documents/{documentId}/, mit Query-String nach dem Schrägstrich. Zwei erfolgreiche Lesevorgänge verhindern eine „Korrektur“, die alles ablehnt. Die Nichtaufruf-Assertions erkennen Ablehnung erst nach Store-Zugriff. Zusammen prüfen sie mehr als finale Statuscodes.

Die Tests decken prozessinterne Anfrageverarbeitung, Middleware-Reihenfolge, Suchargumente und JSON-Antworten dieser Route ab. HTTP-Transport, Reverse-Proxys, echte Identitätsprüfung und Datenbank sind nicht enthalten. Beginnen Sie bei echten Anwendungen am HTTP-Adapter und prüfen Sie die Auswahl des untersuchten Handlers und der Middleware.

Eine überprüfbare Notiz hinterlassen

Schreiben Sie das Ergebnis anhand der Anfrage und der genauen Nutzungsstellen ihrer Werte auf:

Trigger: Alice + GET /workspaces/alpha/documents/doc-b/
Route: createApp captures alpha and doc-b.
Membership: requireWorkspaceMember allows Alice for alpha.
Old read: getDocumentUnsafe passes only doc-b to findById.
Observed: 200 returns a row whose workspaceId is beta.
Fix: getDocument passes alpha and doc-b to findInWorkspace.
Regression: same request returns 404; both valid member reads still return 200.
Scope: in-memory request pipeline; production adapter and storage are untested.

Fügen Sie Quellstellen und Testbefehl hinzu. Fragt jemand „Hat diese Route nicht schon Autorisierung?“, zeigt die Notiz genau den Umfang der Prüfung und wo der Arbeitsbereich nicht mehr weitergegeben wurde.