Files
tripz-llc/backend/src/main.ts
T
Hamza-AyedandClaude Opus 4.8 1605754722 feat: باقي المجموعة I — أمان السحب (OTP + بصمة + HMAC + تدقيق)
I4 — OTP على السحب عبر نبيه. تدفّق خطوتين، وقاعدته: **لا يتحرك مال قبل
إثبات الهوية**:
- POST /payouts/request → يرسل رمزاً، حالة pending_otp، **بلا خصم**
- POST /payouts/:id/confirm → يتحقق ثم يحجز ذرّياً (خصم I1)
- حدّ 5 محاولات (رمز 4 خانات يُخمَّن في دقائق بلا حدّ)، الرمز يُستهلك مرة
- complete يرفض طلباً بلا otp_verified_at (حارس ضد تخطّي التأكيد)
- fail لا يردّ مالاً لطلب pending_otp — لم يُخصم منه شيء، وردّه يخلق مالاً

I5 — biometric_method/at + device_id + request_ip على السحب. أثرٌ للتحقيق
لا مصادقة: العميل يستطيع ادّعاءها، والمصادقة الحقيقية JWT + رمز واتساب.

I6 — HMAC **مبنيّ ومطفأ** (PAYMENTS_REQUIRE_SIGNATURE=false) حتى يوقّع
فلاتر؛ تفعيله الآن يقطع كل سحب. مفتاح **لكل جلسة** يُصدره الدخول لا سرّ
ثابت في التطبيق (الثابت يُستخرج بالهندسة العكسية فيصير التوقيع مسرحية).
يوقّع timestamp.METHOD.path.body بنافذة 5 دقائق؛ rawBody مفعّل في main.

I7 — tripz_audit_log append-only: من·ماذا·متى·أي IP وجهاز. لا يرمي أبداً —
فشل التدقيق يجب ألّا يُسقط عمليةً مالية نجحت.

I2 أُلغيت الحاجة إليها: إيراد المنصة = الشحن، و credit_txns دفتره فعلاً؛
محفظة ثانية = دفتر مزدوج يحتاج مطابقة. الباقي تقرير لا محفظة.
I3/I8 مؤجَّلتان بوعي (توثيق في docs/17).

wallet-race-test.mjs حُدِّث: السباق انتقل من request إلى confirm، وأُضيف
محكّ أن الطلب وحده لا يمسّ الرصيد.

هجرة: PayoutSecurityAndAudit (تعتبر السحوبات القائمة مُتحقَّقة وإلا رفض
complete صرفها للأبد).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:22:00 +03:00

55 lines
2.2 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 { 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 cfg = app.get(ConfigService);
// مُحوّل Socket.IO عبر Redis — يفعّل التوسّع الأفقي (عدة نسخ)
const redisIoAdapter = new RedisIoAdapter(app, cfg);
await redisIoAdapter.connectToRedis();
app.useWebSocketAdapter(redisIoAdapter);
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();