Proje: Okul Platform · Hub: Okul Platform — Architecture

Customer OTP login: çoklu kullanıcı seçimi akışı

customer/user/login.blade.php (kurumsal panel girişi) OTP akışında, aynı telefon/e-postaya bağlı birden fazla customer_user varsa hangi hesapla giriş yapılacağını kullanıcıya seçtiriyoruz.

Akış

  1. Kod iste: POST /api/auth/otp (school-api proxy) → external API OtpCodeController@generate
  2. Kodu doğrula + adayları çek: POST /api/auth/otp/users (yeni proxy, route name school-api.auth.otp.users) → external API customer/otp/users (OtpUserSelectionController)
    • Cevap: { data: [{customer_user, customer, user}], user_count: N }
    • user_count = unique user_id sayısı
  3. Branch:
    • user_count === 1 → direkt POST /api/auth/login (mevcut akış, opsiyonel user_id parametresiyle)
    • user_count > 1 → FE kart liste gösterir (firma/ad-soyad), seçimden sonra POST /api/auth/login çağrısına user_id eklenir

Bu repodaki dosyalar (okulcom/school)

  • routes/school-api.phpauth/otp/users proxy route
  • app/Http/Controllers/SchoolApi/Auth/AuthController::resolveUsers
  • app/Http/Requests/SchoolApi/Auth/ResolveOtpUsersRequest
  • app/Http/Requests/SchoolApi/Auth/UserLoginRequestuser_id opsiyonel kuralı eklendi (sometimes|integer|min:1|prohibits:email,password)
  • app/Service/Api/AuthService::resolveOtpUsers — external API’nin customer/otp/users endpoint’ini çağırır
  • resources/views/customer/user/login.blade.php.otp-user-step kart listesi, resolveAndLogin JS akışı, data-resolve-url form attribute’u

External API tarafı (zaten mevcut)

  • customer/otp/users (auth gerektirmez): OtpUserSelectionControllerOtpCodeService::resolveUsers
    • validateOtpCodeForUserSelection → kod doğrular ama markAsUsed: false (kod resolve’da yanmaz, login’de yanar)
    • Phone path: yalnızca primary contact eşleşmesi
    • Email path: primary contact + users.email eşleşmesi (union)
  • auth/login opsiyonel user_id parametresini destekliyor (UserLoginRequest, AttemptOtpLoginDTO::userId)

Yetkilendirme uyarısı

OtpCodeService::resolveUsers ile auth/login (UserRepository::getCustomersWithAccess) filtre setleri özdeş değil:

  • Resolve: customer_users.status=true, users.is_active=true, user_type=customer, primary contact match.
  • Login: yukarıdakilere ek olarak customer_user_schools.has_content_access=true veya has_lead_access=true zorunluluğu.

Sonuç: bir kullanıcı resolve listesinde görünüp seçildikten sonra login’de getCustomersWithAccess tarafından düşürülürse LoginFailedException::failed() → “Girilen şifre kayıtlarımızla uyuşmuyor.” hatası alınır. İlk testte bu hatayı aldım; ilgili kullanıcının customer_user_schools yetkisi yokmuş, yetki verilince akış sorunsuz çalıştı (yani BE filtre uyumsuzluğu kullanıcıyı düşürmedi, gerçekten yetki eksikti).

İleride farklı bir vakada aynı hata tekrarlarsa filtre uyumsuzluğunu API tarafında hizalamak (resolve’a has_content_access OR has_lead_access filtresi eklemek) doğrudur — login’de görünmeyecek bir kullanıcıyı seçim listesinde göstermek UX hatası.

Response shape (school-api proxy)

AuthController::resolveUsers external API cevabını JsonResponse::success($apiResponse) ile sarıyor:

{ success, message, data: { success, message, data: [...], user_count: N } }

FE: response.data.data[] ve response.data.user_count.