Files
tripz-llc/backend/src/config/configuration.ts
T
Hamza-AyedandClaude Opus 4.8 1eb79fee99 feat: المجموعة D — تطبيع الهاتف + ربط الجلسة بالجهاز
D1 — تطبيع أرقام الهاتف (JO/EG/SY):
- common/phone/phone.service.ts: مفتاح قانوني واحد لكل رقم حقيقي — يقبل
  صفراً محلياً/دولياً/+/00/بلا صفر ويرجع صيغة دولية موحّدة بلا +
- يعالج لبس مصر تحديداً الذي ذكره المالك: "01012345678" يُطبَّع بمفتاح 20
  الصحيح لا "2" — الخوارزمية عامة فلا تحتاج معرفة اللبس، فقط تحذف الصفر
  البادئ وتضيف مفتاح الدولة الحقيقي
- send-otp و verify-otp و platform/users/role تمرّ كلها بالتطبيع الآن؛
  كانت تخزّن/تبحث بالرقم الخام كما كُتب — رقم واحد بصيغتين = حسابان
- هجرة NormalizePhones تُصحّح حسابات الاختبار الموجودة على السيرفر، بحذر:
  تتخطّى أي تصادم بدل كسر القيد الفريد (tenant_id, phone)
- أُزيل config.callingCodes المكرّر — مصدر واحد للحقيقة

D2 — ربط الجلسة بالجهاز (نمط سيرو): **مبنيّ ومطفأ**
(AUTH_REQUIRE_DEVICE_BINDING=false) حتى يرسل فلاتر x-device-id. منفَّذ داخل
JwtStrategy.validate نفسها (passReqToCallback) لا كحارس يُضاف يدوياً لكل
متحكّم — فلا نقطة محميّة يمكن نسيانها. التوكن يحمل hash(deviceId) لا القيمة
الخام. verifyOtp/refresh يمرّران x-device-id من المتحكّم عند إصدار التوكن.

D3 — HMAC: منفَّذ فعلاً ضمن I6 (SigningService/SignatureGuard)، لا تكرار.

خطأ ضبطته قبل الدفع: كتبت مفتاح `auth:` ثانياً في configuration.ts —
كائنات JS تسمح بمفاتيح مكرّرة والأخير يطغى، فكان سيمحو otpDevMode/otpTtl/
otpLength بالكامل. دُمج في الكتلة الأصلية بدل مفتاح جديد.

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

101 lines
4.2 KiB
TypeScript

/**
* إعداد مركزي يقرأ متغيرات البيئة مع افتراضات العزل (راجع docs/14).
*/
export default () => ({
env: process.env.NODE_ENV ?? 'development',
apiPort: parseInt(process.env.API_PORT ?? '4010', 10),
db: {
host: process.env.DB_HOST ?? 'postgres',
port: parseInt(process.env.DB_PORT ?? '5432', 10),
name: process.env.DB_NAME ?? 'tripz',
user: process.env.DB_USER ?? 'tripz',
password: process.env.DB_PASSWORD ?? 'change_me_strong',
// بادئة الجداول لعزل Tripz عن باقي البرامج على نفس السيرفر
tablePrefix: process.env.DB_TABLE_PREFIX ?? 'tripz_',
synchronize: process.env.DB_SYNC === 'true',
},
redis: {
host: process.env.REDIS_HOST ?? 'redis',
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
// DB رقم غير الافتراضي 0 لعزل Tripz عن باقي البرامج
db: parseInt(process.env.REDIS_DB ?? '3', 10),
keyPrefix: process.env.REDIS_KEY_PREFIX ?? 'tripz:',
},
queue: {
prefix: process.env.QUEUE_PREFIX ?? 'tripz_',
},
jwt: {
secret: process.env.JWT_SECRET ?? 'change_me_jwt_secret',
expires: process.env.JWT_EXPIRES ?? '15m',
refreshExpires: process.env.JWT_REFRESH_EXPIRES ?? '30d',
},
auth: {
// وضع تطوير: رمز OTP ثابت 0000 يُطبع باللوغ بلا مزوّد SMS (راجع docs/07).
// للإنتاج: OTP_DEV_MODE=false + ربط محوّل SMS في P2.
otpDevMode: process.env.OTP_DEV_MODE !== 'false',
otpTtl: parseInt(process.env.OTP_TTL ?? '300', 10),
otpLength: parseInt(process.env.OTP_LENGTH ?? '4', 10),
// ربط الجلسة بالجهاز (docs/17 — D2). مطفأ حتى يرسل فلاتر x-device-id؛
// تفعيله قبل ذلك يقطع كل طلب مصادَق (نفس نمط PAYMENTS_REQUIRE_SIGNATURE).
requireDeviceBinding: process.env.AUTH_REQUIRE_DEVICE_BINDING === 'true',
},
maps: {
tilesUrl: process.env.MAPS_TILES_URL ?? 'http://martin:3000',
provider: process.env.MAPS_PROVIDER ?? 'antlaq',
// خرائط انطلق (map-saas). مفتاح افتراضي + مفاتيح لكل دولة.
baseUrl: process.env.MAPS_BASE_URL ?? 'https://map-saas.intaleqapp.com',
apiKey: process.env.MAPS_API_KEY ?? '',
apiKeyByCountry: {
jo: process.env.MAPS_API_KEY_JO ?? process.env.MAPS_API_KEY ?? '',
sy: process.env.MAPS_API_KEY_SY ?? process.env.MAPS_API_KEY ?? '',
} as Record<string, string>,
placesApiKey: process.env.MAPS_PLACES_API_KEY ?? process.env.MAPS_API_KEY ?? '',
},
// Nabeh — إرسال OTP عبر واتساب.
nabeh: {
baseUrl: process.env.NABEH_BASE_URL ?? 'https://nabeh.intaleqapp.com',
email: process.env.NABEH_EMAIL ?? '',
password: process.env.NABEH_PASSWORD ?? '',
otpType: process.env.NABEH_OTP_TYPE ?? 'text',
},
// ملاحظة: خرائط الاتصال الدولية صارت في common/phone/phone.service.ts
// (docs/17 — D1) — مصدر واحد للحقيقة بدل نسختين قد تتباعدان.
// Gemini (رؤية) — قراءة الوثائق ومطابقة الوجه.
gemini: {
apiKey: process.env.GEMINI_API_KEY ?? '',
model: process.env.GEMINI_MODEL ?? 'gemini-2.0-flash',
},
// سرّ المنصة (super-admin) — لتعيين أول أدمن قبل وجود أدمن.
platform: {
secret: process.env.PLATFORM_SECRET ?? '',
},
// تخزين ملفات الوثائق. baseUrl فارغ = محلي؛ يُضبط لسيرفر التوطين لاحقاً (docs/06).
storage: {
dir: process.env.STORAGE_DIR ?? '/app/storage',
baseUrl: process.env.STORAGE_BASE_URL ?? '',
},
// المدفوعات: توقيع HMAC على العمليات المالية (docs/17 — I6).
// مطفأ حتى يوقّع تطبيق فلاتر؛ تفعيله قبل ذلك يقطع كل سحب.
payments: {
requireSignature: process.env.PAYMENTS_REQUIRE_SIGNATURE === 'true',
},
// إشعارات FCM (اتركه فارغاً لتعطيل الإرسال — يُسجَّل فقط).
fcm: {
serverKey: process.env.FCM_SERVER_KEY ?? '',
endpoint: process.env.FCM_ENDPOINT ?? 'https://fcm.googleapis.com/fcm/send',
},
});