userModel->findByEmail($email); if (!$user || !password_verify($password, $user['password_hash'])) { throw new Exception("البريد الإلكتروني أو كلمة المرور غير صحيحة"); } if (!$user['is_active']) { throw new Exception("هذا الحساب معطل حالياً"); } $accessToken = $this->jwtService->issueAccessToken([ 'user_id' => $user['id'], 'tenant_id' => $user['tenant_id'], 'role' => $user['role'], 'assigned_company_id' => $user['assigned_company_id'] ]); $refreshToken = $this->jwtService->issueRefreshToken($user['id']); // Update refresh token hash in DB $this->userModel->update($user['id'], [ 'refresh_token_hash' => password_hash($refreshToken, PASSWORD_BCRYPT), 'last_login_at' => date('Y-m-d H:i:s'), 'last_login_ip' => $_SERVER['REMOTE_ADDR'] ?? null ]); return [ 'access_token' => $accessToken, 'refresh_token' => $refreshToken, 'user' => [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], 'role' => $user['role'], 'assigned_company_id' => $user['assigned_company_id'] ] ]; } public function refresh(string $refreshToken): array { $parts = explode('.', $refreshToken); if (count($parts) !== 2) { throw new Exception("رمز التجديد غير صالحة"); } [$userId, $random] = $parts; $user = $this->userModel->find($userId); if (!$user || !$user['is_active']) { throw new Exception("المستخدم غير موجود أو معطل"); } if (!$user['refresh_token_hash'] || !password_verify($refreshToken, $user['refresh_token_hash'])) { throw new Exception("جلسة العمل منتهية، يرجى تسجيل الدخول مرة أخرى"); } $accessToken = $this->jwtService->issueAccessToken([ 'user_id' => $user['id'], 'tenant_id' => $user['tenant_id'], 'role' => $user['role'], 'assigned_company_id' => $user['assigned_company_id'] ]); $newRefreshToken = $this->jwtService->issueRefreshToken($user['id']); $this->userModel->update($user['id'], [ 'refresh_token_hash' => password_hash($newRefreshToken, PASSWORD_BCRYPT) ]); return [ 'access_token' => $accessToken, 'refresh_token' => $newRefreshToken, 'user' => [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], 'role' => $user['role'], 'assigned_company_id' => $user['assigned_company_id'] ] ]; } public function register(array $data): array { // 1. Check if tenant already exists if ($this->tenantModel->findByEmail($data['email'])) { throw new Exception("هذا البريد الإلكتروني مسجل مسبقاً"); } $tenantId = Uuid::uuid4()->toString(); $userId = Uuid::uuid4()->toString(); // 2. Create Tenant $this->tenantModel->create([ 'id' => $tenantId, 'name' => $data['tenant_name'], 'email' => $data['email'], 'status' => 'trial', 'trial_ends_at' => date('Y-m-d H:i:s', strtotime('+14 days')) ]); // 3. Create Subscription $this->subscriptionModel->create([ 'tenant_id' => $tenantId, 'plan' => 'basic', 'status' => 'trial' ]); // 4. Create User $this->userModel->create([ 'id' => $userId, 'tenant_id' => $tenantId, 'name' => $data['user_name'], 'email' => $data['email'], 'password_hash' => password_hash($data['password'], PASSWORD_ARGON2ID), 'role' => 'admin', 'is_active' => 1 ]); return $this->login($data['email'], $data['password']); } public function logout(string $jti, int $remaining): void { // Blacklist the JTI for its remaining lifetime try { $redis = \App\Core\Redis::getInstance(); $redis->setex('jwt_blacklist:' . $jti, max($remaining, 1), '1'); } catch (\Throwable $e) { error_log('[AUTH] Could not blacklist JTI: ' . $e->getMessage()); } } }