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 keyspinbox24Public/pinbox24PublicOffice— pliki publiczne/biurowe (avatary, loga) — obsługiwane przezs3-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_accessKeyIdwsecrets/pinbox24-w4.env.sops← AKTUALNIE 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)
| Plik | Funkcja |
|---|---|
/app/dist/apps/files/file.route.js:53 | router.get("/:fileId/getSignedUrl", getSignedUrl) |
/app/dist/apps/files/file.controller.js:79 | exports.getSignedUrl = (req, res) => getSignedUrlHelper(req.params.fileId, req.query.retry) |
/app/dist/apps/files/file.helper.js | getSignedUrlHelper(fileId, retry) — findById + wywołanie s3 |
/app/dist/globalHelpers/uploadAwsS3.helper.js | s3getSignedUrl(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
| Parametr | Wartość | Opis |
|---|---|---|
Expires | 1800 | 30 minut ważności |
ResponseContentDisposition | "inline" | Otwiera w przeglądarce zamiast pobierania |
ResponseContentType | auto z doc.mimeType lub brak | Opcjonalnie typ MIME |
3.6 Warunki błędu
| Błąd | Przyczyna | Fix |
|---|---|---|
AccessDenied 403 | Klucz IAM skasowany lub nie ma dostępu do bucketu | Zaktualizuj s3Bucket_api_accessKeyId w backend-environment.env |
InvalidAccessKeyId 403 | Klucz nie istnieje w Wasabi IAM | Utwórz nowy klucz dla pinbox24-bms1-s3 |
FileNotFound 404 | fileId nie istnieje w MongoDB | Klient wysyła złe ID |
TokenExpiredError | JWT 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 prefix | Status | Uwagi |
|---|---|---|
WKRQ**** | Active | Prawdopodobnie nowy, nieużywany |
LGQ2**** | Active | Prawdopodobnie 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 inline5. 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
| Plik | Zawartość | Użytkownik |
|---|---|---|
/home/p24-server-scripts/v4/v42/backend-environment.env | Wszystkie env vars v42-prod | v42-prod container |
/home/p24-server-scripts/v4/v42/s3-environment.env | STARY klucz VCUC**** (usunięty) — nie używany bezpośrednio | deprec |
/root/s3v2-prod/s3-v2-environment.env | Klucze public/office dla s3-v2 | s3-v2-v42-prod container |
5.3 Re-apply script
/root/patch-v42.sh — stosuje 7 patchy po docker recreate:
socket.middleware.js— try-catch + null guardsocketListener.helper.js— null guardio.sockets[id]app.js— CORSworkspaceindex, wyłączony sensor cronredis.config.js— host 172.18.0.9:6379socket.helper.js— host 172.18.0.9:6379, brak TLS/authsocketInitUser.helper.js— null guardyuserProfileDoc+channelList- WSDL copy —
dist/wsdl/UslugaBIRzewnPubl-ver11-test.wsdl
6. Długoterminowy plan
| Priorytet | Zadanie | Opis |
|---|---|---|
| P1 | Klucz IAM w SOPS | Wypełnij V42_s3Bucket_api_accessKeyId w secrets/pinbox24-w4.env.sops |
| P1 | Baked Docker image | Zamroź aktualny stan jako v42-prod:stable-20260706, udokumentuj w Supabase dev_r_services |
| P2 | redis-v42 w docker-compose | Dodaj redis-v42 do docker-compose.yml żeby przeżył recreate |
| P2 | Redis config bez hardcoded IP | Użyj nazwy serwisu (redis-v42) zamiast 172.18.0.9 |
| P3 | Rebuild v42 z source | Zbuduj nowy obraz z patchami w źródle, nie jako in-container fixes |
| P3 | getBase64File deprecation | Zastąp getBase64File przez getSignedUrl we frontendzie W4 |