clqms-be/app/Controllers/AuthV2.php
mahdahar a94df3b5f7 **feat: migrate to v2 frontend with Alpine.js pattern**
- Introduce v2 views directory with Alpine.js-based UI components
- Add AuthV2 controller for v2 authentication flow
- Update PagesController for v2 routing
- Refactor ValueSet module with v2 dialogs and nested CRUD views
- Add organization management views (accounts, departments, disciplines, sites, workstations)
- Add specimen management views (containers, preparations)
- Add master views for tests and valuesets
- Migrate patient views to v2 pattern
- Update Routes and Exceptions config for v2 support
- Enhance CORS configuration
- Clean up legacy files (check_db.php, llms.txt, sanity.php, old views)
- Update agent workflow patterns for PHP Alpine.js
2025-12-30 14:30:35 +07:00

239 lines
6.6 KiB
PHP

<?php
namespace App\Controllers;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
use CodeIgniter\Cookie\Cookie;
/**
* AuthV2 Controller
*
* Handles authentication for V2 UI
* Separate from the main Auth controller to avoid conflicts
*/
class AuthV2 extends Controller
{
use ResponseTrait;
protected $db;
public function __construct()
{
$this->db = \Config\Database::connect();
}
/**
* Check authentication status
* GET /v2/auth/check
*/
public function checkAuth()
{
$token = $this->request->getCookie('token');
$key = getenv('JWT_SECRET');
if (!$token) {
return $this->respond([
'status' => 'failed',
'message' => 'No token found'
], 401);
}
try {
$decodedPayload = JWT::decode($token, new Key($key, 'HS256'));
return $this->respond([
'status' => 'success',
'message' => 'Authenticated',
'data' => $decodedPayload
], 200);
} catch (ExpiredException $e) {
return $this->respond([
'status' => 'failed',
'message' => 'Token expired'
], 401);
} catch (SignatureInvalidException $e) {
return $this->respond([
'status' => 'failed',
'message' => 'Invalid token signature'
], 401);
} catch (BeforeValidException $e) {
return $this->respond([
'status' => 'failed',
'message' => 'Token not valid yet'
], 401);
} catch (\Exception $e) {
return $this->respond([
'status' => 'failed',
'message' => 'Invalid token: ' . $e->getMessage()
], 401);
}
}
/**
* Login user
* POST /v2/auth/login
*/
public function login()
{
$username = $this->request->getVar('username');
$password = $this->request->getVar('password');
$key = getenv('JWT_SECRET');
// Validate username
if (!$username) {
return $this->respond([
'status' => 'failed',
'message' => 'Username is required'
], 400);
}
// Find user
$sql = "SELECT * FROM users WHERE username = " . $this->db->escape($username);
$query = $this->db->query($sql);
$row = $query->getResultArray();
if (!$row) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found'
], 401);
}
$row = $row[0];
// Verify password
if (!password_verify($password, $row['password'])) {
return $this->respond([
'status' => 'failed',
'message' => 'Invalid password'
], 401);
}
// Create JWT payload
$exp = time() + 864000; // 10 days
$payload = [
'userid' => $row['id'],
'roleid' => $row['role_id'],
'username' => $row['username'],
'exp' => $exp
];
try {
$jwt = JWT::encode($payload, $key, 'HS256');
} catch (\Exception $e) {
return $this->respond([
'status' => 'failed',
'message' => 'Error generating JWT: ' . $e->getMessage()
], 500);
}
// Detect if HTTPS is being used
$isSecure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
// Set HTTP-only cookie
$this->response->setCookie([
'name' => 'token',
'value' => $jwt,
'expire' => 864000,
'path' => '/',
'secure' => $isSecure, // false for localhost HTTP
'httponly' => true,
'samesite' => $isSecure ? Cookie::SAMESITE_NONE : Cookie::SAMESITE_LAX
]);
return $this->respond([
'status' => 'success',
'message' => 'Login successful',
'data' => [
'username' => $row['username'],
'role_id' => $row['role_id']
]
], 200);
}
/**
* Logout user
* POST /v2/auth/logout
*/
public function logout()
{
// Detect if HTTPS is being used
$isSecure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
// Clear the token cookie
return $this->response->setCookie([
'name' => 'token',
'value' => '',
'expire' => time() - 3600,
'path' => '/',
'secure' => $isSecure,
'httponly' => true,
'samesite' => $isSecure ? Cookie::SAMESITE_NONE : Cookie::SAMESITE_LAX
])->setJSON([
'status' => 'success',
'message' => 'Logout successful'
]);
}
/**
* Register new user
* POST /v2/auth/register
*/
public function register()
{
$username = strtolower($this->request->getJsonVar('username'));
$password = $this->request->getJsonVar('password');
// Validate input
if (empty($username) || empty($password)) {
return $this->respond([
'status' => 'failed',
'message' => 'Username and password are required'
], 400);
}
// Check for existing username
$exists = $this->db->query("SELECT id FROM users WHERE username = ?", [$username])->getRow();
if ($exists) {
return $this->respond([
'status' => 'failed',
'message' => 'Username already exists'
], 409);
}
// Hash password
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Insert user
$this->db->transStart();
$this->db->query(
"INSERT INTO users(username, password, role_id) VALUES(?, ?, ?)",
[$username, $hashedPassword, 1]
);
$this->db->transComplete();
if ($this->db->transStatus() === false) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to create user'
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User ' . $username . ' successfully created'
], 201);
}
}