From 3e6b2319dd71eaebbeee06da54e17697cbc8eb34 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 9 Sep 2026 13:06:03 +0800 Subject: [PATCH] fix: align v1.6.61 release metadata --- RELEASE.md | 53 +++++++++------------------------------------------ composer.json | 2 +- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 4c1377cc..c398fce9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,50 +1,15 @@ -> v1.6.60 ~ "Revoked keys stay revoked, transactions stop colliding, webhooks stop signing with the wrong secret" +# v1.6.61 — Faster IAM user authorization loading ---- -## Highlights -Three independent defects, each of which let the platform keep doing something an operator had already told it to stop. +## Improvements -**Deleting an API credential did not revoke it.** The console hid the row and the key kept authenticating — indefinitely, on every endpoint. Combined with an "expire immediately" option that also did not take effect, a stock Fleetbase console had **no working way to revoke an API credential**. Anyone who has ever deleted a key in the console should read the upgrade steps. +- Reduce repeated database queries when listing IAM users by loading roles, policies, and permissions in batches and reading each user's primary role once. +- Match authorization to each user's company membership, including users belonging to multiple companies and system administrators viewing users across companies. User response fields remain unchanged. -**Persistent PDO handles shared one MySQL transaction.** Writes landed and the API still answered `422 There is no active transaction`, so anyone who retried after the error applied the write twice. +## Reliability -**A queue worker signed every lifecycle webhook with the first event's secret.** Every outbound webhook after the first carried the wrong HMAC key, along with the wrong credential, environment and company. +- Add database-backed coverage for company isolation, missing and deleted memberships, recovery after a membership was initially absent, and matching responses between lazy and eager loading. +- Enable PHP CI and Postman checks for `release/v*` branches and support release tagging from both `release/v*` and `dev-v*` branches. ---- -## Security Fixes -- **Soft-deleted API credentials still authenticated.** `AuthenticateOnceWithBasicAuth` looks the credential up with `withoutGlobalScopes()`, which strips `SoftDeletingScope` along with `ExpiryScope`. Expiry was re-applied in PHP; soft-deletion never was. A credential the console reports as **Deleted** kept authenticating indefinitely — and Delete is the only revocation most operators ever perform. Now rejected with a 401, before the `OPTIONS` shortcut so a revoked key cannot seed api key session context on a preflight either. +No database migration or configuration change is required. -- **Authentication was fail-open when the credential's creator was gone.** An API credential carries no identity of its own; it acts as the user that created it. When that user no longer resolved — deleted, or soft-deleted on off-boarding — the `is_admin` guard was skipped but `Auth::setSession()` still returned `true`. Authorization degraded safely, since a null user fails every group and admin check, but authentication did not: the key kept working on every read endpoint and every ungated write. Off-boarding a person did not revoke the keys they had created. `setSession()` now returns `false` in that case and the middleware answers a clean 401. - ---- -## Bug Fixes -- **"Expire immediately" did not expire the credential.** `ApiCredential::setExpiresAtAttribute()` maps the console's `immediately` option to `Carbon::now()`, but `Expirable::hasExpired()` used a strict `<`, so `now() < now()` was false. `ExpiryScope` already disagreed with it — it keeps a row only while `expires_at > now()`, i.e. it treats an exactly-now expiry as expired. `hasExpired()` is now inclusive, so the trait and the scope agree. - -- **Persistent PDO handles shared one MySQL transaction.** `Connection::commit()` decides whether to issue a COMMIT from its own counter; PDO decides whether one is legal from the server's `SERVER_STATUS_IN_TRANS` flag, and nothing reconciled the two. With `PDO::ATTR_PERSISTENT => true`, PHP hands the same MySQL session to a second handle built from the same DSN while the first is still using it, so one handle could commit — and thereby invalidate — the other's transaction. Observed on onboarding account creation, ledger invoice creation and inventory stock adjustments, none of which share a code path. Persistence is now `env('DB_PERSISTENT', false)` on the `mysql` and `sandbox` connections. - -- **A queue worker signed lifecycle webhooks with a stale secret.** `SendResourceLifecycleWebhook::setSessionFromEvent()` only wrote a session key when it was absent, and `handle()` preferred the session value over the event's. A `queue:work` process is long-running and its session store is a container singleton, so once the worker handled one lifecycle event, every later event reused that first event's `api_secret` — plus its `api_credential`, `api_key`, `api_environment`, `is_sandbox`, `company` and `user`. The context serialized onto the event is now authoritative for the job that carries it, and a restorer running in a `finally` hands the session back as it was found. Reported in #244. - ---- -## Testing -- New coverage for revoked credentials on both normal and `OPTIONS` requests, for a credential whose creator has been soft-deleted, for `Auth::setSession()` returning `false`, and for the exactly-now expiry boundary. -- Four tests for the webhook secret bleed, each verified to fail against the unpatched listener, covering per-event signing, sandbox/live isolation, session restoration, and event-over-session credential attribution. -- The middleware fixture previously seeded the sandbox user only on the sandbox connection. `User` is pinned to the `mysql` connection and `sandbox:sync` mirrors `mysql → sandbox`, so the fixture now matches the real system. - ---- -## Upgrade Steps -**Audit your API credentials.** Any credential deleted from the console before this release was never actually revoked and has been live the whole time. After upgrading, those keys stop working — which is the point, but it means an integration quietly running on a key someone believed they had deleted will break at upgrade rather than at deletion. List your credentials including soft-deleted rows before deploying if you want to know what will change. - -**Keys whose creator has been off-boarded will start returning 401.** That is the intent of the fix, but it is a behaviour change for anyone whose integrations run on a departed employee's credential. Reassign those to a dedicated service user before upgrading. - -**Deployments not running Octane may see per-request connect cost.** `PDO::ATTR_PERSISTENT` now defaults to off. Under Octane connection counts are unchanged (measured at 17 on a 24-thread FrankenPHP container). Short-lived PHP-FPM workers that relied on the pool will pay roughly 1–3 ms per request; `DB_PERSISTENT=true` restores the old behaviour, at the cost of re-arming the transaction bug for any request that opens a transaction. Note that persistent connections also silently defeated Octane's `DisconnectFromDatabases` listener, which now works as intended. - -No migration and no configuration change is required. - ---- -## Still Open -API credentials still carry no scope of their own — `Auth::setSession()` derives `is_admin` from whoever created the key, so every key an admin creates is a full-admin key regardless of what it is named. Least privilege is currently only reachable indirectly, by scoping the *creator* into a dedicated service user. Per-key roles, or an explicit key assignee decoupled from the creator, is a feature rather than a fix and is tracked separately. - ---- -## Need help? -- [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions) -- [Discord](https://discord.gg/HnTqQ6zAVn) +Changes: [#251](https://github.com/fleetbase/core-api/pull/251), [#250](https://github.com/fleetbase/core-api/pull/250), and release-branch CI updates in [#252](https://github.com/fleetbase/core-api/pull/252). diff --git a/composer.json b/composer.json index a6251875..9d9aa28d 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/core-api", - "version": "1.6.60", + "version": "1.6.61", "description": "Core Framework and Resources for Fleetbase API", "keywords": [ "fleetbase",