Skip to content

/gs/db: pfad-erhaltende host-stabile Dokument-URLs (Entkopplung der Clients vom Storage-Backend)#3958

Merged
TaprootFreak merged 7 commits into
developfrom
feat/gs-proxied-document-urls
Jun 25, 2026
Merged

/gs/db: pfad-erhaltende host-stabile Dokument-URLs (Entkopplung der Clients vom Storage-Backend)#3958
TaprootFreak merged 7 commits into
developfrom
feat/gs-proxied-document-urls

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

Der /gs/db-Pfad liefert in documents[].url die rohe Storage-URL (<storage-backend-host>/kyc/<storage-path>), gebaut aus dem konkreten Storage-Endpoint. Das koppelt die konsumierenden Clients an das jeweilige Storage-Backend: Jede Migration des Storage-Backends ändert oder zerbricht die ausgelieferten URLs.

Wichtige Korrektur ggü. dem ersten Ansatz dieses PRs

Ein erster Ansatz hat documents[].url auf eine uid-basierte Proxy-URL (<services>/file/<uid>?show) umgestellt. Eine Konsumenten-Untersuchung hat ergeben, dass das aktive Konsumenten bricht:

Die konsumierenden Auswertungen extrahieren den Dateinamen aus der URL über

url.split('<scope>/<id>/<type>')[1]   // z. B. url.split('user/40102/UserNotes')[1] -> '/note.pdf'

Die uid-Form enthält den Storage-Pfad <scope>/<id>/<type>/<name> nicht mehr → der Split liefert undefined → die Dokument-Anzeige der betroffenen Auswertungen wird leer. Betroffen sind 12 aktive KYC-/Compliance-Operator-Auswertungen, davon 4 sofort brechend.

Daraus folgt ein harter, nicht verhandelbarer Invariant: documents[].url muss den Storage-Pfad <scope>/<id>/<type>/<name> als unveränderten Substring enthalten.

Lösung: host-stabil UND pfad-erhaltend

documents[].url wird so umgeschrieben, dass nur der Host auf die stabile Services-Domain (Config.frontend.services) gepinnt wird, der vollständige Storage-Pfad aber unverändert erhalten bleibt:

<storage-backend-host>/kyc/<scope>/<id>/<type>/<name>   (vorher, roh)
<services>/kyc/<scope>/<id>/<type>/<name>               (nachher, host-stabil + pfad-erhaltend)

Damit gilt beides:

  1. host-stabil — kein Storage-Backend-Host mehr in der URL; eine Storage-Migration ändert die ausgelieferte URL nicht.
  2. pfad-erhaltend — der Storage-Pfad bleibt unveränderter Substring, das bestehende url.split('<scope>/<id>/<type>')[1]-Parsing der Konsumenten funktioniert unverändert weiter.

Die URL-Struktur ist strukturgleich zur bisherigen rohen URL (gleicher Container-Prefix kyc, gleicher Pfad) — nur der Host ist getauscht. Das maximiert die Kompatibilität mit bestehenden Konsumenten.

Änderungen

  • KycDocumentService.toHostStableUrl(path) (neu): Baut aus einem Storage-Pfad die host-stabile URL ${Config.frontend.services}/kyc/${path}. Die Host-/URL-Form liegt bewusst hier, neben der übrigen Storage-/URL-Logik des Services; der Container-Prefix wird als benannte Konstante geführt.
  • GsService.setUserDataDocs(...): Ersetzt vor der Ausgabe die rohe .url jedes Dokuments durch toHostStableUrl(doc.path).
  • Filterung host-unabhängig: Die interne Pfad-Filterung partitioniert die Dokumente über den Storage-Pfad (doc.path) statt über die .url. Verhalten ist gleichwertig zur bisherigen URL-Substring-Filterung (der Storage-Pfad war auch in der rohen URL Substring), aber entkoppelt von der URL-Form.

Offener Punkt für den API-Owner: Dereferenzierbarkeit der URL

Die gewählte Form erfüllt den Parsing-Invariant der Konsumenten (Namens-Extraktion) und ist host-stabil. Offen ist, ob die URL zusätzlich ein abrufbarer Link sein muss:

  • Heute existiert serverseitig nur ein uid-basierter Serve-Endpunkt (GET /kyc/file/:id). Einen pfad-basierten Serve-Endpunkt unter <services>/kyc/<storage-path>, der diese URL tatsächlich dereferenziert, gibt es nicht.
  • Sofern Konsumenten die URL ausschließlich zum Parsen des Dateinamens verwenden (was den 12 untersuchten Auswertungen entspricht), ist die hier gewählte Form vollständig ausreichend.
  • Sofern die URL auch als klickbarer/abrufbarer Link gebraucht wird, wäre ein pfad-basierter Serve-Endpunkt nötig. Das ist ein eigener, grösserer Umbau (Routing + Auth + Pfad→Datei-Auflösung) und ist bewusst nicht Teil dieses PRs.

Dieser Punkt ist hier explizit als bewusst offene Entscheidung dokumentiert und nicht stillschweigend vorweggenommen; Entscheidung beim API-Owner.

Bewusste Abgrenzung

  • getSupportData (/gs/support) liefert seine documents[].url weiterhin als rohe Storage-URL. Dieser Pfad bedient ein internes Support-/Admin-Tool und ist hier bewusst nicht mitgeändert, um die Änderung auf die client-/auswertungs-sichtbare /gs/db-Ausgabe zu fokussieren. Eine Vereinheitlichung (gleicher Helper) ist als Folge-Schritt denkbar.

Verwandt

Verifikation

  • tsc --noEmit: fehlerfrei (exit 0)
  • prettier: konform (geänderte Dateien)
  • eslint (geänderte Dateien): keine Fehler, kein any in den Tests
  • jest gs.service.spec.ts: 34/34 grün, inkl. der neuen Tests:
    • Host wird auf die Services-Domain gepinnt, Storage-Backend-Host verschwindet, Storage-Pfad bleibt Substring
    • Konsumenten-Invariant url.split('<scope>/<id>/<type>')[1] liefert weiterhin den Dateinamen
    • Filterung partitioniert weiterhin korrekt über den Storage-Pfad trotz umgeschriebener URL
    • echter Round-Trip über die reale KycDocumentService: gelistetes Blob (b.namedoc.path) wird host-stabil + pfad-erhaltend umgeschrieben (user- und spider-Pfad)

Release Checklist

Pre-Release

  • Check migrations
    • No database related infos (sqldb-xxx): keine Migrationen, kein DB-Schema-Change
    • Impact on GS (new/removed columns): ja — /gs/db liefert documents[].url jetzt host-stabil (<services>/kyc/<storage-path>) statt der rohen Storage-URL; der Storage-Pfad bleibt unverändert enthalten, das bestehende Auswertungs-Parsing funktioniert weiter; keine neuen/entfernten Spalten
  • Check for linter errors (in PR): ok
  • Test basic user operations (on DFX services): offen
    • Login/logout
    • Buy/sell payment request
    • KYC page

Post-Release

  • Test basic user operations
  • Monitor application insights log

Consumer-Sync (CONTRIBUTING #4) — verifiziert

Vollständige Untersuchung der betroffenen /gs/db-documents-Konsumenten:

  • Sie nutzen documents[].url ausschliesslich zum Parsen des relativen Dateinamens (url.split(<storagePath>)[1]).
  • Den klickbaren/abrufbaren Link bauen sie unabhängig zusammen (separate Storage-Basis + Zugriffstoken), nicht aus documents[].url.

→ Die host-stabile, pfad-erhaltende Form hält das Namen-Parsing intakt → für diese Konsumenten bruchfrei. Ein pfad-dereferenzierender Server-Endpunkt ist für diese Änderung nicht erforderlich.

Offene Punkte (bewusst, Owner-Entscheid)

  • Dereferenzierbarkeit (verifiziert): Über ALLE betroffenen /gs/db-documents-Konsumenten wurde geprüft, dass keiner documents[].url direkt klickt/abruft — sie parsen nur den Dateinamen und bauen den klickbaren Link unabhängig (separate Basis + Zugriffstoken) bzw. zeigen die Spalte nur an. Die host-stabile URL muss daher NICHT dereferenzierbar sein; ein pfad-basierter Serve-Endpunkt ist für die heutigen Konsumenten nicht erforderlich (falls künftig nötig, separat ergänzbar).
  • URL-Encoding: toHostStableUrl enkodiert Pfadsegmente identisch zur früheren rohen Blob-URL (per-Segment encodeURIComponent, Slashes erhalten) → byte-identisch ausser Host (echter Drop-in), der extrahierte Dateiname-Suffix bleibt unverändert.

Die /gs/db-Dokumentausgabe (GsService.setUserDataDocs) liefert in
documents[].url bisher die rohe Storage-URL, gebaut aus dem konkreten
Storage-Endpoint. Das koppelt API-/Sheet-Clients an das Storage-Backend;
jede Storage-Migration aendert/zerbricht die ausgelieferten URLs.

Aenderungen:
- KycDocumentService.getUserFileProxyUrlMap(userDataId): bildet den
  Storage-Pfad einer KYC-Datei auf ihre host-stabile, proxied URL ab
  (<services>/file/<uid>?show, ausgeliefert von GET /kyc/file/:id).
- setUserDataDocs: ersetzt vor der Ausgabe die rohe .url durch die
  proxied URL, sofern eine zugehoerige KycFile-Entity (uid) existiert.
- Die interne Filterung partitioniert jetzt ueber den Storage-Pfad
  (doc.path) statt ueber die nun proxied .url - host-unabhaengig und
  verhaltensgleich zur bisherigen URL-Substring-Filterung.

Dateien ohne KycFile-Entity (z. B. Legacy-/Spider-Dateien) haben keine
uid und behalten die rohe URL als sicheren Fallback.
…ny aus Tests entfernen

- neuer Test ohne Mock von getUserFileProxyUrlMap: gelistetes Blob matcht
  über den Storage-Pfad (b.name == user/<id>/<type>/<name>) die aus einer
  KycFile gebaute Proxy-Map und wird proxied; spider-/Legacy-Dokument fällt
  auf die rohe Storage-URL zurueck
- any[] durch Partial<UserData>[] ersetzt, as-never-Casts in den neuen Tests
  durch typisierte Mock-Helfer (asKycFileBlobs/storageBlob) abgeloest
- GsService-Konstruktion in wiederverwendbaren buildGsService-Helfer extrahiert
… sortieren

- gs.service.spec: beforeEach nutzt buildGsService-Helper wieder, statt die ~22 Mocks erneut auszuschreiben (Helper nimmt appInsightsQueryService optional entgegen, damit die Tests dieselben Mock-Instanzen sehen)
- getUserFileProxyUrlMap: KycFile-Zeilen vor dem Map-Aufbau deterministisch nach id aufsteigend sortieren, damit bei Re-Uploads (gleicher type+name) der neueste Eintrag konsistent gewinnt
…iant)

Die uid-basierte Proxy-URL-Form bricht aktive Konsumenten: Die Auswertungen
extrahieren den Dateinamen ueber url.split('<scope>/<id>/<type>')[1] und
brauchen daher den Storage-Pfad als Substring der URL. Die uid-Form enthaelt
diesen nicht mehr.

Statt der uid-Form wird jetzt nur der Host auf Config.frontend.services
gepinnt, der vollstaendige Storage-Pfad bleibt erhalten:
<services>/kyc/<scope>/<id>/<type>/<name>. Damit ist die URL host-stabil
(kein Storage-Backend-Host) und gleichzeitig pfad-erhaltend, der Konsumenten-
Invariant bleibt gewahrt.

- KycDocumentService.getUserFileProxyUrlMap durch toHostStableUrl(path) ersetzt
- GsService.setUserDataDocs schreibt doc.url = toHostStableUrl(doc.path)
- Tests asserten Host-Stabilitaet, Pfad-Erhalt und das Split-Parsing der
  Konsumenten; Round-Trip ueber die reale KycDocumentService
@TaprootFreak TaprootFreak changed the title /gs/db: host-stabile proxied Dokument-URLs (Entkopplung der Clients vom Storage-Backend) /gs/db: pfad-erhaltende host-stabile Dokument-URLs (Entkopplung der Clients vom Storage-Backend) Jun 23, 2026
…st-Imports absolut

Die per-Segment-Encoding-Zeile (encodeURIComponent je Segment, Slashes
erhalten) lag dupliziert in azure-storage.service.ts blobUrl und in
kyc-document.service.ts toHostStableUrl. Sie wird in die geteilte statische
Methode AzureStorageService.encodePath ausgelagert; beide Aufrufer nutzen sie,
womit der "byte-identisch ausser Host"-Invariant strukturell erzwungen ist
(Verhalten unverändert).

Die neu hinzugefügten Test-Imports in gs.service.spec.ts werden gemäss
CONTRIBUTING auf absolute src/-Pfade umgestellt.
@TaprootFreak TaprootFreak marked this pull request as ready for review June 24, 2026 00:21
@TaprootFreak TaprootFreak merged commit fc42103 into develop Jun 25, 2026
7 checks passed
@TaprootFreak TaprootFreak deleted the feat/gs-proxied-document-urls branch June 25, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant