feat: D5 — OTP متعدد المزوّدين حسب الدولة (من سيرو) + قرار بلا كلمة مرور
مراجعة سيرو (auth/otp/): مصر تستعمل Kazumi SMS مع failover لواتساب، وسوريا/الأردن Nabeh. عندنا كان مزوّد واحد فقط. - integrations/otp/: واجهة OtpProvider + OtpDispatcher يوجّه حسب tenant.countryPack بسلسلة failover (eg: [kazumi, nabeh] · jo/sy: [nabeh]) - KazumiSmsProvider (مصر SMS) + NabehOtpProvider (غلاف على NabehService) - كل مزوّد يرسل رمزاً نولّده نحن (Redis) — استبعدنا نمط Intaleq (يولّد الرمز بنفسه) حفاظاً على مصدر واحد للرمز - موحَّد: AuthService.sendOtp و PayoutsService.issueOtp يمرّان بنفس المُوزِّع الآن — أُزيل كل استدعاء Nabeh مباشر. أي مسار يحتاج رسالة يستدعي الخدمة الموحّدة (طلب المالك) - فشل السلسلة كاملة = ServiceUnavailable صريح، لا صمت قرار المالك (2026-07-17): بلا كلمة مرور إطلاقاً. المصادقة = هاتف + OTP مرة → جلسة مربوطة بالجهاز (D2). التسجيل بالهاتف لا Google/Apple (محظوران في بعض الدول). سيرو نفسه لا كلمة مرور حقيقية له (password = hash(email) وهمي، ومقارنة نصّية غير آمنة في مسار التجربة). يطابق أوبر/كريم/inDrive. مراجعة حدّ الطلبات في سيرو (RateLimiter.php): حدود مسمّاة لكل نوع + fallback بملف عند تعطّل Redis (fail-closed). عندنا مسار OTP fail-closed أصلاً؛ تخزين Throttler على Redis مؤجَّل للتوسّع الأفقي الفعلي (موثّق في D4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c258a8c4b8
commit
8f6c46f0ac
@@ -8,10 +8,11 @@ import { PaymentsController } from './payments.controller';
|
||||
import { PayoutsController } from './payouts.controller';
|
||||
import { WalletModule } from '../wallet/wallet.module';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { TenantsModule } from '../tenants/tenants.module';
|
||||
|
||||
@Module({
|
||||
// NabehModule و AuditModule عالميان.
|
||||
imports: [TypeOrmModule.forFeature([Payment, Payout]), WalletModule, UsersModule],
|
||||
// OtpModule و AuditModule عالميان.
|
||||
imports: [TypeOrmModule.forFeature([Payment, Payout]), WalletModule, UsersModule, TenantsModule],
|
||||
controllers: [PaymentsController, PayoutsController],
|
||||
providers: [PaymentsService, PayoutsService],
|
||||
exports: [PaymentsService, PayoutsService],
|
||||
|
||||
@@ -16,7 +16,8 @@ import { REDIS } from '../../common/redis/redis.module';
|
||||
import { Payout } from './entities/payout.entity';
|
||||
import { WalletService } from '../wallet/wallet.service';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { NabehService } from '../../integrations/nabeh/nabeh.service';
|
||||
import { OtpDispatcher } from '../../integrations/otp/otp-dispatcher.service';
|
||||
import { TenantsService } from '../tenants/tenants.service';
|
||||
import { AuditService } from '../../common/audit/audit.service';
|
||||
|
||||
export interface PayoutRequestDto {
|
||||
@@ -52,7 +53,8 @@ export class PayoutsService {
|
||||
@Inject(REDIS) private readonly redis: Redis,
|
||||
private readonly wallet: WalletService,
|
||||
private readonly users: UsersService,
|
||||
private readonly nabeh: NabehService,
|
||||
private readonly tenants: TenantsService,
|
||||
private readonly otp: OtpDispatcher,
|
||||
private readonly audit: AuditService,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
@@ -253,8 +255,14 @@ export class PayoutsService {
|
||||
}
|
||||
const user = await this.users.findById(tenantId, driverUserId);
|
||||
if (!user?.phone) throw new BadRequestException('no phone on file');
|
||||
// فشل الإرسال يُفشل الطلب: طلبٌ بلا رمز يصل = سائق عالق بلا سبيل للتأكيد.
|
||||
await this.nabeh.sendOtp(user.phone, code);
|
||||
// نفس مُوزِّع OTP الموحّد المستعمل في تسجيل الدخول (docs/17 — D5): يوجّه
|
||||
// حسب دولة المستأجر ويطبّق failover. فشل السلسلة = سائق عالق بلا رمز.
|
||||
const tenant = await this.tenants.resolve(tenantId);
|
||||
const sent = await this.otp.send(user.phone, code, {
|
||||
countryPack: tenant?.countryPack ?? 'jo',
|
||||
userType: 'driver',
|
||||
});
|
||||
if (!sent) throw new BadRequestException('failed to send verification code');
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user