Files
gravity/บริหารจัดการคิวนวดแผนไทย/app/Controllers/AuthController.php
T
2026-09-16 23:20:08 +07:00

140 lines
5.4 KiB
PHP

<?php
namespace App\Controllers;
use App\Helpers\Response;
use App\Helpers\Security;
use App\Helpers\Validator;
use App\Middleware\RateLimitMiddleware;
use App\Models\User;
use App\Services\AuditLogger;
use App\Services\TwoFactorService;
/**
* Class AuthController
* Authentication REST API Controller (13-digit National ID, Argon2id, 2FA TOTP, JWT)
*
* @package App\Controllers
*/
class AuthController
{
/**
* POST /api/v1/auth/login
*/
public function login(): void
{
RateLimitMiddleware::handle(10, 60); // ป้องกัน Brute-force สูงสุด 10 ครั้ง/นาทีต่อ IP
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
$validator = Validator::make($input, [
'national_id' => '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);
}
}