Initial V2 commit
This commit is contained in:
465
app/Http/Controllers/AuthController.php
Normal file
465
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Driver;
|
||||
use App\Models\Passenger;
|
||||
use App\Models\DriverToken;
|
||||
use App\Models\PassengerToken;
|
||||
use App\Helpers\LegacyEncryption;
|
||||
use Firebase\JWT\JWT;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Authentication Controller
|
||||
*
|
||||
* Unifies all V1 login flows:
|
||||
* login.php → passengerLogin
|
||||
* loginFirstTime.php → passengerRegister
|
||||
* loginJwtDriver.php → driverLogin
|
||||
* loginFirstTimeDriver.php → driverRegister
|
||||
* loginWallet.php → passengerWalletLogin
|
||||
* loginJwtWalletDriver.php → driverWalletLogin
|
||||
* loginAdmin.php → adminLogin
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
private LegacyEncryption $encryption;
|
||||
|
||||
public function __construct(LegacyEncryption $encryption)
|
||||
{
|
||||
$this->encryption = $encryption;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// PASSENGER LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/passenger/login
|
||||
* Replaces: login.php
|
||||
*/
|
||||
public function passengerLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
// Rate limiting: 5 attempts per minute per IP
|
||||
$rateLimitKey = 'login_passenger:' . $request->ip();
|
||||
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));
|
||||
|
||||
$phone = $request->input('phone');
|
||||
$password = $request->input('password');
|
||||
$fingerprint = $request->input('fingerprint');
|
||||
$fcmToken = $request->input('fcm_token');
|
||||
|
||||
// Find passenger by encrypted phone
|
||||
$encryptedPhone = $this->encryption->encrypt($phone);
|
||||
$passenger = Passenger::active()
|
||||
->where('phone', $encryptedPhone)
|
||||
->first();
|
||||
|
||||
if (!$passenger) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// Verify password (bcrypt)
|
||||
if (!password_verify($password, $passenger->password)) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// Verify device fingerprint
|
||||
$token = PassengerToken::where('passengerID', $passenger->id)->first();
|
||||
if ($token && $token->fingerPrint !== $fingerprint) {
|
||||
return $this->failure('Device mismatch. Please login from your registered device.');
|
||||
}
|
||||
|
||||
// Update FCM token
|
||||
if ($token) {
|
||||
$encryptedFcm = $this->encryption->encrypt($fcmToken);
|
||||
$token->update(['token' => $encryptedFcm]);
|
||||
}
|
||||
|
||||
// Generate API keys if not exist
|
||||
if (empty($passenger->api_key)) {
|
||||
$this->generateApiKeys($passenger);
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
$jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 86400); // 24h
|
||||
|
||||
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',
|
||||
'password' => 'required|string|min:6',
|
||||
'first_name' => 'required|string',
|
||||
'last_name' => 'required|string',
|
||||
'gender' => 'required|string',
|
||||
'birthdate' => 'required|string',
|
||||
'site' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
$phone = $request->input('phone');
|
||||
$encryptedPhone = $this->encryption->encrypt($phone);
|
||||
|
||||
// Check if already exists
|
||||
$exists = Passenger::where('phone', $encryptedPhone)->exists();
|
||||
if ($exists) {
|
||||
return $this->failure('Phone number already registered');
|
||||
}
|
||||
|
||||
$passengerId = Str::uuid()->toString();
|
||||
|
||||
// Encrypt sensitive fields
|
||||
$passenger = Passenger::create([
|
||||
'id' => $passengerId,
|
||||
'phone' => $encryptedPhone,
|
||||
'email' => $this->encryption->encrypt($request->input('email')),
|
||||
'password' => password_hash($request->input('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($request->input('gender')),
|
||||
'birthdate' => $this->encryption->encrypt($request->input('birthdate')),
|
||||
'site' => $request->input('site'),
|
||||
]);
|
||||
|
||||
// Create FCM token record
|
||||
PassengerToken::create([
|
||||
'token' => $this->encryption->encrypt($request->input('fcm_token')),
|
||||
'passengerID' => $passengerId,
|
||||
'fingerPrint' => $request->input('fingerprint'),
|
||||
]);
|
||||
|
||||
// Generate API keys
|
||||
$this->generateApiKeys($passenger);
|
||||
|
||||
// Generate temporary JWT (5 min — for registration flow)
|
||||
$jwt = $this->createJwt($passengerId, 'passenger_temp', $request->input('fingerprint'), 300);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 300,
|
||||
'user_id' => $passengerId,
|
||||
'api_key' => $passenger->api_key,
|
||||
'api_secret' => $passenger->api_secret,
|
||||
], 201);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// DRIVER LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/driver/login
|
||||
* Replaces: loginJwtDriver.php
|
||||
*/
|
||||
public function driverLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
// Rate limiting
|
||||
$rateLimitKey = 'login_driver:' . $request->ip();
|
||||
if (Cache::get($rateLimitKey, 0) >= config('intaleq.rate_limit_login', 5)) {
|
||||
return $this->failure('Too many login attempts', 429);
|
||||
}
|
||||
Cache::increment($rateLimitKey);
|
||||
Cache::put($rateLimitKey, Cache::get($rateLimitKey), config('intaleq.rate_limit_login_decay', 60));
|
||||
|
||||
$phone = $request->input('phone');
|
||||
$encryptedPhone = $this->encryption->encrypt($phone);
|
||||
|
||||
$driver = Driver::active()
|
||||
->where('phone', $encryptedPhone)
|
||||
->first();
|
||||
|
||||
if (!$driver) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// HMAC password verification (V1 uses this for drivers)
|
||||
$storedPassword = $driver->password;
|
||||
if (!password_verify($request->input('password'), $storedPassword) &&
|
||||
!hash_equals($storedPassword, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret')))) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// Verify fingerprint
|
||||
$driverToken = DriverToken::where('captain_id', $driver->id)->first();
|
||||
if ($driverToken && $driverToken->fingerPrint !== $request->input('fingerprint')) {
|
||||
return $this->failure('Device mismatch');
|
||||
}
|
||||
|
||||
// Update FCM token
|
||||
$encryptedFcm = $this->encryption->encrypt($request->input('fcm_token'));
|
||||
if ($driverToken) {
|
||||
$driverToken->update([
|
||||
'token' => $encryptedFcm,
|
||||
'fingerPrint' => $request->input('fingerprint'),
|
||||
]);
|
||||
} else {
|
||||
DriverToken::create([
|
||||
'token' => $encryptedFcm,
|
||||
'captain_id' => $driver->id,
|
||||
'fingerPrint' => $request->input('fingerprint'),
|
||||
]);
|
||||
}
|
||||
|
||||
// Generate API keys if not exist
|
||||
if (empty($driver->api_key)) {
|
||||
$this->generateApiKeys($driver);
|
||||
}
|
||||
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v2/auth/driver/register
|
||||
* Replaces: loginFirstTimeDriver.php
|
||||
*/
|
||||
public function driverRegister(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string|min:6',
|
||||
'first_name' => 'required|string',
|
||||
'last_name' => 'required|string',
|
||||
'gender' => 'required|string',
|
||||
'birthdate' => 'required|string',
|
||||
'site' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
$encryptedPhone = $this->encryption->encrypt($request->input('phone'));
|
||||
|
||||
if (Driver::where('phone', $encryptedPhone)->exists()) {
|
||||
return $this->failure('Phone number already registered');
|
||||
}
|
||||
|
||||
$driverId = Str::uuid()->toString();
|
||||
|
||||
$driver = Driver::create([
|
||||
'id' => $driverId,
|
||||
'phone' => $encryptedPhone,
|
||||
'email' => $this->encryption->encrypt($request->input('email')),
|
||||
'password' => password_hash($request->input('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($request->input('gender')),
|
||||
'birthdate' => $this->encryption->encrypt($request->input('birthdate')),
|
||||
'site' => $request->input('site'),
|
||||
]);
|
||||
|
||||
DriverToken::create([
|
||||
'token' => $this->encryption->encrypt($request->input('fcm_token')),
|
||||
'captain_id' => $driverId,
|
||||
'fingerPrint' => $request->input('fingerprint'),
|
||||
]);
|
||||
|
||||
$this->generateApiKeys($driver);
|
||||
|
||||
$jwt = $this->createJwt($driverId, 'driver_temp', $request->input('fingerprint'), 300);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 300,
|
||||
'user_id' => $driverId,
|
||||
'api_key' => $driver->api_key,
|
||||
'api_secret' => $driver->api_secret,
|
||||
], 201);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// WALLET LOGIN (Higher security)
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/passenger/wallet-login
|
||||
* Replaces: loginWallet.php
|
||||
*/
|
||||
public function passengerWalletLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
]);
|
||||
|
||||
// Stricter rate limit for wallet
|
||||
$rateLimitKey = 'wallet_login:' . $request->ip();
|
||||
if (Cache::get($rateLimitKey, 0) >= 3) {
|
||||
return $this->failure('Too many attempts', 429);
|
||||
}
|
||||
Cache::increment($rateLimitKey);
|
||||
Cache::put($rateLimitKey, Cache::get($rateLimitKey), 120);
|
||||
|
||||
$encryptedPhone = $this->encryption->encrypt($request->input('phone'));
|
||||
$passenger = Passenger::active()->where('phone', $encryptedPhone)->first();
|
||||
|
||||
if (!$passenger || !password_verify($request->input('password'), $passenger->password)) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// Short-lived token for wallet operations (5 min)
|
||||
$jwt = $this->createJwt($passenger->id, 'passenger_wallet', $request->input('fingerprint'), 300);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 300,
|
||||
'user_id' => $passenger->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v2/auth/driver/wallet-login
|
||||
* Replaces: loginJwtWalletDriver.php
|
||||
*/
|
||||
public function driverWalletLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
]);
|
||||
|
||||
$rateLimitKey = 'wallet_login_driver:' . $request->ip();
|
||||
if (Cache::get($rateLimitKey, 0) >= 3) {
|
||||
return $this->failure('Too many attempts', 429);
|
||||
}
|
||||
Cache::increment($rateLimitKey);
|
||||
Cache::put($rateLimitKey, Cache::get($rateLimitKey), 120);
|
||||
|
||||
$encryptedPhone = $this->encryption->encrypt($request->input('phone'));
|
||||
$driver = Driver::active()->where('phone', $encryptedPhone)->first();
|
||||
|
||||
if (!$driver || !password_verify($request->input('password'), $driver->password)) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
$jwt = $this->createJwt($driver->id, 'driver_wallet', $request->input('fingerprint'), 60);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 60,
|
||||
'user_id' => $driver->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// ADMIN LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/admin/login
|
||||
* Replaces: loginAdmin.php (NOW WITH ACTUAL PASSWORD CHECK!)
|
||||
*/
|
||||
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) {
|
||||
return $this->failure('Invalid credentials');
|
||||
}
|
||||
|
||||
// TODO: Add password field to adminUser table and verify with password_verify
|
||||
// For now, this is a placeholder — must be implemented before production
|
||||
|
||||
$jwt = $this->createJwt($admin->id, 'admin', $request->input('device_number'), 900);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 900,
|
||||
'user_id' => $admin->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// HELPERS
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry): string
|
||||
{
|
||||
$payload = [
|
||||
'user_id' => $userId,
|
||||
'user_type' => $userType,
|
||||
'fingerprint' => $fingerprint,
|
||||
'iat' => time(),
|
||||
'exp' => time() + $expiry,
|
||||
'jti' => Str::uuid()->toString(),
|
||||
];
|
||||
|
||||
return JWT::encode($payload, config('intaleq.jwt_secret'), 'HS256');
|
||||
}
|
||||
|
||||
private function generateApiKeys($user): void
|
||||
{
|
||||
$apiKey = 'intq_' . Str::random(32);
|
||||
$apiSecret = hash('sha256', Str::random(64) . time());
|
||||
|
||||
$user->update([
|
||||
'api_key' => $apiKey,
|
||||
'api_secret' => $apiSecret,
|
||||
]);
|
||||
}
|
||||
|
||||
private function success(array $data, int $code = 200): JsonResponse
|
||||
{
|
||||
return response()->json(['status' => 'success', 'data' => $data], $code);
|
||||
}
|
||||
|
||||
private function failure(string $message, int $code = 401): JsonResponse
|
||||
{
|
||||
return response()->json(['status' => 'failure', 'message' => $message], $code);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user