النطاق: 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>
62 lines
2.7 KiB
TypeScript
62 lines
2.7 KiB
TypeScript
import 'reflect-metadata';
|
|
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<NestExpressApplication>(AppModule, { rawBody: true });
|
|
const cfg = app.get(ConfigService);
|
|
|
|
// مُحوّل Socket.IO عبر Redis — يفعّل التوسّع الأفقي (عدة نسخ)
|
|
const redisIoAdapter = new RedisIoAdapter(app, cfg);
|
|
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 }),
|
|
);
|
|
app.enableCors();
|
|
|
|
const swagger = new DocumentBuilder()
|
|
.setTitle('Tripz API')
|
|
.setDescription('منصة نقل ذكي متعددة المستأجرين')
|
|
.setVersion('0.1.0')
|
|
.addBearerAuth()
|
|
.build();
|
|
SwaggerModule.setup('api/docs', app, SwaggerModule.createDocument(app, swagger));
|
|
|
|
if (!process.env.ENCRYPTION_KEY) {
|
|
Logger.warn(
|
|
'ENCRYPTION_KEY غير مضبوط — يُستخدم مفتاح تطوير غير آمن. اضبطه للإنتاج (openssl rand -hex 32).',
|
|
'Security',
|
|
);
|
|
}
|
|
if (!process.env.PLATFORM_SECRET) {
|
|
Logger.warn('PLATFORM_SECRET غير مضبوط — كل نقاط السوبر-أدمن مغلقة.', 'Security');
|
|
}
|
|
if (cfg.get<boolean>('payments.requireSignature') !== true) {
|
|
Logger.warn(
|
|
'PAYMENTS_REQUIRE_SIGNATURE=false — العمليات المالية غير موقَّعة. فعّله بعد أن يوقّع تطبيق فلاتر (docs/17 I6).',
|
|
'Security',
|
|
);
|
|
}
|
|
|
|
const port = cfg.get<number>('apiPort') ?? 4010;
|
|
await app.listen(port, '0.0.0.0');
|
|
Logger.log(`Tripz API on :${port} (docs at /api/docs)`, 'Bootstrap');
|
|
}
|
|
bootstrap();
|