Files
intaleq_v2/app/Http/Controllers/AuthController.php

419 lines
16 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Passenger;
use App\Models\PassengerToken;
use App\Models\Driver;
use App\Models\DriverToken;
use App\Models\CarRegistration;
use App\Models\DriverDocument;
use App\Services\LegacyEncryption;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class AuthController extends Controller
{
protected $encryption;
public function __construct(LegacyEncryption $encryption)
{
$this->encryption = $encryption;
}
// ══════════════════════════════════════════════
// PASSENGER LOGIN & REGISTRATION
// ══════════════════════════════════════════════
/**
* POST /v2/auth/passenger/login
* Replaces: loginJwtPassenger.php
*/
public function passengerLogin(Request $request): JsonResponse
{
$request->validate([
'phone' => 'required|string',
'password' => 'required|string',
'fingerprint' => 'required|string',
'fcm_token' => 'required|string',
]);
$phone = $request->input('phone');
$password = $request->input('password');
$fingerprint = $request->input('fingerprint');
$fcmToken = $request->input('fcm_token');
// Rate limiting: 5 attempts per minute per (IP + Phone)
$rateLimitKey = 'login_passenger:' . $request->ip() . ':' . $phone;
if (Cache::get($rateLimitKey, 0) >= config('intaleq.rate_limit_login', 5)) {
return $this->failure('Too many login attempts. Please try again later.', 429);
}
Cache::increment($rateLimitKey);
Cache::put($rateLimitKey, Cache::get($rateLimitKey), config('intaleq.rate_limit_login_decay', 60));
// Flexible phone lookup (encrypted/raw, international/local)
$rawPhone = $phone;
$localPhone = '0' . substr($phone, 3);
$encRawPhone = $this->encryption->encrypt($rawPhone);
$encLocalPhone = $this->encryption->encrypt($localPhone);
$passenger = Passenger::active()
->whereIn('phone', [$rawPhone, $localPhone, $encRawPhone, $encLocalPhone])
->first();
if (!$passenger) {
return $this->failure('Invalid credentials');
}
// HMAC password verification (V1 uses this for passengers)
$storedPassword = $passenger->password;
if (!password_verify($password, $storedPassword) &&
!hash_equals($storedPassword, hash_hmac('sha256', $password, config('intaleq.jwt_secret')))) {
return $this->failure('Invalid credentials');
}
// Verify fingerprint
$passengerToken = PassengerToken::where('passengerID', $passenger->id)->first();
if ($passengerToken && $passengerToken->fingerPrint !== $fingerprint) {
return $this->failure('Device mismatch');
}
// Update FCM token
$encryptedFcm = $this->encryption->encrypt($fcmToken);
if ($passengerToken) {
$passengerToken->update([
'token' => $encryptedFcm,
'fingerPrint' => $fingerprint,
]);
} else {
PassengerToken::create([
'token' => $encryptedFcm,
'passengerID' => $passenger->id,
'fingerPrint' => $fingerprint,
]);
}
// Generate API keys if missing
if (empty($passenger->api_key)) {
$this->generateApiKeys($passenger);
}
$jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 86400);
return $this->success([
'token' => $jwt,
'expires_in' => 86400,
'user_id' => $passenger->id,
'api_key' => $passenger->api_key,
'api_secret' => $passenger->api_secret,
]);
}
/**
* POST /v2/auth/passenger/register
* Replaces: loginFirstTime.php
*/
public function passengerRegister(Request $request): JsonResponse
{
$request->validate([
'phone' => 'required|string',
'email' => 'required|email',
'first_name' => 'required|string',
'last_name' => 'required|string',
'password' => 'nullable|string|min:6',
'gender' => 'nullable|string',
'birthdate' => 'nullable|string',
'site' => 'nullable|string',
'fingerprint' => 'nullable|string',
'fcm_token' => 'nullable|string',
]);
$phone = $request->input('phone');
$password = $request->input('password', Str::random(12));
$gender = $request->input('gender', 'not_specified');
$birthdate = $request->input('birthdate', '2000-01-01');
$site = $request->input('site', 'none');
$fingerprint = $request->input('fingerprint', 'unknown');
$fcmToken = $request->input('fcm_token', 'none');
$encryptedPhone = $this->encryption->encrypt($phone);
// Check if already exists
$exists = Passenger::where('phone', $encryptedPhone)->exists();
if ($exists) {
return $this->failure('Phone number already registered');
}
// Generate a 19-digit numeric ID for better indexing performance
$passengerId = (string) mt_rand(1000000000, 9999999999) . mt_rand(100000000, 999999999);
// Encrypt sensitive fields
$passenger = Passenger::create([
'id' => $passengerId,
'phone' => $encryptedPhone,
'email' => $this->encryption->encrypt($request->input('email')),
'password' => password_hash($password, PASSWORD_BCRYPT),
'first_name' => $this->encryption->encrypt($request->input('first_name')),
'last_name' => $this->encryption->encrypt($request->input('last_name')),
'gender' => $this->encryption->encrypt($gender),
'birthdate' => $this->encryption->encrypt($birthdate),
'site' => $site,
]);
// Create FCM token record if provided
if ($fcmToken !== 'none') {
PassengerToken::create([
'token' => $this->encryption->encrypt($fcmToken),
'passengerID' => $passengerId,
'fingerPrint' => $fingerprint,
]);
}
// Generate API keys
$this->generateApiKeys($passenger);
// Generate 24h JWT for immediate use after registration
$jwt = $this->createJwt($passengerId, 'passenger', $fingerprint, 86400);
return $this->success([
'token' => $jwt,
'expires_in' => 86400,
'user_id' => $passengerId,
'api_key' => $passenger->api_key,
'api_secret' => $passenger->api_secret,
], 201);
}
// ══════════════════════════════════════════════
// DRIVER LOGIN
// ══════════════════════════════════════════════
/**
* POST /v2/auth/driver/login
*/
public function driverLogin(Request $request): JsonResponse
{
$request->validate([
'phone' => 'required|string',
'password' => 'required|string',
'fingerprint' => 'required|string',
'fcm_token' => 'required|string',
]);
$phone = $request->input('phone');
$encryptedPhone = $this->encryption->encrypt($phone);
$driver = Driver::active()->where('phone', $encryptedPhone)->first();
if (!$driver) {
return $this->failure('Invalid credentials');
}
if (!password_verify($request->input('password'), $driver->password) &&
!hash_equals($driver->password, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret')))) {
return $this->failure('Invalid credentials');
}
$jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerprint'), 86400);
return $this->success([
'token' => $jwt,
'expires_in' => 86400,
'user_id' => $driver->id,
'api_key' => $driver->api_key,
'api_secret' => $driver->api_secret,
]);
}
// ══════════════════════════════════════════════
// WALLET LOGIN
// ══════════════════════════════════════════════
public function passengerWalletLogin(Request $request): JsonResponse
{
if (!$request->has('fingerprint') && $request->has('fingerPrint')) {
$request->merge(['fingerprint' => $request->input('fingerPrint')]);
}
$request->validate([
'id' => 'required|string',
'password' => 'required|string',
'fingerprint' => 'required|string',
'aud' => 'required|string',
]);
$secret = config('intaleq.jwt_secret');
$jwt = $this->createWalletJwt($request->input('id'), $request->input('fingerprint'), $request->input('aud'), 300, $secret);
$hmac = hash_hmac('sha256', $request->input('id'), config('intaleq.wallet_hmac_secret'));
return $this->success([
'jwt' => $jwt,
'hmac' => $hmac,
'expires_in' => 300,
]);
}
// ══════════════════════════════════════════════
// ADMIN LOGIN
// ══════════════════════════════════════════════
public function adminLogin(Request $request): JsonResponse
{
$request->validate([
'device_number' => 'required|string',
'password' => 'required|string',
]);
$admin = DB::connection('primary')->table('adminUser')->where('device_number', $request->input('device_number'))->first();
if (!$admin || !password_verify($request->input('password'), $admin->password ?? '')) {
return $this->failure('Invalid credentials');
}
$jwt = $this->createJwt((string)$admin->id, 'admin', $request->input('device_number'), 900);
return $this->success([
'token' => $jwt,
'expires_in' => 900,
'user_id' => $admin->id,
]);
}
public function adminWalletLogin(Request $request): JsonResponse
{
$request->validate([
'id' => 'required|string',
'password' => 'required|string',
'fingerprint' => 'required|string',
'aud' => 'required|string',
]);
$admin = DB::connection('primary')->table('adminUser')->where('id', $request->input('id'))->first();
if (!$admin) return $this->failure('Not found');
$jwt = $this->createWalletJwt((string)$admin->id, $request->input('fingerprint'), $request->input('aud'), 60);
$hmac = hash_hmac('sha256', (string)$admin->id, config('intaleq.wallet_hmac_secret'));
return $this->success([
'jwt' => $jwt,
'hmac' => $hmac,
'expires_in' => 60,
'user_id' => $admin->id,
]);
}
// ══════════════════════════════════════════════
// GOOGLE LOGIN (Legacy V1 Compatibility)
// ══════════════════════════════════════════════
public function passengerLoginGoogle(Request $request): JsonResponse
{
$request->validate([
'email' => 'required|string',
'id' => 'required|string',
]);
$email = $request->input('email');
$id = $request->input('id');
// Check if email is already encrypted (contains non-alphanumeric chars usually)
$searchEmail = (str_contains($email, '+') || str_contains($email, '/')) ? $email : $this->encryption->encrypt($email);
$row = DB::connection('primary')
->table('passengers as p')
->leftJoin('phone_verification_passenger', 'phone_verification_passenger.phone_number', '=', 'p.phone')
->leftJoin('invitesToPassengers', 'invitesToPassengers.inviterPassengerPhone', '=', 'p.phone')
->leftJoin('promos', 'promos.passengerID', '=', 'p.id')
->select([
'p.id', 'p.phone', 'p.email', 'p.gender', 'p.status',
'p.birthdate', 'p.site', 'p.first_name', 'p.last_name',
'p.sosPhone', 'p.education', 'p.employmentType', 'p.maritalStatus',
'p.created_at', 'p.updated_at',
'phone_verification_passenger.verified',
'invitesToPassengers.isInstall',
'invitesToPassengers.inviteCode',
'invitesToPassengers.isGiftToken',
'promos.promo_code as promo',
'promos.amount as discount',
'promos.validity_end_date as validity',
'p.api_key',
'p.api_secret',
])
->where('p.email', $searchEmail)
->where('p.id', $id)
->first();
if (!$row) {
return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']);
}
// Decrypt all fields for Flutter
$data = (array) $row;
foreach ($data as $key => $value) {
if (is_string($value) && !in_array($key, ['id', 'status', 'created_at', 'updated_at', 'verified', 'isInstall', 'isGiftToken', 'api_key', 'api_secret'])) {
$dec = $this->encryption->decrypt($value);
if ($dec) $data[$key] = $dec;
}
}
// Flutter expects data as a List
return response()->json([
'status' => 'success',
'count' => 1,
'data' => [$data],
]);
}
// ══════════════════════════════════════════════
// JWT HELPERS
// ══════════════════════════════════════════════
private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry, ?string $audience = null): string
{
$payload = [
'user_id' => $userId,
'user_type' => $userType,
'fingerprint' => $fingerprint,
'iat' => time(),
'exp' => time() + $expiry,
'aud' => $audience ?? 'mobile-app',
'iss' => 'Tripz',
'jti' => bin2hex(random_bytes(16)),
];
return JWT::encode($payload, config('intaleq.jwt_secret'), 'HS256');
}
private function createWalletJwt(string $userId, string $fingerprint, string $audience, int $expiry = 300, ?string $secret = null): string
{
$payload = [
'user_id' => $userId,
'fingerPrint' => hash('sha256', $fingerprint . config('intaleq.fp_pepper', '')),
'exp' => time() + $expiry,
'iat' => time(),
'iss' => 'Tripz-Wallet',
'aud' => $audience,
'jti' => bin2hex(random_bytes(16)),
];
return JWT::encode($payload, $secret ?? config('intaleq.jwt_secret'), 'HS256');
}
private function generateApiKeys($model)
{
$model->api_key = bin2hex(random_bytes(16));
$model->api_secret = bin2hex(random_bytes(32));
DB::connection('primary')->table($model->getTable())
->where('idn', $model->idn ?? $model->id)
->update([
'api_key' => $model->api_key,
'api_secret' => $model->api_secret
]);
}
}