Spec: W4 — Pliki S3 / Wasabi i Inline Preview

Wersja: 1.0
Data: 2026-07-06
Autor: Claude (p24-infra sys-admin role)
Status: DRAFT — odzwierciedla stan rzeczywisty + wymagane fixe


1. Przegląd architektury

W4 (w4.pinbox24.com) przechowuje pliki w dwóch bucketach Wasabi:

  • pinbox24 — prywatne dokumenty rejestru (umowy, faktury, potwierdzenia), tylko IAM keys
  • pinbox24Public / pinbox24PublicOffice — pliki publiczne/biurowe (avatary, loga) — obsługiwane przez s3-v2-v42-prod

2. Kontenery i IAM

2.1 v42-prod — główny backend Node.js + PM2

Rola S3: generuje presigned URLs dla prywatnych dokumentów, serwuje getBase64File (deprecated).

Klucz IAM: pinbox24-bms1-s3 user w Wasabi

  • IAM user: pinbox24-bms1-s3
  • Env var: s3Bucket_api_accessKeyId + s3Bucket_api_secretAccessKey
  • Bucket: pinbox24
  • Endpoint: https://s3.eu-central-1.wasabisys.com
  • Dostęp: pełny (upload + download + signed URL)
  • SOPS key: V42_s3Bucket_api_accessKeyId w secrets/pinbox24-w4.env.sopsAKTUALNIE PUSTE — wymaga uzupełnienia

2.2 s3-v2-v42-prod — proxy S3 v2

Rola S3: obsługuje upload/download publicznych plików biurowych.

Klucz IAM: s3v2-prod-bms1 user w Wasabi

  • IAM user: s3v2-prod-bms1
  • Env var: pinbox24PublicOfficeAccessKeyId + pinbox24PublicOfficeSecretAccessKey
  • Bucket: pinbox24PublicOffice
  • Config: persistent-patches/storage.config.js (bind mount na kontener)

3. Endpoint: GET /api/offices/files/:fileId/getSignedUrl

3.1 Cel

Generuje tymczasowy presigned URL (Wasabi AWS SigV4) dla pliku dokumentu, który można otworzyć inline w przeglądarce (PDF viewer, preview) bez pobierania.

3.2 Przepływ

Browser → GET /api/offices/files/:fileId/getSignedUrl
  → [officeAuth middleware — weryfikacja JWT]
  → file.route.js → file.controller.js → file.helper.js
  → FileModel.findById(fileId) → {path, storageInfo[0].bucketName}
  → uploadAwsS3.helper.js: s3getSignedUrl(path, bucketName)
  → AWS.S3.getSignedUrl("getObject", {
      Bucket: bucketName,
      Key: path,
      Expires: 1800,                          // 30 minut
      ResponseContentDisposition: "inline",   // otwiera w przeglądarce
      ResponseContentType: (auto z rozszerzenia)
    })
  → zwraca: { url: "https://s3.eu-central-1.wasabisys.com/pinbox24/..." }

3.3 Pliki kodu (w obrazie v42-prod:merged-20260706-0512)

PlikFunkcja
/app/dist/apps/files/file.route.js:53router.get("/:fileId/getSignedUrl", getSignedUrl)
/app/dist/apps/files/file.controller.js:79exports.getSignedUrl = (req, res) => getSignedUrlHelper(req.params.fileId, req.query.retry)
/app/dist/apps/files/file.helper.jsgetSignedUrlHelper(fileId, retry) — findById + wywołanie s3
/app/dist/globalHelpers/uploadAwsS3.helper.jss3getSignedUrl(key, bucketName) — AWS SDK v2, SigV4

3.4 Konfiguracja AWS SDK (uploadAwsS3.helper.js)

const s3 = new AWS.S3({
  accessKeyId: process.env.s3Bucket_api_accessKeyId,
  secretAccessKey: process.env.s3Bucket_api_secretAccessKey,
  endpoint: "s3.eu-central-1.wasabisys.com",
  region: "us-east-1",
  signatureVersion: "v4",
});

Uwaga: Wasabi eu-central-1 wymaga signatureVersion: 'v4' i region: 'us-east-1' (Wasabi nie rozróżnia regionów logicznie, używa tego jako default).

3.5 Parametry podpisanego URL

ParametrWartośćOpis
Expires180030 minut ważności
ResponseContentDisposition"inline"Otwiera w przeglądarce zamiast pobierania
ResponseContentTypeauto z doc.mimeType lub brakOpcjonalnie typ MIME

3.6 Warunki błędu

BłądPrzyczynaFix
AccessDenied 403Klucz IAM skasowany lub nie ma dostępu do bucketuZaktualizuj s3Bucket_api_accessKeyId w backend-environment.env
InvalidAccessKeyId 403Klucz nie istnieje w Wasabi IAMUtwórz nowy klucz dla pinbox24-bms1-s3
FileNotFound 404fileId nie istnieje w MongoDBKlient wysyła złe ID
TokenExpiredErrorJWT wygasłUżytkownik musi odświeżyć sesję

4. Zarządzanie kluczami IAM

4.1 IAM User dla prywatnego bucketu

User: pinbox24-bms1-s3
Bucket: pinbox24
Region: eu-central-1 (Wasabi Frankfurt)
Endpoint: https://s3.eu-central-1.wasabisys.com
Policy: pełny dostęp do bucket pinbox24 (s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket)

4.2 Aktualny stan kluczy

Key ID prefixStatusUwagi
WKRQ****ActivePrawdopodobnie nowy, nieużywany
LGQ2****ActivePrawdopodobnie stary, do usunięcia przy rotacji

Action required: usuń LGQ2****, utwórz nowy klucz, zaktualizuj backend-environment.env i SOPS.

4.3 Procedura rotacji klucza s3Bucket_api_accessKeyId

# 1. Otwórz SOPS admin
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
 
# 2. Użyj Wasabi admin (z secrets/administration.env.sops):
#    - Usuń stary klucz dla pinbox24-bms1-s3
#    - Utwórz nowy klucz
#    - Zapisz ACCESS_KEY_ID i SECRET_ACCESS_KEY
 
# 3. Zaktualizuj backend-environment.env na bms-1:
# ssh root@94.23.26.113
# sed -i "s|^s3Bucket_api_accessKeyId=.*|s3Bucket_api_accessKeyId=NEW_KEY|" /home/p24-server-scripts/v4/v42/backend-environment.env
# sed -i "s|^s3Bucket_api_secretAccessKey=.*|s3Bucket_api_secretAccessKey=NEW_SECRET|" /home/p24-server-scripts/v4/v42/backend-environment.env
 
# 4. Zrestartuj v42-prod:
# docker exec v42-prod pm2 reload all
 
# 5. Zaktualizuj SOPS:
# sops --decrypt secrets/pinbox24-w4.env.sops → edytuj V42_s3Bucket_api_accessKeyId + V42_s3Bucket_api_secretAccessKey → sops encrypt
 
# 6. Test:
# curl -H "Authorization: Bearer $TOKEN" https://w4.pinbox24.com/api/offices/files/FILE_ID/getSignedUrl
# → { "url": "https://s3.eu-central-1.wasabisys.com/..." }
# Otwórz URL w przeglądarce → plik PDF inline

5. Konfiguracja serwera bms-1

5.1 Kontenery Docker

v42-prod                (image: v42-prod:merged-20260706-0512)
  PM2 cluster: 2x v42-prod_backend
  Network: prod-v-4-net
  Port: 3000 (wewnętrzny)

redis-v42               (image: redis:7-alpine)
  IP: 172.18.0.9:6379
  Network: prod-v-4-net
  Auth: brak (LAN only)

s3-v2-v42-prod          (obraz: s3-v2-v42-prod:latest)
  Bind mount: persistent-patches/storage.config.js → /app/dist/config/storage.config.js:ro
  Env: pinbox24Public*, pinbox24PublicOffice*

5.2 Env files na serwerze

PlikZawartośćUżytkownik
/home/p24-server-scripts/v4/v42/backend-environment.envWszystkie env vars v42-prodv42-prod container
/home/p24-server-scripts/v4/v42/s3-environment.envSTARY klucz VCUC**** (usunięty) — nie używany bezpośredniodeprec
/root/s3v2-prod/s3-v2-environment.envKlucze public/office dla s3-v2s3-v2-v42-prod container

5.3 Re-apply script

/root/patch-v42.sh — stosuje 7 patchy po docker recreate:

  1. socket.middleware.js — try-catch + null guard
  2. socketListener.helper.js — null guard io.sockets[id]
  3. app.js — CORS workspaceindex, wyłączony sensor cron
  4. redis.config.js — host 172.18.0.9:6379
  5. socket.helper.js — host 172.18.0.9:6379, brak TLS/auth
  6. socketInitUser.helper.js — null guardy userProfileDoc + channelList
  7. WSDL copy — dist/wsdl/UslugaBIRzewnPubl-ver11-test.wsdl

6. Długoterminowy plan

PriorytetZadanieOpis
P1Klucz IAM w SOPSWypełnij V42_s3Bucket_api_accessKeyId w secrets/pinbox24-w4.env.sops
P1Baked Docker imageZamroź aktualny stan jako v42-prod:stable-20260706, udokumentuj w Supabase dev_r_services
P2redis-v42 w docker-composeDodaj redis-v42 do docker-compose.yml żeby przeżył recreate
P2Redis config bez hardcoded IPUżyj nazwy serwisu (redis-v42) zamiast 172.18.0.9
P3Rebuild v42 z sourceZbuduj nowy obraz z patchami w źródle, nie jako in-container fixes
P3getBase64File deprecationZastąp getBase64File przez getSignedUrl we frontendzie W4