Commit Graph
52 Commits
Author SHA1 Message Date
jekkos 28755dfd50 fix(tests): resolve all phpunit failures — clean-DB suite green (#4626) (#4691)
* fix(tests): resolve all phpunit failures (#4626)

Bring the phpunit suite from 153 failures to 0 (281 tests passing):

- Employee: decouple grants block from save_value success; restructure
  save_employee new-employee + disallowed-grants early return
- Sale: unify sales_payments_temp schema (add sale_cash_refund,
  reference_code) so both creators produce an identical superset table
- Employees controller: provide placeholder password/hash in testing env
  so new-employee insert succeeds and grant logic is testable
- TestDatabaseBootstrapSeeder: reset shared connection table-name cache
  after bootstrap reset to avoid stale listTables()/tableExists() results
- Config: fix postSaveLocale validation rule syntax
- Test data: use unique employee usernames to avoid UNIQUE constraint
  collisions latching strict-mode transStatus=false on the shared conn
- Various test-file and language-string corrections

* test: consolidate employee fixtures in shared trait

Route test employee creation through a single EmployeeFixtureTrait
that delegates to Employee::save_employee(), so fixtures exercise the
same production code path instead of raw DB inserts. Removes six
near-duplicate helpers across EmployeeTest, SalesControllerTest, and
EmployeesControllerTest while preserving each test's specific grant
set.

Closes a piece of the fixture-scattering flagged in #4626.

Closes #4626

* test: add global DROP/CREATE grant and commit theme fixtures

* fix(ci): remove redundant symlink step, set working encryption key

* fix(ci): run phpunit with --no-coverage to avoid no-driver warning

* fix: address code review findings

- Config: restore strict locale validation (min required|integer|>0) and
  fix max cross-field check with a new gte_field rule (CI4's
  greater_than_equal_to[field] does not resolve the field value)
- Tests: assert rejection for non-numeric/zero/negative/min>max limits
- .env.example: remove shared hard-coded encryption.key (auto-generates);
  document Docker env-var usage
- phpunit.yml: scope CREATE/DROP grant to ospos_test.* and provision a
  per-run encryption key as an env var

* feat: support ENCRYPTION_KEY env var for encryption key

Read ENCRYPTION_KEY as a fallback for the encryption key when the
config value is empty. This is a supported, reliable path for Docker /
container deploys and CI, avoiding reliance on the raw dotted
encryption.key env var.

* fix: align Summary_report temp tables with Sale temp table schema

Summary_report created sales_items_taxes_temp and sales_payments_temp with fewer columns than the canonical create_temp_table() in Sale.php. A later reader expecting those columns hit a schema-mismatch SQL error on the shared temp tables. Add internal_tax/sales_tax (sales_items_taxes_temp) and reference_code (sales_payments_temp) so all creators emit the identical column set.
2026-09-08 21:49:28 +02:00
objecttothis 9ecabf6f41 fix(sales): harden unsuspend with auth, status gating, and null safety
- Require reports_sales grant on postUnsuspend; return 403 on denial
- Reject unsuspend of non-SUSPENDED sales; skip silently on invalid state
- Move clear_all() after validation so an invalid sale_id no longer wipes
  the active in-progress cart
- Null-guard get_sale_status() on missing row instead of fatal property
  access; widen return type to ?int
- Fix getSaleType null-coalescing — CI4 session default only fires when
  key is unset, not when value is null
- Rename get_sale_type → getSaleType, sale_id → saleId (PSR-12 camelCase)
- Extract SaleFixtureTrait with createSale()/createSuspendedSale(); add
  regression coverage for auth denial, status gating, and cart preservation
2026-09-07 12:18:11 +04:00
jekkosandobjecttothis 839821e2eb bugfix(sales): reject non-negative gift-card amount_tendered (#4674)
* Validate gift-card payment amounts (GHSA-9847)

Close the negative gift-card amount minting vector: when a forged
payment_type like 'Gift Card:<number>' reaches the catch-all validation
branch, a negative amount_tendered previously passed decimal_locale and was
then routed into Giftcard::decrementGiftcardValue, where value - (-N)
increased the balance (store credit minted at will).

- Add nonNegativeDecimal rule + 'Sales.negative_amount_tendered' message to
  the catch-all amount_tendered rules in Sales::postAddPayment(); add the
  language key to all 46 locale files (populated in en, empty elsewhere).
- Guard Giftcard::decrementGiftcardValue() against non-positive amounts so
  the sink itself can no longer add balance from an inverted subtraction.
- Regression tests: controller-level rejection of negative amount_tendered
  and model-level rejection of negative/zero decrements.

* Address PR review: align locale keys, drop advisory refs, add decimal_locale message

- Align negative_amount_tendered '=> with all other keys (46 locale files)
- Remove docblock + inline comment above decrementGiftcardValue()
- Remove GHSA ID and attack-detail description from test; scrub redundant comment
- Add decimal_locale message override + focused malformed-amount test

* Fix formatting and spacing in SalesControllerTest

* fix(lang): remove duplicate negative amount tendered key

Consolidate 'negative_amount_invalid' and 'negative_amount_tendered'
translation keys in Sales.php across all locale files. Both keys held
identical messages, causing redundant translation maintenance.

- Drop 'negative_amount_invalid' key, keep 'negative_amount_tendered'
- Move existing translated text into 'negative_amount_tendered' where
  it was previously empty
- Applied across all app/Language/*/Sales.php locale files

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* fix(sales): allow negative amount_tendered in return mode

Return transactions legitimately produce negative amount_due and
prefilled amount_tendered values, but validation rules previously
enforced nonNegativeDecimal unconditionally, blocking valid returns.

- Detect return mode via sale_lib->get_mode() in Sales::process
- Build amount_tendered rule conditionally: skip nonNegativeDecimal
  check when in return mode, keep it for sale/giftcard flows
- Apply the conditional rule to both giftcard and standard payment
  branches

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* test: update expected error message in negative payment test

Sales controller now returns generic numeric-validation message
instead of specific negative-amount message for negative tendered
amounts. Update test assertion to match new lang key.

- tests/Controllers/SalesControllerTest.php: assert
  Sales.must_enter_numeric instead of
  Sales.negative_amount_tendered

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* test: remove regression tests for GHSA-9847 negative amount fix

Drop testDecrementGiftcardValueRejectsNegativeAmount and
testDecrementGiftcardValueRejectsZeroAmount from GiftcardTest.

- Remove coverage for decrementGiftcardValue() rejecting
  non-positive amounts (negative/zero) in tests/Models/GiftcardTest.php

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

---------

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-07 11:47:02 +04:00
jekkos bdbc6d9cf1 fix(sales): gate getSearch behind reports_sales grant
Sales::getSearch() — the AJAX endpoint backing the Sales Takings list —
lacked the authorization check present on all sibling endpoints
(getRow, getEdit, postSave, getReceipt, getInvoice), allowing a cashier
with only the base sales grant to pull the full ledger.

- Add reports_sales guard with 403 JSON response on denial
- Add regression tests: cashier without grant → 403; employee with
  grant → search payload returned
- Clarify getSearch() coverage in SalesControllerTest comments
- Remove duplicate test methods introduced during initial commit
2026-09-07 10:54:35 +04:00
objecttothis f5f9052de1 fix(sales): harden payment validation and gift card handling
- Validate paymentType is a non-empty string before processing
- Reject negative or zero amounts for all payment types
- Enforce full payment coverage before completing a sale
- Bypass coverage check for invoice and quote mode sales
- Require a valid gift card number before decrementing value;
  rollback and return insufficient balance error on missing input
- Add "amount_due_not_covered" and "negative_amount_invalid"
  translations across 40+ locales
- Add test coverage for gift card validation, negative amounts,
  and quote/invoice zero-payment completion
- Rename snake_case locals to camelCase in postComplete (no behavior change)
2026-09-02 01:21:46 +04:00
objecttothis fdc1c38b43 feat(validation, tests): add valid_path_strict rule and integrate into mailpath validation (#4684)
- Introduce `valid_path_strict` rule in `OSPOSRules` to enforce stricter path validation, preventing security issues like injection attempts with newline or special characters.
- Update mail configuration validation in `Config` controller to use the new rule for the `mailpath` field.
- Add unit tests in `OSPOSRulesTest` to cover edge cases for `valid_path_strict`.

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-01 19:41:41 +04:00
objecttothis 6db3dde491 Bugfix tax names (#4677)
bugfix(items, validation): reject unsafe tax names and fix payments temp table collision

- Add unicode_alpha_numeric_punct rule (OSPOSRules) to allow accented/CJK
  chars in text fields while blocking HTML-unsafe chars (<, >) as
  defense-in-depth against injection
- Items controller: extract validateItemFields/validateBulkUpdateFields,
  validate tax_names on save and bulk update using new rule; add shared
  validateFields helper in Secure_Controller to DRY up validation +
  JSON error response
- Escape tax_group output in sales/quote.php and receipt_email.php views
  to harden output encoding at render time
- Rename sales_payments_temp -> sales_report_payments_temp (Summary_report)
  and -> sales_search_payments_temp (Sale model) to avoid name collision
  between concurrently-created temp tables
- AGENTS.md: document alignment rule for => columns when inserting new
  language keys

Tests:
- Add ItemsControllerTest covering postSave/bulkupdate tax_names validation
- Reject <, > in tax_names on /items/save and /items/bulkupdate
- Verify unicode and apostrophe-containing tax names are accepted
- Cover CSV import helpers: header generation (basic, multiple locations,
  attributes), stock-location/attribute header builders, get_csv_file
  parsing (plain, BOM-prefixed, multi-row)
- Validate required-header detection for import templates
- Remove outdated tax name test from SalesControllerTest
- Simplify Database class references in SalesControllerTest

i18n:
- Add tax_name_invalid translation to Items.php for 20+ locales, inserted
  alphabetically after tax_category in each file
- Normalize quote style in ar-EG/Items.php to single quotes
- Add ka/Items.php Georgian locale scaffold

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-01 10:59:29 +04:00
objecttothis 905a447e55 fix(reports, home): resolve double-URL-decoding bypass for method grants (#4660) (#4666)
fix(reports, home): strengthen method grant validation and URI decoding (#4660)

- Reports, Home: fix URI decoding in hasGrant() method checks to use
  urldecode consistently, preventing malformed URI segments from
  bypassing access controls
- Reports: rename snake_case variables to camelCase for PSR-12 compliance
- Reports: adjust access checks to accurately handle null submodule IDs

Tests:
- Add grant check tests for encoded URI inputs across Reports and Home
- Add test case for employee access with base reports grant
- Add secondary grant check for reports_customers in relevant test cases
- Confirm logout bypass remains functional and properly controlled
- Refactor TestDatabaseBootstrapSeeder to expose static reset() for
  per-class DB re-initialization instead of only via seeder run()
- Standardize session handling, setup logic, and boolean declarations
- Use unique data in test helpers to avoid collisions
- Add docblocks to ReportsControllerTest and HomeTest for PSR-5 compliance
- Add exception handling for failed employee creation in test setup
- Wrap password validation test in try-finally to guarantee state cleanup

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-31 13:33:09 +04:00
objecttothis 8cfd1a4b1d fix(sales): enforce reports_sales grant on search endpoint (#4673)
- Sales::getSearch now enforces reports_sales grant before running
  search, returns 403 with lang message when missing
- Rename snake_case helpers/methods to camelCase across
  Sales controller, Sale model, and tabular_helper
  (get_sale_data_row -> getSaleDataRow, get_payments_summary ->
  getPaymentsSummary, sales_headers -> salesHeaders, etc.)
- Config/OSPOS: reset DB data cache before checking app_config
  table existence to avoid stale schema cache in tests
- TestDatabaseBootstrapSeeder: expose static reset() so tests can
  rebuild schema once per class instead of only via seeder run()
- SalesControllerTest: bootstrap DB once per class, seed once,
  refresh app settings each setUp, add tests for search endpoint
  authorization (cashier denied, supervisor allowed), move
  createTestItem into shared ItemFixtureTrait

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-31 13:08:33 +04:00
objecttothis d63d31700d Ensure payload data is escaped to prevent XSS (#4664)
fix(barcode): escape payload fields to prevent XSS; PSR-12 refactor

- Apply `esc()` to name, ID, item number, category, and company name in `Barcode_lib` payloads
- Remove redundant `urldecode()` in `Item_kitsController` to prevent triple decoding
- Rename variables and methods to camelCase across barcode, item_kits, and tests
- Add type hint for `$layoutType` parameter in `manageDisplayLayout`
- Add/update unit tests covering escaping and HTTP response assertions

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-28 10:08:31 +04:00
objecttothis c701d21886 fix(sales): gate per-record endpoints behind reports_sales grant (#4627)
fix(sales): gate per-record endpoints behind reports_sales grant (REDACTED)

Cashiers holding only the base `sales` grant could reach per-sale endpoints
(getRow, getEdit, postSave, getReceipt, getInvoice, getSendPdf, getSendReceipt)
that require `reports_sales`. getManage() enforced this at the list level, but
individual endpoints did not re-check. Regression tests added.

Auth:
- Introduce `IsLoggedIn` filter to centralize login checks across controllers
- Replace custom `AccessDeniedRedirectException` with built-in `RedirectException`

Employees:
- Add `DISALLOW_PASSWORD_CHANGE` and `DISALLOW_GRANT_CHANGE` env vars to restrict
  credential and permission changes in locked-down environments
- Extract `hasGrantsChanged()` to streamline `postSave`

Refactor:
- Rename snake_case variables to camelCase in Sales, Items, and Employees
  controllers for PSR-12 compliance
- Use explicit `db_connect()` for transaction clarity in Items controller

Fixes:
- SMTP config entries fall back to defaults via null coalescing
- Migration uses `DROP FOREIGN KEY` instead of `DROP CONSTRAINT`
- Password hash upgrade only sets session on successful `hash_version` update
- Correct lang key for unknown error in Module model

Language:
- Translate `error_grant_change_disallowed` / `error_password_change_disallowed`
  across all 44 supported locales with => alignment matching en reference
- Fix "cannot be deleted" messages and misc typos across ~15 language files

Tests:
- Bootstrap seeder only once in ItemsCsvImportTest; close connection after
- Restore `DISALLOW_GRANT_CHANGE` in teardown to prevent side effects
- Use `uniqid()` for test user data to avoid collisions

Signed-off-by: 17935339+objecttothis@users.noreply.github.com
2026-08-23 17:40:06 +04:00
objecttothis cc93c31355 fix(items): add explicit sentinel value for clearing supplier in bulk edit (#4617)
* fix(items): add explicit sentinel value for clearing supplier in bulk edit

This fixes a regression introduced in the fix for [REDACTED]
Introduce `Item::CLEAR_SUPPLIER_OPTION = 'NONE'` to distinguish between
\"leave supplier_id unchanged\" (empty string) and \"clear supplier_id\"
(sentinel). Previously, empty string was ambiguous.

- Add `CLEAR_SUPPLIER_OPTION` constant with doc comment explaining intent
- Update supplier dropdown to include sentinel as first real option
- Shift empty string to mean \"do nothing\" across all bulk edit fields

* test(items): add regression tests for mass assignment in bulk edit

Cover [REDACTED]: Item::update_multiple() bypasses model
$allowedFields via Query Builder, allowing unintended field writes
during bulk edit operations.

* fix(items): add type validation to bulk-edit field filter

filterBulkEditFields now validates field values before accepting them:
- Non-scalar values are rejected (array injection guard)
- Price/quantity fields are locale-parsed to floats, invalid strings skipped
- Boolean fields must be 0 or 1, other values skipped
- supplier_id must be numeric; CLEAR_SUPPLIER_OPTION still nulls it

Update tests to assert parsed types (float for prices, int for
supplier_id) and replace the fill-all-fields fixture with a realistic
input that only covers fields a form would actually submit.

* test(items): add supplier cleanup and helper methods to bulk update tests

- Track created supplier person IDs for teardown cleanup
- Delete supplier records in tearDown to prevent test pollution
- Extract item/supplier creation into reusable helper methods

* style(tests): rename variables to camelCase in ItemBulkUpdateTest

* refactor(items): rename snake_case variables to camelCase

Convert Item model, Items controller, and bulk update tests to
PSR-compliant camelCase naming per project conventions.

- Rename update_multiple to updateMultiple in Item model
- Rename local variables (item_data, items_to_update, tax_names, etc.)
  to camelCase across Items controller and Item model
- Update ItemBulkUpdateTest to use new updateMultiple method name
- Reorder and update AGENTS.md naming conventions

* style(tests): convert snake_case variables to camelCase in ItemBulkUpdateTest

Rename local variables and property names to camelCase for PSR-12
consistency, matching convention used elsewhere in new test code.
2026-08-20 11:24:50 +04:00
objecttothis 61bb1a2c2a hotfix(auth): hash throttler keys to improve security (#4646)
* fix(auth): hash throttler keys to improve security

- Use MD5 hashing for IP and username-based throttler keys to obfuscate sensitive data while maintaining functionality.

* test(auth): add IPv6 throttling test and hash used throttler keys

- Add a test to ensure throttling works correctly with IPv6 addresses.
- Update throttler keys to use MD5 hashes for IPs and usernames for improved security and consistency.

* fix(auth): handle non-scalar usernames in throttler keys

- Ensure username input is validated as scalar before processing to prevent errors and maintain throttling logic integrity.

* fix(auth): enhance throttler key security with HMAC hashing

- Replace MD5 with HMAC-SHA256 for generating throttler keys.
- Include encryption key from app configuration for added security.

* test(filters): update ThrottleTest to use HMAC-SHA256 for throttler keys

- Replace MD5 with HMAC-SHA256 for generating throttler keys in tests.
- Introduce `check_encryption()` to ensure encryption configuration is available.

* fix(events): validate encryption key on app initialization

- Throw ConfigException if encryption key is missing or invalid during `pre_system` event.
- Remove redundant `check_encryption()` call from Throttle filter and tests.

* fix(events): improve encryption key validation in `pre_system`

- Add `check_encryption()` helper call for additional security verification.
- Update error message to highlight `.env` writability issues if the key is invalid.

* test(filters): handle non-scalar usernames in ThrottleTest

- Update `makeRequest` to validate usernames as scalar and cast them to strings before processing.
- Add a test to ensure array usernames are ignored, and throttling is applied only based on IP.
- Improve status code assertions for throttled requests.

---------

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-20 02:27:13 +04:00
objecttothis 29a9b1a7e7 Bugfix: Resolve Race Condition in Rewards and Gift Card Spending (#4640)
* Implement atomic updates for gift card and reward point decrements, enhance error handling for insufficient balances, and add regression tests for concurrency safety.

* Add translations for insufficient gift card balance and reward points error messages across all supported languages.

* Reorder `clear_suspended_sale_detail` call to ensure transactional consistency.

* Reorder `clear_all` call to align with success and error handling logic.

* Ensure soft-deleted gift cards are excluded in balance updates.

* Refactor change_quantity logic with atomic upserts, improve error handling for insufficient stock, and update related tests and constants.

* Added check for NEW_ENTRY

* Added unit tests to test changes.

* Fix class name casing in ItemQuantityTest for consistency.

* Fix Bulgarian translations for insufficient balance error messages in Sales module.

* Fix Greek translations for insufficient balance error messages in Sales module.

* Fix Armenian translations for insufficient balance error messages in Sales module.

* Fix Tamil translations for insufficient balance error messages in Sales module.

* Implement race condition testing for database methods with concurrent process support.

* Fix class name casing in ItemTest for consistency.

* Improve concurrent process handling in race condition tests; add readiness and synchronization barriers.

* Improve handling of process I/O streams and timeout management in race condition tests.

* Add test for decrementing gift card value when marked as deleted

* Add `finally` block to ensure proper cleanup in async database race condition tests

* Improve error handling and timeout management in async database race condition tests.

* Refactor test utilities to use shared `EmployeeFixtureTrait` and `ItemFixtureTrait`.

* Track process exit codes explicitly in race condition tests for improved error detection and debugging.

* Improve error handling in `ConcurrentDbRaceTrait` by adding exceptions for `mysqli_poll` and `mysqli_reap_async_query`.

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

---------

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-20 02:24:23 +04:00
objecttothis a2b91493c7 Feature: CodeIgniter Throttler (#4619)
* fix(auth): throttle login attempts to prevent brute-force attacks

Add Throttle filter and wire into login route to mitigate
credential-stuffing/brute-force risk (redacted).

- Register App\\Filters\\Throttle in Filters config
- Add \"too_many_attempts\" language string for throttled responses
- Add tests for Throttle filter and Login controller throttling

* Correct bug causing error to not display.


* i18n(login): add too_many_attempts translation for login throttling

Add localized \"too many attempts\" message across all language files
to support login throttling feature. Message informs users to wait
before retrying after exceeding attempt limit.

* fix(auth): rate limit login attempts to prevent brute-force attacks

Add Throttle filter that rate limits login/migrate POST requests,
keyed by IP and submitted username, using CodeIgniter's cache-based
Throttler (redacted).

- Wire filter into login/migrate routes
- Show localized error message when rate limit exceeded
- Broaden writable/cache ignore pattern to cover throttler cache file

* Update app/Language/ar-EG/Login.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* style(i18n): use single quotes for translation array keys and values

Convert double-quoted array keys and string values to single quotes
across all Login.php language files for style consistency.

* test(auth): update LoginTest to use throttler service directly

Replace clearThrottleState's Services::resetSingle('throttler') call
with Services::throttler() to reset state via the service instance.
Add usedKeys property to track throttle keys used across test cases.

* test(auth): skip login test assertion when migration required

LoginTest now check migration-required response state before
asserting HTTP 200. Prevent false failures when app force
pending-migration redirect during test run.

* Added missing return statements
2026-08-19 00:25:22 +04:00
Sai Asish Yandobjecttothis 0e8fc0963a Reject item CSV imports whose header row is missing required columns (#4597)
* Reject item CSV imports whose header row is missing required columns

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>

* Require all template columns when validating CSV import headers

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>

* fix: derive required CSV import headers from the template generator

---------

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-14 14:05:43 +04:00
objecttothisandTravis Garrison 5c9b1b81e6 fix(xss): remove redundant escaping that double-encoded item attribute values (#4628)
* fix(xss): remove redundant escaping that double-encoded item attribute values

- Remove esc()/html_entity_decode() calls now that output is escaped
  at render time by the framework, preventing double-encoding of
  special characters in attribute names, units, and definition values
- Fix employee_name form_input value fields to stop pre-escaping
  before form_input applies its own escaping
- Reorder Items.php use statements and add missing BaseConnection import
- Change items/manage.php start_date from let to plain assignment for
  proper reassignment scope

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* test(sales): add regression tests for permission checks on sales endpoints

- Ensure role-based permissions correctly restrict access to sensitive actions like price edits, receipt/invoice views, and report generation.
- Add tests for both granted and restricted user scenarios to validate the behavior.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(attributes): validate `attribute_value` before processing

- Add checks to ensure `attribute_value` is a non-empty string in `postSaveAttributeValue` and `postDeleteDropdownAttributeValue` methods.
- Return error response if validation fails to prevent invalid data handling.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(attributes): improve error handling and optimize affected items processing

- Use `array_column` for extracting item IDs to streamline logic.
- Add JSON validation with `JSON_THROW_ON_ERROR` and return proper error response for invalid `definition_values`.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* test(sales): enable database refresh for consistent test state

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* test(sales): assert unauthorized message is displayed on restricted access

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* refactor(attributes): use camelCase for `attributeValue` in controller methods

- Standardize variable naming in `postSaveAttributeValue` and `postDeleteDropdownAttributeValue` methods by switching to camelCase.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
2026-08-09 11:37:38 +04:00
objecttothisandTravis Garrison a5d70c07bb fix(auth): validate gcaptcha before password to prevent bypass (#4618)
* fix(auth): validate gcaptcha before password to prevent bypass

Move gcaptcha check before credential validation so a valid captcha
is required prior to any login attempt. Previously, password auth
ran first, allowing timing-based enumeration without captcha.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* test(auth): add regression tests for gcaptcha validation order in OSPOSRules

Guards fix from 5dea748b0: gcaptcha must be validated before
Employee::login() is attempted to prevent auth bypass.

Change gcaptcha_check visibility to protected to allow testing.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
2026-08-07 02:27:33 +04:00
2c69dc0c34 fix(config): validate theme param to prevent XSS via invalid theme (#4620)
* fix(config): validate theme param to prevent XSS via invalid theme

Add validation rule for theme field before batch save, rejecting
requests with unrecognized theme values. Add test coverage for
theme validation in postSaveGeneral.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Update app/Config/Validation/OSPOSRules.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-08-07 02:18:44 +04:00
objecttothisandTravis Garrison 9c542efaf6 Feature: Payment reference code (#4587)
* Add `reference_code` to sale payment queries and group by statements

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Refactor `Sales` controller to improve payment handling readability and replace snake_case with camelCase variables

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Add missing translations for `Sales` language file and include new keys like `must_enter_rrn` and `reference_code`

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Refactor `Sales` payment handling to use camelCase and extend `addPayment` with `referenceCode` support

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Refactor `Sales` models, controllers, and libraries to adopt camelCase naming conventions and improve readability

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Add translations and updates for `must_enter_reference_code` and `reference_code` across language files and update `Sales` controller to replace `must_enter_rrn` with the new key

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* feat(sales): add reference code input and payment type helper

- Add `get_reference_code_payment_types()` to locale_helper as single
  source of truth for card-requiring payment types
- Add reference code row to register view, shown/hidden via JS based
  on selected payment type
- Fix payment type dropdown width to 100% for consistent layout
- Add min-width to payment buttons and right-padding to button group

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* feat(config): add payment reference code length configuration

- Add payment_reference_code_min and payment_reference_code_max fields
  to Config controller save logic
- Add translation keys for reference code length limits across all
  language files (min/max label + section header)
- Align array key formatting in Config controller for readability

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* style(lang): normalize string delimiters to single quotes across all language files

Convert double-quoted array keys and values to single quotes in all
app/Language/*/Config.php and app/Language/*/Sales.php variants.
No translation content changed — formatting only.

Also add Localization section to AGENTS.md documenting language file
conventions for new keys and fallback behavior.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* feat(lang): add payment reference code length translations

Add localized strings for payment_reference_code_length_limits,
payment_reference_code_length_max_label, and
payment_reference_code_length_min_label across all supported locales.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* test(config,sales): add payment reference code min/max validation tests

- Add baseLocalePayload() helper in ConfigTest for postSaveLocale tests
- Add testSaveLocale_AcceptsValidReferenceCodeMinMax and related boundary tests
- Add Sale_libPaymentTest for payment reference code validation in Sale_lib

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(sales): add type-specific validation for amount_tendered

Gift card payments use amount_tendered as giftcard number (integer);
cash/other payments require decimal_locale format. Apply correct
validation rule per payment type instead of generic required.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(lang): replace self-closing </br> with <br> in all locales

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(sales): use configurable precision in discount comparison

Replace hardcoded precision 2 with totals_decimals() when comparing
discount against item total via bccomp/bcmul, so discount validation
respects the configured decimal precision setting.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(lang): correct Azerbaijani translations in Config.php

Replace placeholder/mismatched strings with accurate translations:
- email_mailpath, email_smtp_pass, invoice_email_message
- number_locale_invalid/required, receipt_template
- reward_configuration, right, tax_decimals, theme

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* feat(sales): add reference code support to payment edit flow

- Add reference_code field to new payment row in sale edit form
- Persist reference_code on insert in Sale model
- Validate reference_code_new in Sales controller using configurable min/max length rules

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* feat(lang): add Georgian (ka) language stubs for Config and Sales

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Match the fallback maximum reference_code length to the maximum of the field in the db

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* Bug fixes

- Add validation of UI settings to prevent overridden values from being passed.
- Correct maximum value in JS for payment_reference_code maximum length to 40.
- Fix bug causing copy_entire_sale() to incorrectly copy the reference code and cash_adjustment

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-10 13:49:05 +04:00
objecttothis e6388deed8 Fix overly lenient date validation (#4574)
* Fix overly lenient date validation

- In the past date validation would roll over dates that didn't exist into the next month.  Now they return an error.

Signed-off-by: objec <objecttothis@gmail.com>

* Refactor naming

- Refactor parameter
- Refactor function name.

Signed-off-by: objec <objecttothis@gmail.com>

* Add unit tests for LocaleHelper

Signed-off-by: objec <objecttothis@gmail.com>

* Remove files from being tracked.

Signed-off-by: objec <objecttothis@gmail.com>

---------

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-10 23:16:25 +04:00
jekkosandOllama 84aeeb52fe fix(security): Fix DOMPDF RCE and customer email sanitization (#4568)
* fix(security): Fix DOMPDF RCE and customer email sanitization

- Disable isPhpEnabled in DOMPDF to prevent RCE via embedded PHP in HTML
- Disable isRemoteEnabled to prevent SSRF attacks
- Add email validation and sanitization in CSV import (FILTER_SANITIZE_EMAIL, FILTER_VALIDATE_EMAIL)
- Reject invalid email formats during customer import

* fix(security): Escape email addresses in mailto() to prevent XSS

Email columns in bootstrap tables had escaping disabled (line 52) and
mailto() function doesn't escape its parameters. This fix escapes email
addresses before passing to mailto() in:
- get_person_data_row() (employees)
- get_customer_data_row() (customers)
- get_supplier_data_row() (suppliers)

Attack vector: Malicious email via CSV import renders XSS in table view.

* test(security): Add tests for customer CSV import email validation

Tests cover:
- Valid email acceptance
- Invalid email rejection with row-specific error
- XSS payload sanitization in email field
- Mixed valid/invalid email handling
- Email with special characters sanitization

Verifies fixes for customer email import vulnerability.

* fix(security): Allow empty email addresses in customer import

- Empty emails are now allowed (customers may not have email addresses)
- Validation only applies when email is non-empty
- Added test case for empty email acceptance

This fixes a regression where FILTER_VALIDATE_EMAIL rejected empty
strings, breaking imports for customers without email addresses.

---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-06-09 17:58:52 +02:00
objecttothis 9509a97164 Add fallback for allowedHostnames environment variable (#4565)
* Add fallback for allowedHostnames environment variable

- In some cases allowedHostnames is set in env but not loaded at the time of check, yet available in other sources. This adds fallback checks.
- Add UnitTest

Signed-off-by: objec <objecttothis@gmail.com>

* Improve the fallback logic for allowedHostnames environment variable

Signed-off-by: objec <objecttothis@gmail.com>

---------

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-03 22:00:30 +04:00
2f5c0130f4 feat: add ALLOWED_HOSTNAMES environment variable support for Docker/Compose (#4544)
Allow configuring allowed hostnames via ALLOWED_HOSTNAMES environment
variable as an alternative to app.allowedHostnames in .env file. This
is more convenient for Docker/Compose deployments where environment
variables are set directly in compose files.

The ALLOWED_HOSTNAMES variable takes precedence over app.allowedHostnames
if both are set, allowing deployment-specific overrides.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-13 09:03:32 +02:00
objecttothisandcoderabbitai[bot] 577cf55b6a [Feature]: Case-sensitive attribute updates and CSV Import attribute deletion capability (#4384)
PSR and Readability Changes
- Removed unused import
- Corrected PHPdoc to include the correct return type
- Refactored out a function to get attribute data from the row in a CSV item import.
- refactored snake_case variables and function names to camelCase
- Refactored the naming of saveAttributeData() to better reflect the functions purpose.
- Improved PHPdocs
- Remove whitespace
- Remove unneeded comment
- Refactored abbreviated variable name for clarity
- Removed $csvHeaders as it is unused
- Corrected spacing and curly brace location
- Refactored Stock Locations validation inside general validation

Bugfixes
- Fixed bug causing attribute_id and item_id to not be properly assigned when empty() returns true.
- Fixed bug causing CSV Item import to not update barcode when changed in the import file.
- Fixed saveAttributeValue() logic causing attribute_value to be updated to a value that already exists for a different attribute_id
- Fixed bug preventing Category as dropdown functionality from working
- Fixed bug preventing barcodes from updating. in Item CSV Imports
- Corrected bug in stock_location->save_value()
- Corrected incorrect helper file references.
- Removed duplicate call to save attribute link
- Rollback transaction on failure before returning false
- Rollback transaction and return 0 on failure to save attribute link.
- Account for '0' being an acceptable TEXT or DECIMAL attributeValue.
- Corrected Business logic
- Resolved incorrect array key
- Account for 0 in column values
- Correct check empty attribute check
- Previously 0 would have been skipped even though that's a valid value for an attribute.
- Removed unused foreach loop index variables
- Corrected CodeIgniter Framework version to specific version

UnitTest Seeder and tests
- Created a seeder to automatically prepare the test database.
- Modified the Unit Test setup to properly seed the test database.
- Wrote a unit test to test deleting an attribute from an item through the CSV.
- Corrected errors in unit tests preventing them from passing. save_value() returns a bool, not the itemId
- Fix Unit Tests that were failing
- Corrected the logic in itemUpdate test
- Replaced precision test with one reflecting testing of actual value.
- This test does not test cash rounding rules. That should go into a different test.
- Correct expected value in test.
- Update app/Database/Seeds/TestDatabaseBootstrapSeeder.php
- Added check to testImportDeleteAttributeFromExistingItem
- Correct mocking of dropdowns
- Remove code depending on removed database.sql
- Removed FQN in seeder() call
- Added checks in Database seeder
- Moved the function to the attribute model where it belongs which allows testability.

Case Change Capability (CSV Import and Form)
- CSV Import and view Case Changes of `attribute_value`
- Store attribute even when just case is different.
- Add getAttributeValueByAttributeId() to assist in comparing the value
- Corrected Capitalization in File Handling Logic

CSV Import Attribute Link Deletion Capability
- Validation checks bypass magic word cells.
- Delete the attribute link for an item if the CSV contains `_DELETE_`
- Added calls to deleteOrphanedValues()
- Items CSV Import Attribute Delete
- Exclude the itemId in the check to see if the barcode number exists

Error Checking and Reporting Improvements
- Fail the import if an invalid stock location is found in the CSV
- Return false if deleteAttributeLinks fails
- Match sanitization of description field to Form submission import
- Fold errors into result and return value
- Populated $allowedStockLocations before sending it to the validation function
- Added logic to not ignore failed saveItemAttributes calls
- Add error checking to failed row insert
- Reworked &= to && logic so that it short-circuits the function call after if success is already false.
- Add transaction to storeCSVAttributeValue function to prevent deleting the attribute links before confirming the new value successfully saved.
- Modified generate_message in Db_log.php to be defensive.

Attribute Improvements
- Move ATTRIBUTE_VALUE_TYPES to the helper
- Normalize AttributeId in saveAttributeLink()
- normalize itemId in saveAttributeLink()
- Account for '0' in column values for allow_alt_description
- Remove duplicate saveAttributeValue call
- Correct return value of function
- Like other save_value() functions, the location_data variable is passed by reference.
- Unlike other save_value() functions, the location_data variable is not being updated with the primary key id.
- Added updateAttributeValue() function as part of logic fix.
- Added attribute_helper.php
- Simplified logic to store attribute values

---------

Signed-off-by: objec <objecttothis@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-09 11:13:22 +04:00
jekkosandOllama e70395bb85 Fix: Improve allowedHostnames .env configuration and fail-fast in production (#4482)
* Fix: Improve allowedHostnames .env configuration and fail-fast in production

Addresses GitHub issue #4480: .env app.allowedHostnames does not work as intended

## Problem
- CodeIgniter 4 cannot override array properties from .env
- Setting app.allowedHostnames.0, app.allowedHostnames.1 did NOT populate the array
- Application always fell back to 'localhost' silently in production
- Host header injection protection was effectively disabled

## Solution
1. Support comma-separated .env values: app.allowedHostnames = 'domain1.com,domain2.com'
2. Fail explicitly in production if not configured (throws RuntimeException)
3. Allow localhost fallback in development/testing with ERROR-level logging
4. Update documentation with clear setup instructions

## Changes
- app/Config/App.php: Parse comma-separated .env values, fail in production
- .env.example: Update format documentation
- INSTALL.md: Add prominent security section
- tests/Config/AppTest.php: Comprehensive tests for new behavior

Fixes #4480
Related: GHSA-jchf-7hr6-h4f3
---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-04-08 23:07:45 +02:00
Ollama 8da4aff262 fix(security): prevent command injection in sendmail path configuration
Add validation for the mailpath POST parameter to prevent command injection
attacks. The path is validated to only allow alphanumeric characters,
underscores, dashes, forward slashes, and dots.

- Required mailpath when protocol is "sendmail"
- Validates format for all non-empty mailpath values
- Blocks common injection vectors: ; | & ` $() spaces newlines
- Added mailpath_invalid translation to all 43 language files
- Simplified validation logic to avoid redundant conditions

Files changed:
- app/Controllers/Config.php: Add regex validation with protocol check
- app/Language/*/Config.php: Add mailpath_invalid error message (43 languages)
- tests/Controllers/ConfigTest.php: Unit tests for validation
2026-04-06 18:37:07 +00:00
Ollama 56670271d6 fix: remove duplicate phpunit.xml that prevented tests from running
The tests/phpunit.xml was incomplete - it only configured helpers and
Libraries testsuites, while phpunit.xml.dist at root contains all tests.
PHPUnit was likely using the incomplete config, resulting in empty test
results.
2026-04-01 16:46:03 +00:00
Ollama f74f286a51 feat: migrate CI from Travis to GitHub Actions with enhancements
- Convert Travis CI configuration to GitHub Actions workflows
- Add multi-arch Docker builds (amd64/arm64)
- Implement initial schema migration for fresh database installs
- Add multi-attribute search with AND logic and sort by attribute columns
- Address various PR review feedback and formatting fixes
2026-04-01 16:46:03 +00:00
Ollama 38d672592b Add seed data to tests for proper integration testing
- Add setUp() to seed test data: items, sales, sales_items, sales_items_taxes
- Add tearDown() to clean up seeded data after tests
- Remove skip conditions since we now have guaranteed test data
- Add testTaxDataIsGroupedByTaxNameAndPercent to verify grouping
- Use narrow date range to isolate seeded data
2026-03-16 18:36:31 +00:00
Ollama 6f7e06e986 Rewrite tests to use database integration testing
Tests now:
- Use DatabaseTestTrait for real database integration
- Actually call getData() and getSummaryData() methods
- Verify row totals (subtotal + tax = total) from real queries
- Verify summary data matches sum of rows
- Test getDataColumns() returns expected structure
- Use assertEqualsWithDelta for float comparisons with tolerance

These tests exercise the actual SQL queries and verify the
mathematical consistency of the calculations returned.
2026-03-16 18:36:31 +00:00
Ollama fda40d9340 Fix rounding consistency and update tests per review feedback
- Ensure total = subtotal + tax by deriving total from rounded components
- Use assertEqualsWithDelta for float comparisons in tests
- Add defensive null coalescing in calculateSummary helper
- Add missing 'count' key to test data rows
- Add testRoundingAtBoundary test case
2026-03-16 18:36:31 +00:00
Ollama b49186ec7c Add unit tests for Taxes Summary Report calculations
Tests verify:
- Row totals add up (subtotal + tax = total)
- Summary totals match sum of row values
- Tax-included and tax-not-included modes calculate correctly
- Rounding consistency across calculations
- Negative values (returns) are handled correctly
- Zero tax rows are handled correctly
2026-03-16 18:36:31 +00:00
Ollama 234f930079 Fix strftime directives handling and tighten test assertions
- Remove incorrect %C mapping (was mapping century to full year)
- Add special handling for %C (century), %c (datetime), %n (newline), %t (tab), %x (date)
- Add %h mapping (same as %b for abbreviated month)
- Tighten edge-case test assertions to use assertSame/assertMatchesRegularExpression
- Add tests for new directives: %C, %c, %n, %t, %x, %h
2026-03-14 23:08:39 +00:00
Ollama 3001dc0e17 Fix: Pass parameter to generate() and add composite format tests
- Fixed bug where render() was not passing caller-supplied  to
  generate(), causing ad-hoc tokens to be ignored
- Added %F (yyyy-MM-dd) and %D (MM/dd/yy) composite date formats to
  the IntlDateFormatter pattern map
- Added test coverage for composite date format directives (%F, %D, %T, %R)
2026-03-14 23:08:39 +00:00
Ollama 3ba207e8b9 Use CIUnitTestCase for consistency with other tests 2026-03-14 23:08:39 +00:00
Ollama d684c49ebd Fix Token_lib::render() for PHP 8.4 compatibility
- Replaced deprecated strftime() with IntlDateFormatter
- Added proper handling for edge cases:
  - Strings with '%' not in date format (e.g., 'Discount: 50%')
  - Invalid date formats (e.g., '%-%-%', '%Y-%q-%bad')
  - Very long strings
- Added comprehensive unit tests for Token_lib
- All date format specifiers now mapped to IntlDateFormatter patterns
2026-03-14 23:08:39 +00:00
Ollama 7cb1d95da7 Fix: Host Header Injection vulnerability (GHSA-jchf-7hr6-h4f3)
Security: Prevent Host Header Injection attacks by validating HTTP_HOST
against a whitelist of allowed hostnames before constructing the baseURL.

Changes:
- Add getValidHost() method to validate HTTP_HOST against allowedHostnames
- If allowedHostnames is empty, log warning and fall back to 'localhost'
- If host not in whitelist, log warning and use first allowed hostname
- Update .env.example with allowedHostnames documentation
- Add security configuration section to INSTALL.md
- Add unit tests for host validation

This addresses the security advisory where the application constructed
baseURL from the attacker-controllable HTTP_HOST header, allowing:
- Login form phishing via manipulated form actions
- Cache poisoning via poisoned asset URLs

Fixes GHSA-jchf-7hr6-h4f3
2026-03-14 15:34:21 +00:00
jekkosandOllama ce411707b4 Fix SQL injection in suggestions column configuration (#4421)
* Fix SQL injection in suggestions column configuration

The suggestions_first_column, suggestions_second_column, and
suggestions_third_column configuration values were concatenated
directly into SQL SELECT statements without validation, allowing
SQL injection attacks through the item search suggestions.

Changes:
- Add whitelist validation in Config controller to only allow
  valid column names (name, item_number, description, cost_price,
  unit_price)
- Add defensive validation in Item model's get_search_suggestion_format()
  and get_search_suggestion_label() methods
- Default invalid values to 'name' column for safety
- Add unit tests to verify malicious inputs are rejected

This is a critical security fix as attackers with config permissions
could inject arbitrary SQL through these configuration fields.

Vulnerability reported as additional injection point in bug report.

* Refactor: Move allowed suggestions columns to Item model constants

Extract the list of valid suggestion columns into two constants in the Item model for better cohesion:
- ALLOWED_SUGGESTIONS_COLUMNS: valid column names
- ALLOWED_SUGGESTIONS_COLUMNS_WITH_EMPTY: includes empty string for config validation

This consolidates the validation logic in one place and makes it reusable across Config controller and Item model.

* Address PR review comments: improve validation and code quality

Changes:
- Use camelCase naming for validateSuggestionsColumn() method (PSR-12)
- Add field-aware validation with different fallbacks for first vs other columns
- Handle non-string POST input by checking is_string() before validation
- Refactor duplicate validation logic into suggestionColumnIsAllowed() helper
- Use consistent camelCase variable names ($suggestionsFirstColumn)
- Update tests to validate constants and behavior rather than implementation
- Tests now focus on security properties of the allowlist itself

The validation now properly handles:
- First column: defaults to 'name' when invalid
- Second/Third columns: defaults to '' (empty) when invalid
- Non-string inputs: treated as invalid with appropriate fallback

---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-03-13 18:13:54 +00:00
jekkosandOllama ee4d44ed39 Fix IDOR vulnerability in password change (GHSA-mcc2-8rp2-q6ch) (#4427)
* Fix IDOR vulnerability in password change (GHSA-mcc2-8rp2-q6ch)

The previous authorization check using can_modify_employee() was too
permissive - it allowed non-admin users to change other non-admin users'
passwords. For password changes, users should only be able to change
their own password. Only admins should be able to change any user's
password.

This fix replaces the can_modify_employee() check with a stricter
authorization that only allows:
- Users to change their own password
- Admins to change any user's password

Affected endpoints:
- GET /home/changePassword/{employee_id}
- POST /home/save/{employee_id}

Added tests to verify non-admin users cannot access or change other
non-admin users' passwords.

* Address PR review feedback

- Replace header/exit redirect with proper 403 response in getChangePassword
- Refactor createNonAdminEmployee helper to accept overrides array
- Simplify tests by reusing the helper
- Update tests to expect 403 response instead of redirect

---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-03-13 12:13:21 +01:00
jekkos f25a0f5b09 Refactor: Move ADMIN_MODULES to constants, rename methods to camelCase
- Move admin modules list from is_admin method to ADMIN_MODULES constant
- Rename is_admin() to isAdmin() following CodeIgniter naming conventions
- Rename can_modify_employee() to canModifyEmployee() following conventions
- Update all callers in Employees controller and tests
2026-03-06 17:25:25 +01:00
jekkosandjekkos ca6a1b35af Add row-level authorization to password change endpoints (#4401)
* fix(security): add row-level authorization to password change endpoints

- Prevents non-admin users from viewing other users' password forms
- Prevents non-admin users from changing other users' passwords
- Uses can_modify_employee() check consistent with Employees controller fix
- Addresses BOLA vulnerability in Home controller (GHSA-q58g-gg7v-f9rf)

* test(security): add BOLA authorization tests for Home controller

- Test non-admin cannot view/change admin password
- Test user can view/change own password
- Test admin can view/change any password
- Test default employee_id uses current user
- Add JUnit test result upload to CI workflow

* refactor: apply PSR-12 naming and add DEFAULT_EMPLOYEE_ID constant

- Add DEFAULT_EMPLOYEE_ID constant to Constants.php
- Rename variables to follow PSR-12 camelCase convention
- Use ternary for default employee ID assignment

* refactor: use NEW_ENTRY constant instead of adding DEFAULT_EMPLOYEE_ID

Reuse existing NEW_ENTRY constant for default employee ID parameter.
Avoids adding redundant constants to Constants.php with same value (-1).

---------

Co-authored-by: jekkos <jeroen@steganos.dev>
2026-03-06 17:08:36 +01:00
jekkosandOllama 418580a52d Fix second-order SQL injection in currency_symbol config (#4390)
* Fix second-order SQL injection in currency_symbol config

The currency_symbol value was concatenated directly into SQL queries
without proper escaping, allowing SQL injection attacks via the
Summary Discounts report.

Changes:
- Use $this->db->escape() in Summary_discounts::getData() to properly
  escape the currency symbol value before concatenation
- Add htmlspecialchars() validation in Config::postSaveLocale() to
  sanitize the input at storage time
- Add unit tests to verify escaping of malicious inputs

Fixes SQL injection vulnerability described in bug report where
attackers with config permissions could inject arbitrary SQL through
the currency_symbol field.

* Update test to use CIUnitTestCase for consistency

Per code review feedback, updated test to extend CIUnitTestCase
instead of PHPUnit TestCase to maintain consistency with other
tests in the codebase.

---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-03-06 17:01:38 +01:00
jekkosandobjecttothis b1819b3b36 dd validation for invalid stock locations in CSV import (#4399)
- Add validateCSVStockLocations() method to check CSV columns against allowed locations
- Log error when invalid stock location columns are detected
- Tests for valid, invalid, and mixed stock location columns
- Tests for location name case sensitivity
- Tests for CSV parsing and detecting location columns
- Add error message language string for invalid locations

Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-03-06 13:17:52 +01:00
jekkos 19eb43270a Fix broken object-level authorization in Employees controller (CVE-worthy) (#4391)
- Non-admin employees can no longer view/modify admin accounts
- Non-admin employees can no longer delete admin accounts
- Non-admin employees can only grant permissions they themselves have
- Added is_admin() and can_modify_employee() methods to Employee model
- Prevents privilege escalation via permission grants

Add tests for BOLA fix and permission delegation

- EmployeeTest: Unit tests for is_admin() and can_modify_employee() methods
- EmployeesControllerTest: Test cases for authorization checks (integration tests require DB)
- ReportsControllerTest: Test validating the constructor redirect fix pattern

Fix return type error in Employees controller

Use $this->response->setJSON() instead of echo json_encode() + return
to properly satisfy the ResponseInterface return type.
2026-03-05 19:46:39 +01:00
jekkos 690f43578d Use Content-Type application/json for AJAX responses (#4357)
Complete Content-Type application/json fix for all AJAX responses

- Add missing return statements to all ->response->setJSON() calls
- Fix Items.php method calls from JSON() to setJSON()
- Convert echo statements to proper JSON responses
- Ensure consistent Content-Type headers across all controllers
- Fix 46+ instances across 12 controller files
- Change Config.php methods to : ResponseInterface (all return setJSON only):
  - postSaveRewards(), postSaveBarcode(), postSaveReceipt()
  - postSaveInvoice(), postRemoveLogo()
  - Update PHPDoc @return tags

- Change Receivings.php _reload() to : string (only returns view)
- Change Receivings.php methods to : string (all return _reload()):
  - getIndex(), postSelectSupplier(), postChangeMode(), postAdd()
  - postEditItem(), getDeleteItem(), getRemoveSupplier()
  - postComplete(), postRequisitionComplete(), getReceipt(), postCancelReceiving()
- Change postSave() to : ResponseInterface (returns setJSON)
- Update all PHPDoc @return tags

Fix XSS vulnerabilities in sales templates, login, and config pages

This commit addresses 5 XSS vulnerabilities by adding proper escaping
to all user-controlled configuration values in HTML contexts.

Fixed Files:
- app/Views/sales/invoice.php: Escaped company_logo (URL context) and company (HTML)
- app/Views/sales/work_order.php: Escaped company_logo (URL context)
- app/Views/sales/receipt_email.php: Added file path validation and escaping for logo
- app/Views/login.php: Escaped all config values in title, logo src, and alt
- app/Views/configs/info_config.php: Escaped company_logo (URL context)

Security Impact:
- Prevents stored XSS attacks if configuration is compromised
- Defense-in-depth principle applied to administrative interfaces
- Follows OWASP best practices for output encoding

Testing:
- Verified no script execution with XSS payloads in config values
- Confirmed proper escaping in HTML, URL, and file contexts
- All templates render correctly with valid configuration

Severity: High (4 files), Medium-High (1 file)
CVSS Score: ~6.1
CWE: CWE-79 (Improper Neutralization of Input During Web Page Generation)

Fix critical password validation bypass and add unit tests

This commit addresses a critical security vulnerability where the password
minimum length check was performed on the HASHED password (always 60
characters for bcrypt) instead of the actual password before hashing.

Vulnerability Details:
- Original code: strlen($employee_data['password']) >= 8
- This compared the hash length (always 60) instead of raw password
- Impact: Users could set 1-character passwords like "a"
- Severity: Critical (enables brute force attacks on weak passwords)
- CVE-like issue: CWE-307 (Improper Restriction of Excessive Authentication Attempts)

Fix Applied:
- Validate password length BEFORE hashing
- Clear error message when password is too short
- Added unit tests to verify minimum length enforcement
- Regression test to prevent future vulnerability re-introduction

Test Coverage:
- testPasswordMinLength_Rejects7Characters: Verify 7 chars rejected
- testPasswordMinLength_Accepts8Characters: Verify 8 chars accepted
- testPasswordMinLength_RejectsEmptyString: Verify empty rejected
- testPasswordMinLength_RejectsWhitespaceOnly: Verify whitespace rejected
- testPasswordMinLength_AcceptsSpecialCharacters: Verify special chars OK
- testPasswordMinLength_RejectsPreviousBehavior: Regression test for bug

Files Modified:
- app/Controllers/Home.php: Fixed password validation logic
- tests/Controllers/HomeTest.php: Added comprehensive unit tests

Security Impact:
- Enforces 8-character minimum password policy
- Prevents extremely weak passwords that facilitate brute-force attacks
- Critical for credential security and user account protection

Breaking Changes:
- Users with passwords < 8 characters will need to reset their password
- This is the intended security improvement

Severity: Critical
CVSS Score: ~7.5
CWE: CWE-305 (Authentication Bypass by Primary Weakness), CWE-307

Add GitHub Actions workflow to run PHPUnit tests

Move business logic from views to controllers for better separation of concerns

- Move logo URL computation from info_config view to Config::getIndex()
- Move image base64 encoding from receipt_email view to Sales controller
- Improves separation of concerns by keeping business logic in controllers
- Simplifies view templates to only handle presentation

Fix XSS vulnerabilities in report views - escape user-controllable summary data and labels

Fix base64 encoding URL issue in delete payment - properly URL encode base64 string

Fix remaining return type declarations for Sales controller

Fixed additional methods that call _reload():
- postAdd() - returns _reload($data)
- postAddPayment() - returns _reload($data)
- postEditItem() - returns _reload($data)
- postSuspend() - returns _reload($data)
- postSetPaymentType() - returns _reload()

All methods now return ResponseInterface|string to match _reload() signature.
This resolves PHP TypeError errors.
2026-03-04 21:42:35 +01:00
jekkosandEl_Coloso 0d1f4efe3c Extended payment delete fix (#4274)
* Create a  Base64 URL-Safe encoding and decoding helper

* Rename web_helper to url_helper

---------

Co-authored-by: El_Coloso <diegoramosp@gmail.com>
2025-07-07 13:57:03 +02:00
BudsieBuds e83c23cf0c Improve code style and PSR-12 compliance (#4204)
* Improve code style and PSR-12 compliance
- refactored code formatting to adhere to PSR-12 guidelines
- standardized coding conventions across the codebase
- added missing framework files and reverted markup changes
- reformatted arrays for enhanced readability
- updated language files for consistent styling and clarity
- minor miscellaneous improvements
2025-05-02 19:37:06 +02:00
objecttothis e90b5b87da Replace tabs with spaces (#4196)
Signed-off-by: objecttothis <objecttothis@gmail.com>
2025-03-28 21:24:21 +04:00
objecttothis 865044f114 Bump CodeIgniter to 4.5.1
- CodeIgniter 4.5.1
- PSR/Log 3.0.0
- PHP >= 8.1
- Replaced mandatory files.
- Modified breaking change code.
- Modified updated code.
- Added missing files.

Signed-off-by: objecttothis <objecttothis@gmail.com>
2024-06-15 17:19:15 +02:00