feat: TLS — إغلاق المنافذ المكشوفة + الثقة بالبروكسي + دليل الإصدار

النطاق: tripz-api.intaleqapp.com (قرار المالك) — مملوك وجاهز، ومحايد عن
اسم العلامة الذي لم يُحسم فلا يحتاج تغييراً لاحقاً.

ثغرتان كانتا ستُبطلان TLS من أصله:
- API كان منشوراً على 0.0.0.0:4010 → صار 127.0.0.1. بلا هذا يبقى
  http://IP:4010 مفتوحاً فيتجاوز TLS كلياً وتصير الشهادة زينة
- **Postgres كان مكشوفاً على 0.0.0.0:55432** — القاعدة على الإنترنت مباشرة
  → صار 127.0.0.1. المنفذ غير القياسي إخفاء لا أمان. الوصول من الماك عبر
  نفق SSH (docs/20 §4)

trust proxy = 1: خلف Nginx كان req.ip يصير 127.0.0.1 للجميع فيمتلئ سجل
التدقيق المالي (I7) بعنوان البروكسي بدل السائق — أثرٌ بلا قيمة عند النزاع.
قفزة واحدة فقط: الثقة المفتوحة تسمح للعميل بتزوير X-Forwarded-For.

docs/20: DNS → موقع reverse proxy → شهادة → **إعداد ترقية WebSocket**
(بدونه يسقط التتبّع الحيّ) + مهلة 3600s للنبضات طويلة العمر + فحص تحقّق
أهمّه أن http://IP:4010 يجب أن **يفشل**.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-17 16:36:14 +03:00
co-authored by Claude Opus 4.8
parent 1605754722
commit d2a34f753c
5 changed files with 117 additions and 8 deletions
+2 -1
View File
@@ -2,7 +2,8 @@
# ملاحظة: السيرفر مشترك — كل شيء معزول ببادئة tripz و Redis DB غير الافتراضي (راجع docs/14)
NODE_ENV=development
API_PORT=4010 # منفذ غير شائع لتفادي التصادم على السيرفر المشترك
API_PORT=4010 # يُنشر على 127.0.0.1 فقط — Nginx (CloudPanel) ينهي الـTLS. راجع docs/20
DB_EXPOSE_PORT=55432 # 127.0.0.1 فقط. للوصول من الماك: نفق SSH (docs/20 §4)
# ---- PostgreSQL (قاعدة مستقلة + بادئة جداول) ----
DB_HOST=postgres
+8 -2
View File
@@ -16,7 +16,10 @@ services:
volumes:
- tripz-pgdata:/var/lib/postgresql/data
ports:
- "${DB_EXPOSE_PORT:-55432}:5432" # منفذ مضيف غير قياسي (عزل)
# 127.0.0.1 فقط — كانت مكشوفة على 0.0.0.0 أي أن القاعدة كانت على
# الإنترنت مباشرة. المنفذ غير القياسي ليس أماناً، هو إخفاء.
# للوصول من الماك: نفق SSH (راجع docs/20).
- "127.0.0.1:${DB_EXPOSE_PORT:-55432}:5432"
networks: [tripz-net]
redis:
@@ -50,7 +53,10 @@ services:
command: ["node", "dist/main.js"]
depends_on: [postgres, redis]
ports:
- "${API_PORT:-4010}:4010"
# 127.0.0.1 فقط: لا يصل إليه إلا Nginx المحلي (CloudPanel) الذي ينهي
# الـTLS. بلا هذا يبقى http://IP:4010 مفتوحاً فيتجاوز الـTLS كله
# ويصير الشهادة زينة (docs/20).
- "127.0.0.1:${API_PORT:-4010}:4010"
volumes:
- tripz-storage:/app/storage
networks: [tripz-net]
+8 -1
View File
@@ -3,13 +3,14 @@ import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { RedisIoAdapter } from './realtime/redis-io.adapter';
async function bootstrap() {
// rawBody: التوقيع يُحسب على الجسم **كما وصل** (docs/17 — I6). إعادة تسلسل
// JSON قد تغيّر ترتيب المفاتيح أو المسافات فيفشل توقيع سليم.
const app = await NestFactory.create(AppModule, { rawBody: true });
const app = await NestFactory.create<NestExpressApplication>(AppModule, { rawBody: true });
const cfg = app.get(ConfigService);
// مُحوّل Socket.IO عبر Redis — يفعّل التوسّع الأفقي (عدة نسخ)
@@ -17,6 +18,12 @@ async function bootstrap() {
await redisIoAdapter.connectToRedis();
app.useWebSocketAdapter(redisIoAdapter);
// خلف بروكسي (Nginx ينهي الـTLS): بلا هذا يصير req.ip = 127.0.0.1 للجميع،
// فيمتلئ سجل التدقيق المالي (docs/17 — I7) بعنوان البروكسي بدل عنوان
// السائق — أي أثرٌ بلا قيمة عند النزاع. `1` = ثقة بقفزة واحدة فقط؛ الثقة
// المفتوحة تسمح للعميل بتزوير X-Forwarded-For.
app.set('trust proxy', 1);
app.setGlobalPrefix('api');
app.useGlobalPipes(
new ValidationPipe({ whitelist: true, transform: true }),