closing security gap [ignore guarding change for now]
This commit is contained in:
@@ -1,87 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to TRx WMS are documented here.
|
|
||||||
Format: `## [version] — YYYY-MM-DD` with sections Added / Changed / Fixed / Removed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Planned
|
|
||||||
- Role-based access control enforcement across all API endpoints and UI pages (see `ROLES.md`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.6.0] — 2026-05-06
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Barcode system — `md_barcode` table, SKU label page (`ics/sku_barcode_label.php`), Location label page (`ics/location_barcode_label.php`)
|
|
||||||
- `scanner.js` — unified scanner module supporting USB scanner, handheld, phone camera (Html5Qrcode), and clipboard paste
|
|
||||||
- Scan-driven stock flows — scan SKU label → scan location label → F2 to save (no mouse required)
|
|
||||||
- `ics/api/engine/barcode_lookup.php` — validates barcode string, returns typed result (`sku` / `loc` / `raw`)
|
|
||||||
- `ics/api/engine/validate_scan_location.php` — pre-save location validation for stock in/out/transfer
|
|
||||||
- `ics/api/engine/sku_label_lots.php`, `sku_label_products.php` — lot and product data feeds for SKU label page
|
|
||||||
- Cost, price, and margin fields on `md_product`
|
|
||||||
- Contact module linked to setting sidebar
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- `location_barcode_label.php` — `ON DUPLICATE KEY UPDATE` now includes `status = 1` so previewing a previously-disabled location barcode re-enables it
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.5.0] — 2026-04
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Purchase Orders (`po/`) — create, confirm, receive, cancel, payment tracking
|
|
||||||
- Order module (`order/`) — sales orders, returns, invoices (invoice / credit note / debit note), order confirmation, payment status
|
|
||||||
- `td_order`, `td_invoice`, `td_return`, `td_purchase_order` tables
|
|
||||||
- Invoice print view (`order/print_invoice.php`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.4.0] — 2026-03
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Warehouse location modes — simple (warehouse + rack) and advanced (warehouse + zone + aisle + rack)
|
|
||||||
- `company_setting` keys: `advanced_location`, `location_label_rack/zone/aisle`, `default_stock_status`, `auto_complete_on_ship`, `auto_invoice_and_credit_note`
|
|
||||||
- Per-warehouse stock tables (`td_stock_{warehouse_id}`) — balance = `SUM(in) - SUM(out)` where `status = 1`
|
|
||||||
- Stock approval flow — transactions created as `status = 0` (draft) and approved separately
|
|
||||||
- Rack occupancy report (`reports/occupy_rack.php`)
|
|
||||||
- Expired / near-expiry report (`reports/expired_stock.php`)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Warehouse layers made switchable via `advanced_location` setting
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.3.0] — 2026-02
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Contact module (`contact/`) — supplier/customer contacts, contact types
|
|
||||||
- Stock movement report with date and SKU filters
|
|
||||||
- Product lot report with expiry date tracking
|
|
||||||
- Low-stock dashboard widget with restock shortcut
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.2.0] — 2026-01
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Multi-tenant branch support — `company_list`, `company_map_user`, branch switcher in topbar
|
|
||||||
- User management (`setting/users.php`) — invite by email, role assignment, remove member
|
|
||||||
- SMTP configuration (`setting/smtp.php`)
|
|
||||||
- OTP validation on every API request (HMAC-SHA1 based on user password + session time)
|
|
||||||
- CSRF token enforcement on all POST requests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.1.0] — 2025-12
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Initial project scaffold — PHP + MySQL + Bootstrap 5 + jQuery
|
|
||||||
- Login / registration / onboarding flow
|
|
||||||
- Product master (`inventory/product.php`) — SKU, name, UOM, category, barcode
|
|
||||||
- Warehouse master (`inventory/warehouse.php`) — warehouses and rack definitions
|
|
||||||
- Stock In / Out / Transfer pages with manual form entry
|
|
||||||
- `td_stock` base table structure
|
|
||||||
- Dashboard with basic stock summary
|
|
||||||
@@ -64,7 +64,7 @@ A multi-tenant Warehouse Management System (WMS) built with PHP, MySQL, and vani
|
|||||||
### Security
|
### Security
|
||||||
- Session-based authentication with TOTP-style OTP validation on every API request
|
- Session-based authentication with TOTP-style OTP validation on every API request
|
||||||
- CSRF token enforcement on all POST requests
|
- CSRF token enforcement on all POST requests
|
||||||
- Role-based access control: `owner`, `admin`, `staff`, `viewer` (see `ROLES.md`)
|
- Role-based access control: `owner`, `admin`, `staff`, `viewer`; enforced in many write APIs with `require_role()` and mirrored in page/sidebar UI (see `docs/ROLES.md`)
|
||||||
- Passwords hashed; profile picture uploads sandboxed to `uploads/profile/`
|
- Passwords hashed; profile picture uploads sandboxed to `uploads/profile/`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -124,7 +124,7 @@ App is served by Apache at `http://localhost/wms/app/`.
|
|||||||
|
|
||||||
| File | Contents |
|
| File | Contents |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| `CHANGELOG.md` | Version history and notable changes |
|
| `docs/CHANGELOG.md` | Version history and notable changes |
|
||||||
| `docs/ROLES.md` | Role-based access control spec (admin / staff / viewer) |
|
| `docs/ROLES.md` | Role-based access control spec (admin / staff / viewer) |
|
||||||
| `docs/DATABASE.md` | Full schema for both databases — tables, columns, relationships |
|
| `docs/DATABASE.md` | Full schema for both databases — tables, columns, relationships |
|
||||||
| `docs/API.md` | All API endpoints — request fields, response format, error codes |
|
| `docs/API.md` | All API endpoints — request fields, response format, error codes |
|
||||||
@@ -134,3 +134,5 @@ App is served by Apache at `http://localhost/wms/app/`.
|
|||||||
| `docs/STOCK.md` | Stock ledger, approval flow, rack lifecycle, StockManager/WarehouseManager |
|
| `docs/STOCK.md` | Stock ledger, approval flow, rack lifecycle, StockManager/WarehouseManager |
|
||||||
| `docs/SCANNER.md` | scanner.js internals, device support, integrating scanning into new pages |
|
| `docs/SCANNER.md` | scanner.js internals, device support, integrating scanning into new pages |
|
||||||
| `docs/CONTRIBUTING.md` | Patterns for adding new APIs, pages, modules, settings, and schema changes |
|
| `docs/CONTRIBUTING.md` | Patterns for adding new APIs, pages, modules, settings, and schema changes |
|
||||||
|
|
||||||
|
Security hardening note: protected API engines must include `assets/utils/db_auth.php`, must reject unauthenticated sessions server-side, and must define role requirements with `require_role()` where the action is not viewer-safe.
|
||||||
|
|||||||
@@ -77,6 +77,13 @@ if(!empty($_SESSION["login_company_id"])){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fail closed — reject any request that arrives without an authenticated session
|
||||||
|
// 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.']));
|
||||||
|
}
|
||||||
|
|
||||||
// set up ANSWER
|
// set up ANSWER
|
||||||
$answer = array("success"=>0, "message"=>"");
|
$answer = array("success"=>0, "message"=>"");
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<footer class="text-center py-2 mt-6 text-secondary ">
|
<footer class="text-center py-2 mt-6 text-secondary ">
|
||||||
<p class="mb-0">Copyright © 2026 TRx WMS. Developed by <a href="https://codescandy.com/"
|
<p class="mb-0">Copyright © 2026 TRx WMS. Developed by <a href="https://codescandy.com/"
|
||||||
target="_blank" class="text-primary">CodesCandy</a> </p>
|
target="_blank" class="text-primary">TR3</a> </p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
// Security headers — emitted before any HTML output.
|
||||||
|
// X-Content-Type-Options: prevents MIME-sniffing attacks.
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
// X-Frame-Options: blocks this page from being embedded in a cross-origin iframe.
|
||||||
|
header('X-Frame-Options: SAMEORIGIN');
|
||||||
|
// Referrer-Policy: sends origin only on same-origin; omits on cross-origin navigations.
|
||||||
|
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||||
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
require '../../../session.php';
|
require '../../../session.php';
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
// Intentional 1-second delay — prevents timing attacks on session enumeration
|
// Intentional 1-second delay — prevents timing attacks on session enumeration
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
require '../../../session.php';
|
require '../../../session.php';
|
||||||
require '../../../config.php';
|
require '../../../config.php';
|
||||||
require '../../../preset.php';
|
require '../../../preset.php';
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
// ── Step 1: Load session state written by login_otp.php ───────────────────────
|
// ── Step 1: Load session state written by login_otp.php ───────────────────────
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
require '../../../session.php';
|
require '../../../session.php';
|
||||||
require '../../../config.php';
|
require '../../../config.php';
|
||||||
require '../../../preset.php';
|
require '../../../preset.php';
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
// ── Step 1: Resolve user_id from username or email (case-insensitive) ────────
|
// ── Step 1: Resolve user_id from username or email (case-insensitive) ────────
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
require '../../../session.php';
|
require '../../../session.php';
|
||||||
require '../../../config.php';
|
require '../../../config.php';
|
||||||
require '../../../preset.php';
|
require '../../../preset.php';
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
// ── Step 1: Reload credentials from session ───────────────────────────────────
|
// ── Step 1: Reload credentials from session ───────────────────────────────────
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
// app/session.php
|
// app/session.php
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
// Reject client-supplied session IDs — prevents session fixation.
|
||||||
|
ini_set('session.use_strict_mode', 1);
|
||||||
|
// Server-side session file lifetime: 1 hour.
|
||||||
|
ini_set('session.gc_maxlifetime', 3600);
|
||||||
session_set_cookie_params([
|
session_set_cookie_params([
|
||||||
'lifetime' => 0,
|
'lifetime' => 0,
|
||||||
'path' => '/wms/',
|
'path' => '/wms/',
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
require_role($user_role, ['owner', 'admin']);
|
require_role($user_role, ['owner', 'admin']);
|
||||||
|
|
||||||
// ─── Allowed upload MIME types ────────────────────────────────────────────
|
// ─── Allowed upload types ─────────────────────────────────────────────────
|
||||||
const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
const ALLOWED_EXT = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
const MAX_SIZE = 2 * 1024 * 1024; // 2 MB
|
const MAX_SIZE = 2 * 1024 * 1024; // 2 MB
|
||||||
|
|
||||||
// ─── Helper: handle one image slot ────────────────────────────────────────
|
// ─── Helper: handle one image slot ────────────────────────────────────────
|
||||||
@@ -48,13 +49,17 @@
|
|||||||
throw new RuntimeException('Invalid file type. Only JPEG, PNG, GIF, WEBP allowed.');
|
throw new RuntimeException('Invalid file type. Only JPEG, PNG, GIF, WEBP allowed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($ext, ALLOWED_EXT, true)) {
|
||||||
|
throw new RuntimeException('Invalid file extension. Only jpg, png, gif, webp allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
// Delete old file first
|
// Delete old file first
|
||||||
if ($current && file_exists($upload_dir . $current)) {
|
if ($current && file_exists($upload_dir . $current)) {
|
||||||
unlink($upload_dir . $current);
|
unlink($upload_dir . $current);
|
||||||
}
|
}
|
||||||
|
|
||||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
$filename = $prefix . uniqid() . '.' . $ext;
|
||||||
$filename = $prefix . uniqid() . '.' . strtolower($ext);
|
|
||||||
|
|
||||||
if (!move_uploaded_file($file['tmp_name'], $upload_dir . $filename)) {
|
if (!move_uploaded_file($file['tmp_name'], $upload_dir . $filename)) {
|
||||||
throw new RuntimeException("Failed to save {$slot}.");
|
throw new RuntimeException("Failed to save {$slot}.");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
require '../../../assets/utils/classes/PasswordResetManager.php';
|
require '../../../assets/utils/classes/PasswordResetManager.php';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
define('UNAUTHENTICATED_ROUTE', true);
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
require '../../../assets/utils/classes/PasswordResetManager.php';
|
require '../../../assets/utils/classes/PasswordResetManager.php';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user