S,e,curity:6 \Fix HMAC handshake, generate API keys in Google Login, and relax JWT issuer
This commit is contained in:
@@ -31,10 +31,6 @@ class AuthController extends Controller
|
||||
// PASSENGER LOGIN & REGISTRATION
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/passenger/login
|
||||
* Replaces: loginJwtPassenger.php
|
||||
*/
|
||||
public function passengerLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
@@ -49,15 +45,6 @@ class AuthController extends Controller
|
||||
$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);
|
||||
@@ -71,20 +58,13 @@ class AuthController extends Controller
|
||||
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([
|
||||
@@ -99,26 +79,21 @@ class AuthController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
// Generate API keys if missing
|
||||
if (empty($passenger->api_key)) {
|
||||
$this->generateApiKeys($passenger);
|
||||
}
|
||||
|
||||
$jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 86400);
|
||||
$jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 3600); // 1 Hour
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 86400,
|
||||
'expires_in' => 3600,
|
||||
'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([
|
||||
@@ -126,64 +101,45 @@ class AuthController extends Controller
|
||||
'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) {
|
||||
if (Passenger::where('phone', $encryptedPhone)->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),
|
||||
'password' => password_hash($request->input('password', '123456'), 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,
|
||||
'gender' => $this->encryption->encrypt($request->input('gender', 'male')),
|
||||
'birthdate' => $this->encryption->encrypt($request->input('birthdate', '2000-01-01')),
|
||||
'site' => $request->input('site', 'Syria'),
|
||||
]);
|
||||
|
||||
// Create FCM token record if provided
|
||||
if ($fcmToken !== 'none') {
|
||||
if ($request->has('fcm_token')) {
|
||||
PassengerToken::create([
|
||||
'token' => $this->encryption->encrypt($fcmToken),
|
||||
'token' => $this->encryption->encrypt($request->input('fcm_token')),
|
||||
'passengerID' => $passengerId,
|
||||
'fingerPrint' => $fingerprint,
|
||||
'fingerPrint' => $request->input('fingerprint', 'unknown'),
|
||||
]);
|
||||
}
|
||||
|
||||
// Generate API keys
|
||||
$this->generateApiKeys($passenger);
|
||||
|
||||
// Generate 24h JWT for immediate use after registration
|
||||
$jwt = $this->createJwt($passengerId, 'passenger', $fingerprint, 86400);
|
||||
$jwt = $this->createJwt($passengerId, 'passenger', $request->input('fingerprint', 'unknown'), 3600);
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 86400,
|
||||
'expires_in' => 3600,
|
||||
'user_id' => $passengerId,
|
||||
'api_key' => $passenger->api_key,
|
||||
'api_secret' => $passenger->api_secret,
|
||||
@@ -194,9 +150,6 @@ class AuthController extends Controller
|
||||
// DRIVER LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /v2/auth/driver/login
|
||||
*/
|
||||
public function driverLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
@@ -206,56 +159,71 @@ class AuthController extends Controller
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
$phone = $request->input('phone');
|
||||
$encryptedPhone = $this->encryption->encrypt($phone);
|
||||
|
||||
$encryptedPhone = $this->encryption->encrypt($request->input('phone'));
|
||||
$driver = Driver::active()->where('phone', $encryptedPhone)->first();
|
||||
if (!$driver) {
|
||||
|
||||
if (!$driver || (!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');
|
||||
}
|
||||
|
||||
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);
|
||||
$jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerprint'), 14400); // 4 Hours
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 86400,
|
||||
'expires_in' => 14400,
|
||||
'user_id' => $driver->id,
|
||||
'api_key' => $driver->api_key,
|
||||
'api_secret' => $driver->api_secret,
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// HANDSHAKE (V1 Compatibility)
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
public function passengerJwtHandshake(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']);
|
||||
|
||||
$passenger = Passenger::find($request->input('id'));
|
||||
if (!$passenger) return $this->failure('User not found');
|
||||
|
||||
$jwt = $this->createJwt($passenger->id, 'passenger', $request->input('fingerPrint'), 3600);
|
||||
return response()->json(['status' => 'success', 'jwt' => $jwt, 'expires_in' => 3600]);
|
||||
}
|
||||
|
||||
public function driverJwtHandshake(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']);
|
||||
|
||||
$driver = Driver::find($request->input('id'));
|
||||
if (!$driver) return $this->failure('User not found');
|
||||
|
||||
$jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerPrint'), 14400);
|
||||
return response()->json(['status' => 'success', 'jwt' => $jwt, 'expires_in' => 14400]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// 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);
|
||||
$request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']);
|
||||
$jwt = $this->createWalletJwt($request->input('id'), $request->input('fingerPrint'), $request->input('aud', 'TripzWallet'), 60);
|
||||
$hmac = hash_hmac('sha256', $request->input('id'), config('intaleq.wallet_hmac_secret'));
|
||||
|
||||
return $this->success([
|
||||
'jwt' => $jwt,
|
||||
'hmac' => $hmac,
|
||||
'expires_in' => 300,
|
||||
]);
|
||||
return $this->success(['jwt' => $jwt, 'hmac' => $hmac, 'expires_in' => 60]);
|
||||
}
|
||||
|
||||
public function driverWalletLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']);
|
||||
$jwt = $this->createWalletJwt($request->input('id'), $request->input('fingerPrint'), $request->input('aud', 'TripzWallet'), 60, config('intaleq.wallet_jwt_secret'));
|
||||
$hmac = hash_hmac('sha256', $request->input('id'), config('intaleq.wallet_hmac_secret'));
|
||||
|
||||
return $this->success(['jwt' => $jwt, 'hmac' => $hmac, 'expires_in' => 60]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -264,11 +232,7 @@ class AuthController extends Controller
|
||||
|
||||
public function adminLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'device_number' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$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 ?? '')) {
|
||||
@@ -276,52 +240,17 @@ class AuthController extends Controller
|
||||
}
|
||||
|
||||
$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,
|
||||
]);
|
||||
return $this->success(['token' => $jwt, 'expires_in' => 900, 'user_id' => $admin->id]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// GOOGLE LOGIN (Legacy V1 Compatibility)
|
||||
// GOOGLE LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
public function passengerLoginGoogle(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string',
|
||||
'id' => 'required|string',
|
||||
]);
|
||||
|
||||
$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')
|
||||
@@ -344,36 +273,36 @@ class AuthController extends Controller
|
||||
'p.api_key',
|
||||
'p.api_secret',
|
||||
])
|
||||
->selectSub(function ($query) use ($request) {
|
||||
$query->from('packageInfo')
|
||||
->select('version')
|
||||
->where('platform', $request->input('platform', 'ios'))
|
||||
->limit(1);
|
||||
}, 'package')
|
||||
->where('p.email', $searchEmail)
|
||||
->where('p.id', $id)
|
||||
->where('p.id', $request->input('id'))
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']);
|
||||
}
|
||||
if (!$row) return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']);
|
||||
|
||||
// Decrypt all fields for Flutter
|
||||
$data = (array) $row;
|
||||
$data['package'] = $data['package'] ?? '1.1.33'; // Default to avoid Null error in Flutter
|
||||
|
||||
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'])) {
|
||||
if (is_string($value) && !in_array($key, ['id', 'status', 'created_at', 'updated_at', 'verified', 'isInstall', 'isGiftToken', 'api_key', 'api_secret', 'package'])) {
|
||||
$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],
|
||||
]);
|
||||
return response()->json(['status' => 'success', 'count' => 1, 'data' => [$data]]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// JWT HELPERS
|
||||
// HELPERS
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry, ?string $audience = null): string
|
||||
private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry): string
|
||||
{
|
||||
$payload = [
|
||||
'user_id' => $userId,
|
||||
@@ -381,15 +310,14 @@ class AuthController extends Controller
|
||||
'fingerprint' => $fingerprint,
|
||||
'iat' => time(),
|
||||
'exp' => time() + $expiry,
|
||||
'aud' => $audience ?? 'mobile-app',
|
||||
'aud' => '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
|
||||
private function createWalletJwt(string $userId, string $fingerprint, string $audience, int $expiry, ?string $secret = null): string
|
||||
{
|
||||
$payload = [
|
||||
'user_id' => $userId,
|
||||
@@ -400,7 +328,6 @@ class AuthController extends Controller
|
||||
'aud' => $audience,
|
||||
'jti' => bin2hex(random_bytes(16)),
|
||||
];
|
||||
|
||||
return JWT::encode($payload, $secret ?? config('intaleq.jwt_secret'), 'HS256');
|
||||
}
|
||||
|
||||
@@ -408,11 +335,9 @@ class AuthController extends Controller
|
||||
{
|
||||
$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
|
||||
]);
|
||||
DB::connection('primary')->table($model->getTable())->where('id', $model->id)->update([
|
||||
'api_key' => $model->api_key,
|
||||
'api_secret' => $model->api_secret
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user