Files
tripz-llc/backend/src/modules/auth/auth.controller.ts
T
Hamza-AyedandClaude Opus 4.8 c258a8c4b8 fix: D4 — حدّ الطلبات كان مُعطَّلاً كلياً رغم أنه يبدو مفعَّلاً
ThrottlerModule.forRoot(...) كان مسجَّلاً في app.module.ts منذ البداية —
بلا أي حارس يطبّقه (لا APP_GUARD ولا @UseGuards). التسجيل وحده لا يفعل شيئاً
في NestJS: كل نقطة في الـAPI، بما فيها verify-otp و payouts/*، كانت بلا أي
حدّ طلبات إطلاقاً منذ اليوم الأول.

- APP_GUARD → ThrottlerGuard يُفعّل الحدّ العام (120/دقيقة) على كل نقطة
- عدّاد محاولات لكل (مستأجر، رقم) في AuthService.verifyOtp — الحماية
  الحقيقية ضد تخمين الرمز لا الحدّ العام: رمز 4 خانات = 10000 احتمال،
  ومهاجم يدوّر IP يتجاوز أي حدّ بالـIP وحده. 5 محاولات ثم إبطال الرمز،
  نفس نمط payouts.service (I4)
- حدود أضيق للأهداف عالية القيمة: send-otp (كل إرسال يكلّف رسالة واتساب
  مدفوعة فعلياً)، verify-otp، payouts/request، payouts/confirm
- HealthController مُستثنى (@SkipThrottle) — مراقبة تشغيل بلا بيانات حساسة

قرار وعي بالمخاطرة: بنيت أولاً حارساً مخصَّصاً يتتبّع بالمستخدم المصادَق
لا بالـIP وحده (مهم لموبايل — NAT عند مشغّلي الجوّال يجمع آلاف المستخدمين
خلف IP واحد)، لكن تطلّب حقناً يدوياً دقيقاً (InjectThrottlerOptions/
InjectThrottlerStorage) — خطأ فيه يمنع إقلاع التطبيق كاملاً، ولا بيئة هنا
لاختبار NestFactory.create() قبل الدفع (jest ينشئ الخدمات يدوياً فلا يكشف
أخطاء DI لحارس عالمي). حذفته ورجّحت الأمان بالتتبّع الافتراضي — موثّق في
docs/17 D4 لتُنفَّذ حين يمكن اختبارها فعلياً على السيرفر.

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

52 lines
2.0 KiB
TypeScript

import { Controller, Post, Body, Headers, UnauthorizedException } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import { AuthService } from './auth.service';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
/**
* حدّ أضيق من العام (docs/17 — D4): كل إرسال ينادي Nabeh (رسالة واتساب
* مدفوعة فعلياً) — الحدّ العام (120/دقيقة على كل نقاط API) كان سيسمح
* بإغراق مالي رخيص لرقم واحد أو أرقام كثيرة.
*/
@Throttle({ default: { limit: 3, ttl: 300_000 } })
@Post('send-otp')
async sendOtp(
@Headers('x-tenant-id') tenantId: string,
@Body('phone') phone: string,
) {
if (!tenantId) throw new UnauthorizedException('Tenant ID (x-tenant-id) is required');
return this.authService.sendOtp(tenantId, phone);
}
/**
* الحارس الأقوى فعلياً هو عدّاد المحاولات لكل (مستأجر، رقم) داخل
* `AuthService` — يصمد أمام تدوير الـIP. هذا الحدّ طبقة إضافية بسيطة على
* مستوى الشبكة، لا الحماية الأساسية.
*/
@Throttle({ default: { limit: 10, ttl: 300_000 } })
@Post('verify-otp')
async verifyOtp(
@Headers('x-tenant-id') tenantId: string,
@Headers('x-device-id') deviceId: string,
@Body('phone') phone: string,
@Body('code') code: string,
) {
if (!tenantId) throw new UnauthorizedException('Tenant ID (x-tenant-id) is required');
return this.authService.verifyOtp(tenantId, phone, code, deviceId);
}
@Post('refresh')
async refresh(
@Headers('x-device-id') deviceId: string,
@Body('refresh_token') refreshToken: string,
) {
if (!refreshToken) throw new UnauthorizedException('refresh_token is required');
return this.authService.refresh(refreshToken, deviceId);
}
}