Start every session through session.php; idle timeout, app access and auth status codes

This commit is contained in:
Thanakorn
2026-09-24 14:30:35 +07:00
parent 5cc43cff92
commit e579dd596c
273 changed files with 409 additions and 274 deletions
+30 -4
View File
@@ -38,6 +38,7 @@ set_exception_handler(function (Throwable $e) {
require_once __DIR__."/../../config.php";
require_once __DIR__."/../../dbconn.php";
require_once __DIR__."/db_helpers.php";
require_once __DIR__."/app_access.php";
if (!function_exists('require_role')) {
function require_role(string $user_role, array $allowed): void {
@@ -53,14 +54,14 @@ if(!empty($_SESSION["login_company_id"])){
// CSRF Validation — add right at the top of the logged-in block
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$csrf_token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if(empty($csrf_token) || $csrf_token !== $_SESSION['csrf_token']){
if(empty($csrf_token) || !hash_equals((string)($_SESSION['csrf_token'] ?? ''), $csrf_token)){
http_response_code(403);
exit(json_encode(["message" => "Invalid request"]));
}
}
// validate otp
$sql = "SELECT `password`
$sql = "SELECT `password`, license, app_access
FROM user
WHERE user_id = :company_id";
$sth = $pdo1->prepare($sql);
@@ -68,7 +69,8 @@ if(!empty($_SESSION["login_company_id"])){
":company_id" => $_SESSION["login_user_id"]
]);
db_check($sth, $answer);
$password = $sth->fetchColumn();
$user_row = $sth->fetch(PDO::FETCH_ASSOC) ?: [];
$password = $user_row['password'] ?? '';
/** Generate OTP */
function generateOTP($sercet_key, $time_step = 180, $length = 6){
$counter = floor($_SESSION["otpTime"] / $time_step);
@@ -83,7 +85,9 @@ if(!empty($_SESSION["login_company_id"])){
$otp = generateOTP($password);
if( $_SESSION["otp"]!=$otp ){
http_response_code(401);
$answer["message"] = "Your password has been reset, Please logout and login again.";
$answer["code"] = "password_changed";
exit(json_encode($answer));
}
@@ -98,13 +102,29 @@ if(!empty($_SESSION["login_company_id"])){
$map = $sth->fetchAll(PDO::FETCH_ASSOC);
if( count($map)==0 ){
http_response_code(403);
$answer["message"] = "Your accessibility to this company has been removed.";
$answer["code"] = "access_removed";
exit(json_encode($answer));
}
$user_role = $map[0]['role'] ?? 'viewer';
$_SESSION['login_role'] = $user_role;
// App access (WMS / Accounting), re-read on every request so a change made in
// Setting → Users applies at once. Owners hold it on their own user row;
// invited users per company (same rule as login_confirm.php).
$app_access = (($user_row['license'] ?? 'owner') === 'owner')
? ($user_row['app_access'] ?? 'wms')
: ($map[0]['app_access'] ?? 'wms');
$_SESSION['login_app_access'] = $app_access;
$required_app = app_access_app_for($_SERVER['SCRIPT_NAME'] ?? '');
if ($required_app !== null && !app_access_allows($app_access, $required_app)) {
http_response_code(403);
exit(json_encode(['success' => 0, 'message' => 'Your account does not have access to this module.']));
}
// Single-session enforcement: if a session_token was issued at login, verify
// it still matches the DB. A mismatch means a newer login has taken over.
if (!empty($_SESSION['session_token'])) {
@@ -132,7 +152,12 @@ if(!empty($_SESSION["login_company_id"])){
// unless the engine explicitly declared itself a pre-auth route.
if (empty($_SESSION['login_company_id']) && !defined('UNAUTHENTICATED_ROUTE')) {
http_response_code(401);
exit(json_encode(['success' => 0, 'message' => 'Authentication required.']));
$expired = !empty($_SESSION['_idle_expired']);
exit(json_encode([
'success' => 0,
'message' => $expired ? 'Your session has expired. Please sign in again.' : 'Authentication required.',
'code' => $expired ? 'session_expired' : 'auth_required',
]));
}
// set up ANSWER
@@ -153,6 +178,7 @@ if (!is_array($data)) {
$data = $_POST;
} else {
// Truly no data received
http_response_code(400);
$answer["message"] = "Request denied: No valid JSON payload or Form Data detected.";
exit(json_encode($answer));
}