'required|cid_13', 'password' => 'required|min:8', ]); if ($validator->fails()) { Response::error('ข้อมูลเข้าสู่ระบบไม่ถูกต้อง (Validation Failed)', 422, $validator->getErrors()); } $userModel = new User(); $authRes = $userModel->verifyCredentials(trim($input['national_id']), $input['password']); if (!$authRes['success']) { AuditLogger::logLoginFailed(trim($input['national_id']), $authRes['error']); Response::error($authRes['error'], 401); } $user = $authRes['user']; // ตรวจสอบว่าบัญชีนี้เปิดใช้งาน 2FA หรือไม่ if ((int)$user['two_factor_enabled'] === 1) { if (session_status() === PHP_SESSION_NONE) session_start(); $_SESSION['pending_2fa_user_id'] = (int)$user['id']; $_SESSION['pending_2fa_user_role'] = $user['role']; $_SESSION['2fa_verified'] = false; Response::success('2FA Verification Required', [ 'require_2fa' => true, 'user_id' => (int)$user['id'], 'message' => 'กรุณากรอกรหัส OTP 6 หลักจากแอป Google Authenticator', ], 200); } // หากไม่เปิด 2FA ให้สร้าง JWT Access Token ทันที $this->issueTokenAndRespond($user); } /** * POST /api/v1/auth/verify-2fa */ public function verifyTwoFactor(): void { RateLimitMiddleware::handle(5, 60); // สูงสุด 5 ครั้ง/นาทีสำหรับ 2FA $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; if (empty($input['code']) || strlen($input['code']) !== 6) { Response::error('กรุณาระบุรหัส TOTP 6 หลักที่ถูกต้อง', 422); } if (session_status() === PHP_SESSION_NONE) session_start(); $userId = $_SESSION['pending_2fa_user_id'] ?? ($input['user_id'] ?? null); if (!$userId) { Response::error('Session หมดอายุ กรุณาเข้าสู่ระบบใหม่อีกครั้ง', 401); } $userModel = new User(); $user = $userModel->find((int)$userId); if (!$user || (int)$user['two_factor_enabled'] !== 1 || empty($user['two_factor_secret'])) { Response::error('ไม่พบการตั้งค่า 2FA หรือผู้ใช้งานไม่ถูกต้อง', 400); } // Verify TOTP Code if (!TwoFactorService::verifyCode($user['two_factor_secret'], trim($input['code']))) { Response::error('รหัส TOTP ไม่ถูกต้องหรือหมดเวลา กรุณาลองใหม่อีกครั้ง', 401); } $_SESSION['2fa_verified'] = true; unset($user['password_hash'], $user['two_factor_secret'], $user['two_factor_recovery_codes']); $this->issueTokenAndRespond($user); } private function issueTokenAndRespond(array $user): void { $config = require __DIR__ . '/../../config/security.php'; $secret = $config['jwt']['secret']; $ttl = $config['jwt']['access_token_ttl']; $payload = [ 'sub' => (int)$user['id'], 'nat_id' => $user['national_id'], 'role' => $user['role'], 'name' => $user['first_name'] . ' ' . $user['last_name'], 'branch' => (int)($user['branch_id'] ?? 1), ]; $token = Security::generateJwt($payload, $secret, $ttl); if (session_status() === PHP_SESSION_NONE) session_start(); $_SESSION['access_token'] = $token; $_SESSION['user_id'] = (int)$user['id']; $_SESSION['user_role'] = $user['role']; $_SESSION['branch_id'] = (int)($user['branch_id'] ?? 1); AuditLogger::logLogin((int)$user['id'], $user['role']); Response::success('เข้าสู่ระบบสำเร็จ (Authentication Successful)', [ 'access_token' => $token, 'token_type' => 'Bearer', 'expires_in' => $ttl, 'user' => [ 'id' => (int)$user['id'], 'national_id' => $user['national_id'], 'full_name' => $user['title'] . $user['first_name'] . ' ' . $user['last_name'], 'role' => $user['role'], 'branch_id' => (int)($user['branch_id'] ?? 1), ] ], 200); } }