- Rename all controllers from X.php to XController.php format - Add new RefTxtModel for text-based reference ranges - Rename group_dialog.php to grp_dialog.php and remove title_dialog.php - Add comprehensive test suite for v2/master/TestDef module - Update Routes.php to reflect controller renames - Remove obsolete data files (clqms_v2.sql, lab.dbml)
239 lines
6.6 KiB
PHP
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 AuthV2Controller 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);
|
|
}
|
|
}
|