The default mail driver in config/mail.php is 'log', which had no
matching Vue component, causing the mail configuration step in the
install wizard (and settings page) to render empty.
Cherry-picked from v3.0 branch. Three fixes:
1. Refresh CSRF cookie after logout (auth.js)
2. Clear auth.token and selectedCompany from localStorage on logout (auth.js)
3. Invalidate session and regenerate CSRF token on server-side logout (web.php)
Without these, logging out and back in as a different user would fail
with CSRF token mismatch and 401 Unauthenticated errors because the
browser held stale session cookies and localStorage tokens.
Add scopeWhereCompany() to User model using whereHas through the
user_company pivot table. Apply it in UsersController::index() and
SearchController so users only see members of their current company.
Previously, the users page showed ALL users across all companies.
Ref #574
Verify the source record belongs to the current company before cloning.
Previously, users could clone invoices/estimates from other companies,
leaking sensitive data (amounts, customer details, items, taxes, notes).
The view policy already includes hasCompany() check, so authorizing
view on the source record gates both ability and company ownership.
Ref #574
Bulk delete: filter IDs through whereCompany() before deleting in all
controllers (Invoices, Payments, Items, Expenses, Estimates, Recurring
Invoices). Previously, any user could delete records from other companies
by providing cross-company IDs.
Transfer ownership: fix inverted hasCompany() check that allowed
transferring company ownership to users who do NOT belong to the company,
while blocking users who DO belong.
Ref #567
* Fix CustomerPolicy missing hasCompany() check (cross-company IDOR)
Add $user->hasCompany($customer->company_id) check to view, update,
delete, restore, and forceDelete methods in CustomerPolicy, matching
the pattern used by all other policies (InvoicePolicy, PaymentPolicy,
EstimatePolicy, etc.).
Without this check, a user in Company A with view-customer ability
could access customers belonging to Company B by providing the target
customer's ID.
Add cross-company authorization tests to verify the fix.
Closes#565
* Scope bulk delete to current company to prevent cross-company deletion
Filter customer IDs through whereCompany() before passing to
deleteCustomers(), ensuring users cannot delete customers belonging
to other companies via the bulk delete endpoint.
Update Node.js from 20 to 24 across CI workflows, Dockerfiles,
package.json engines field, and add .node-version file for consistent
local development.
Replace workflow-level paths-ignore with per-job filtering using
dorny/paths-filter. PHP lint and test jobs now only run when PHP-related
files (app/, config/, database/, routes/, tests/, composer.*, phpunit.xml)
are modified.
Migrate all 37 store definitions from the deprecated object-with-id
signature to the string-id-first signature required by Pinia 3:
defineStore({ id: 'name', ... }) → defineStore('name', { ... })
* refactor: add HTTP client wrapper and upgrade axios to v1
Introduce a thin HTTP wrapper (resources/scripts/http) that centralizes
axios configuration, interceptors, and auth header injection. All 43
files now import from the wrapper instead of axios directly, making
future library swaps a single-file change. Upgrade axios from 0.30.0
to 1.14.0.
* fix: restore window.Ls assignment removed during axios refactor
company.js uses window.Ls.set() to persist selected company,
which broke after the axios plugin (that set window.Ls) was deleted.
* docs: add CLAUDE.md for Claude Code guidance
* fix: handle missing settings table in installation middlewares
RedirectIfInstalled crashed with "no such table: settings" when the
database_created marker file existed but the database was empty.
Changed to use isDbCreated() which verifies actual tables, and added
try-catch around Setting queries in both middlewares.
* feat: pre-select database driver from env in installation wizard
The database step now reads DB_CONNECTION from the environment and
pre-selects the matching driver on load, including correct defaults
for hostname and port.
* feat: pre-select mail driver and config from env in installation wizard
The email step now fetches the current mail configuration on load
instead of hardcoding the driver to 'mail'. SMTP fields fall back
to Laravel config values from the environment.
* refactor: remove file-based DB marker in favor of direct DB checks
The database_created marker file was a second source of truth that
could drift out of sync with the actual database. InstallUtils now
checks the database directly via Schema::hasTable which is cached
per-request and handles all error cases gracefully.