fix login and configurations

This commit is contained in:
nok
2026-08-03 11:17:41 +07:00
parent 7b294f70da
commit 618045540a
11 changed files with 364 additions and 31 deletions
+27
View File
@@ -22,6 +22,33 @@ if(true){
$time_zone = "Asia/Bangkok";
}
// ── Real-time Node.js server ─────────────────────────────────────────────────
// Base URL the browser uses to reach nodejs/server.js (Socket.IO). Must match
// PORT in nodejs/.env.
if (!defined('NODE_PUBLIC_URL')) {
define('NODE_PUBLIC_URL', 'http://localhost:3000');
}
// Server-to-server calls (PHP → Node /emit, and Node scheduler → PHP cron
// endpoints) authenticate with this shared secret — must match EMIT_SECRET
// in nodejs/.env exactly.
if (!defined('NODE_EMIT_URL')) {
define('NODE_EMIT_URL', 'http://127.0.0.1:3000/emit');
}
if (!defined('NODE_EMIT_SECRET')) {
define('NODE_EMIT_SECRET', 'YOUR_NODE_EMIT_SECRET'); // must match nodejs/.env EMIT_SECRET
}
// ── Usage packages ───────────────────────────────────────────────────────────
// Keyed by company_list.package (defaults to 'starter'). Read by UsageGuard to
// enforce daily/weekly action limits and which features lock once exceeded.
$packages = [
'starter' => [
'daily_limit' => 30,
'weekly_limit' => 100,
'lock_on_limit' => ['dashboard'],
],
];
// unique key — used for SMTP password encryption, keep consistent across deploys
+38 -13
View File
@@ -24,9 +24,10 @@
* Fail either → return "Wrong OTP! Please try again."
* 5. On success:
* a. Concurrent-session check — if the account already has a session_token
* set and session_last_seen is within the last hour, the login is blocked
* with "already signed in on another device." A stale or NULL token allows
* the login (user closed browser without logging out, or used back.php).
* set and session_last_seen is within SESSION_ACTIVE_GRACE_SECONDS
* (see below), the login is blocked with "already signed in on another
* device." A stale or NULL token allows the login (previous session
* expired naturally, or the user used back.php to log out explicitly).
* b. session_regenerate_id(true) — prevents session fixation attack by
* issuing a new session ID and deleting the old one.
* c. Generate a fresh CSRF token and store in session.
@@ -109,22 +110,46 @@ if (empty($_SESSION['skip_otp'])) {
// ── Step 4b: Concurrent session check ────────────────────────────────────────
// Block the login if this account already has an active session.
// "Active" = session_token is set AND session_last_seen is within the last hour.
// A stale last_seen (user closed browser without logging out) expires after 1 h,
// matching the PHP session GC maxlifetime configured in session.php.
// "Active" = session_token is set AND session_last_seen is within the last
// SESSION_ACTIVE_GRACE_SECONDS. db_auth.php refreshes session_last_seen on every
// authenticated request (throttled to once per 60s), so a session that is truly
// still in use on another device keeps re-touching this timestamp well within
// the grace window below. A session that has actually ended — browser/tab closed,
// cookie lost, PHP session GC'd — stops refreshing it and goes stale quickly.
//
// This window must stay well above the 60s throttle in db_auth.php (otherwise a
// live second session could go stale between its own refreshes and let a login
// through) but short enough that a real re-login isn't blocked for long after the
// previous session actually ended. It intentionally does NOT match PHP's
// session.gc_maxlifetime (3600s) — that timeout is about when PHP reclaims the
// session file on disk, not about how quickly this check should stop treating a
// dead session as "still active".
// An explicit logout clears session_token to NULL, so back.php bypasses this.
//
// The staleness comparison is done entirely in SQL (session_last_seen vs MySQL's
// own NOW()), not in PHP. session_last_seen is written with MySQL's NOW(), and
// the MySQL server here runs on Asia/Bangkok time while PHP's default timezone is
// UTC (config.php's $time_zone is never applied via date_default_timezone_set()).
// Pulling the timestamp into PHP and comparing with strtotime()/time() silently
// misreads that Bangkok wall-clock string as UTC — 7 hours in the future — which
// made idle_seconds permanently negative and this check block every login,
// regardless of window size. Comparing inside MySQL sidesteps the mismatch
// without touching PHP's global timezone (which would ripple into every other
// date()/time() call in the app).
define('SESSION_ACTIVE_GRACE_SECONDS', 120);
$sth_active = $pdo1->prepare(
"SELECT session_token, session_last_seen FROM user WHERE user_id = :uid LIMIT 1"
"SELECT session_token,
(session_last_seen IS NOT NULL
AND session_last_seen > (NOW() - INTERVAL " . SESSION_ACTIVE_GRACE_SECONDS . " SECOND)) AS is_active
FROM user WHERE user_id = :uid LIMIT 1"
);
$sth_active->execute([':uid' => $user_id]);
$active_row = $sth_active->fetch(PDO::FETCH_ASSOC);
if (!empty($active_row['session_token']) && !empty($active_row['session_last_seen'])) {
$idle_seconds = time() - strtotime($active_row['session_last_seen']);
if ($idle_seconds < 3600) {
$answer['message'] = 'This account is currently signed in on another device. Please sign out from that session first.';
exit(json_encode($answer));
}
if (!empty($active_row['session_token']) && !empty($active_row['is_active'])) {
$answer['message'] = 'This account is currently signed in on another device. Please sign out from that session first.';
exit(json_encode($answer));
}
// ── Step 4c: Claim session ────────────────────────────────────────────────────
+14 -1
View File
@@ -342,7 +342,20 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
"company_id" => $default_company,
"smtp" => $smtp_config,
"subject" => "One Time Password (OTP) For reference number " . $reference_number,
"message" => "Your OTP is " . $otp . " for reference number " . $reference_number,
"message" => implode("\n", [
"Dear WMS user,",
"",
"You requested a One-Time Password (OTP) to log in to WMS.",
"",
"Please use the OTP below to complete your request:",
"• OTP code: " . $otp,
"• Reference number: " . $reference_number,
"",
"Please note:",
"• This code will expire in 3 minutes. Please complete your action promptly.",
"• Do not share this code with anyone to keep your account secure.",
"• If you did not request this code, please ignore this email.",
]),
"channel_name" => "WMS LOGIN OTP",
"to" => $user_email,
"key" => $pinkey,
+14 -1
View File
@@ -115,7 +115,20 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
"company_id" => 0,
"smtp" => $SMTP,
"subject" => "One Time Password (OTP) For reference number " . $reference_number,
"message" => "Your OTP is " . $otp . " for reference number " . $reference_number,
"message" => implode("\n", [
"Dear WMS user,",
"",
"You requested a One-Time Password (OTP) to log in to WMS.",
"",
"Please use the OTP below to complete your request:",
"• OTP code: " . $otp,
"• Reference number: " . $reference_number,
"",
"Please note:",
"• This code will expire in 3 minutes. Please complete your action promptly.",
"• Do not share this code with anyone to keep your account secure.",
"• If you did not request this code, please ignore this email.",
]),
"channel_name" => "WMS LOGIN OTP ",
"to" => $user_email,
"key" => $pinkey,