Skip to content

Releases: fleetbase/core-api

v1.6.68

Choose a tag to compare

@github-actions github-actions released this 05 Oct 08:02
bab4daa

v1.6.68 — Resource transformers apply to every resource

Added

  • Agnostic resource transformers. Any extension can decorate the serialized output of any API resource without modifying the resource or its model. Register a transformer against an HTTP resource class, an Eloquent model class, an interface, or '*' (subclasses match), and FleetbaseResource::resolve() applies it to JSON responses, nested resources, collection items, webhook payloads and broadcast payloads. Transformers chain in ascending priority and can be scoped by contexts (http, webhook, broadcast) and only (internal, public). (#285)
  • Fleetbase\Contracts\ResourceTransformer, Fleetbase\Contracts\PreparesResourceTransformation (a once-per-collection prepare() hook for batch loading, so transformers never add N+1 queries), Fleetbase\Support\ResourceTransformerContext, and the Fleetbase\Http\Transformers\Transformer base class.
  • Closure transformers via ResourceTransformerRegistry::register(fn (...) => ..., ['target' => ...]).
  • CoreServiceProvider::$transformers, registerTransformers() and registerTransformersFrom(__DIR__ . '/../Http/Transformers') for declarative and directory-based registration from extensions, mirroring expansions.

Changed

  • FleetbaseResourceCollection resolves items (instead of calling toArray()), sharing one prepare() pass per collection. A hand-built collection with a manually set preserveKeys now filters item arrays with the item's flag.
  • ResourceLifecycleEvent payloads, chat participant broadcasts, Utils::serializeJsonResource() and the cached internal user payload serialize through resolve(), so transformers reach them and conditional MissingValues are no longer emitted as {}.
  • Find::httpResourceForModel() caches internal and public resolutions separately, consulting the request only when a model has a dedicated Internal resource.

Removed

  • Legacy duck-typed transformers ($target property + static output($model, $data)), ResourceTransformerRegistry::transform(Model, array), resolveByTarget(), fixClassName() and the static $transformers array. The User resource no longer calls the registry directly.

Dependencies

  • fleetbase/laravel-mysql-spatial ^1.0.3. The spatial MysqlConnection no longer connects to MySQL when the connection object is built, so resolving DB::connection() during boot (for example artisan package:discover during composer install) no longer requires a reachable database.

Upgrade Steps

  • Extensions that registered a legacy transformer must implement Fleetbase\Contracts\ResourceTransformer (or extend Fleetbase\Http\Transformers\Transformer) and register it through $transformers or registerTransformersFrom(). See the README section "Resource transformers". The only known legacy consumer, aws-marketplace, is deprecated and is not updated.

What's Changed

Full Changelog: v1.6.67...v1.6.68

v1.6.67

Choose a tag to compare

@github-actions github-actions released this 29 Sep 10:58
a698292

v1.6.67 — API keys are generated randomly

Security

  • API keys created in the same second were identical, across organizations. A key was derived from its creation time and row id, but the id is never loaded after insert (the primary key is the uuid), so every key created in the same second got the same value. API authentication resolves a key to the first matching credential, so a key issued to one organization could authenticate as another's. Keys are now 32 random characters from the CSPRNG, for new keys and for rolled keys. (#283)

Upgrade Steps

  • Check for existing duplicate keys and roll every credential that shares one, in both the live and sandbox databases:
    SELECT `key`, COUNT(*) AS credentials, COUNT(DISTINCT company_uuid) AS orgs
    FROM api_credentials WHERE deleted_at IS NULL
    GROUP BY `key` HAVING COUNT(*) > 1;

What's Changed

  • fix(security): generate API keys from the CSPRNG instead of the creation time by @roncodes in #283
  • release/v1.6.67 by @roncodes in #284

Full Changelog: v1.6.66...v1.6.67

v1.6.66

Choose a tag to compare

@github-actions github-actions released this 29 Sep 09:39
74cc07c

v1.6.66 — Per-consumer API rate limiting, admin rate-limit controls and API consumer metrics

Fixes

  • One API consumer can no longer rate-limit the whole platform. ThrottleRequests ran before API authentication, so Laravel keyed every bucket on the client IP. Behind a load balancer that is the balancer's IP, so every tenant, API key and console visitor shared a single 120/min bucket, and one busy integration returned 429 to everyone. The limiter now keys on the presented credential (API key, Sanctum token or basic auth), falling back to the user and then the IP only when none is sent. The first path segment keeps /v1 and /int in separate buckets. (#280)
  • 429 responses keep Retry-After and X-RateLimit-*. The exception handler used to drop them, so throttled clients could not back off correctly. (#280)

Improvements

  • System admins can manage API rate limits at runtime. The limits (THROTTLE_* environment defaults) can be overridden from the console and stored as the system.rate-limits setting: enable/disable, requests per window, and window length. Per-organization overrides give an organization a custom limit or none. New admin-only endpoints GET/POST/DELETE int/v1/rate-limits/settings. (#281)
  • API consumer metrics. The throttle middleware counts every request and every 429 per consumer in Redis: minute buckets are kept for 2 hours, hour buckets for 8 days, at one pipelined round trip per request. GET int/v1/rate-limits/consumers?window=&sort=&limit= lists the busiest or most throttled consumers with their organization, masked key, scope, IP, avg and peak per minute, and share. POST int/v1/rate-limits/consumers/{signature}/reset clears one consumer's window. Tracking can be disabled with THROTTLE_TRACK_CONSUMERS=false; THROTTLE_METRICS_REDIS_CONNECTION picks the Redis connection (default cache). (#281)

What's Changed

  • fix(throttle): key the API rate limiter on the consumer, not the proxy IP by @roncodes in #280
  • feat(throttle): admin-managed rate limits, org overrides and API consumer metrics by @roncodes in #281
  • release/v1.6.66 by @roncodes in #282

Full Changelog: v1.6.65...v1.6.66

v1.6.65

Choose a tag to compare

@github-actions github-actions released this 28 Sep 09:49
b82d921

v1.6.65 — Authenticator-app 2FA, sign-in hardening and IAM permission fixes

Improvements

  • Organization administration supports company and owner identity search, country/timezone/owner-IP and registration/update-date filters, and sorting by current user count. Admins can open organizations outside their own memberships, inspect organization-scoped usage totals, and see members' 2FA methods and linked OAuth providers without exposing authentication secrets.
  • Sign in with an authenticator app (TOTP, RFC 6238), next to email and SMS 2FA (#163). It works with Authy, Google Authenticator, Microsoft Authenticator and 1Password.
    • New users/two-fa/authenticator endpoints to set up, confirm, disable and inspect the app. Setup, disable and regenerating recovery codes need the current password.
    • Confirming the app returns 8 single-use recovery codes. two-fa/verify accepts an app code (±1 step for clock drift, each code once) or a recovery code.
    • two-fa/resend falls back to an emailed (or SMS) code and returns the new method.
    • The secret is encrypted with the app key, and recovery codes are stored as keyed hashes.
    • New Fleetbase\Support\Barcode helper with a compact qrCodeSvg().
  • Record the app, APNs environment and last-seen time on user devices. New nullable user_devices columns: app_identifier (indexed), environment and last_seen_at.
  • New organization setting to let users change their own password (default on), at GET/POST companies/auth-settings. GET users/password-policy returns {can_change_password}.

Fixes

  • Settings → Notifications applies when notifications are sent from the queue or a console command (#262). NotificationRegistry takes the company from the notification's subject instead of the session. notifyUsingDefinitionName() reads the company-scoped settings instead of a global key nothing writes. New Setting::lookupForCompany().
  • Invited users can set their first password, and non-admins can change their own (#263). The password endpoints no longer fall through to iam create user.
  • FCM order notifications are sent with Android priority: high, so drivers' phones in Doze get them immediately (#268).

Security

  • A 2FA session starts only after the password is checked. GET two-fa/check used to start one from the identity alone, so an email plus the emailed code was enough to sign in. It now always returns {twoFaSession: null, isTwoFaEnabled: false} and no longer reveals whether an account uses 2FA.
  • 2FA sessions expire after 10 minutes; they used to live about 56 years. The 5th wrong code deletes the session, and resending doesn't reset the count. Codes are compared in constant time and generated with random_int.
  • users/change-password requires current_password in the same request, and iam change-password is enforced. users/set-password works only once, within 24 hours of accepting an invite. validate-password and change-password are limited to 10 requests per minute.
  • Close authorization gaps where AuthorizationGuard resolved to permission names that don't exist:
    • Updating the organization and its 2FA policy is limited to the owner, the Administrator role and system admins. Non-admin updates ignore owner_uuid, Stripe ids, plan, status, trial_ends_at and type.
    • POST two-fa/config (system 2FA policy) and admin platform metrics are limited to system admins.
    • IAM and developer metrics need iam list user / developers list api-key.
    • Reports use the iam service: iam execute report for direct queries, iam export report for exports.
    • API credentials, webhooks, API events and request logs check the api-key, webhook, event and log permissions.
  • Password, auth-setting and authenticator changes are written to the auth activity log.

Behaviour changes

  • Consoles need the companion fleetbase/fleetbase changes: the 2FA sign-in flow (fix/2fa-login-hardening), current_password on change-password (fleetbase/fleetbase#685) and the authenticator-app UI (fleetbase/fleetbase#686). With an older console, users with 2FA can't sign in and self-service password changes fail.
  • Users need iam … report permissions to use reports, and developers … permissions for API keys, webhooks, events and logs. Fleet-Ops' report screens check the same names in fleetbase/fleetops#345.
  • A user who accepted an invite before this release but never set a password should use Forgot password.

Run the migrations for the new user_devices columns. Run composer update to install pragmarx/google2fa.

Changes: #272, #273, #274, #275, #276, #277, #278.

What's Changed

  • fix(2fa): start two-factor sessions only after the password is checked by @roncodes in #272
  • feat(2fa): sign in with an authenticator app, with recovery codes by @roncodes in #277
  • fix(notifications): resolve notification settings from the notified company by @roncodes in #274
  • fix(iam): enforce the change-password permission and let invited users set a password by @roncodes in #275
  • fix(push): send FCM order notifications with android high priority by @roncodes in #273
  • feat: record app and APNs environment on user devices by @roncodes in #276
  • fix(iam): close authorization gaps in core controllers by @roncodes in #278
  • Release v1.6.65 by @roncodes in #279

Full Changelog: v1.6.64...v1.6.65

v1.6.64

Choose a tag to compare

@github-actions github-actions released this 25 Sep 10:15
3965998

v1.6.64 — Order reporting and test SMS with the entered credentials

Improvements for reporting

  • Declare row-level expression columns with Column::expression($name, $sql, $type). Bare names resolve against the table or relationship that declares it, so JSON_EXTRACT(meta, '$.quantity') on payload.entities reads the joined entity's meta. An expression column can be selected, filtered, sorted, grouped by and aggregated.
  • Summary columns (Column::count/sum/avg) are flagged aggregate and resolve to their computation. Without grouping they return a single summary row; with grouping they sit beside the group keys.
  • Table::softDeletes() and Relationship::softDeletes() leave out soft-deleted rows. On joins the filter goes in the ON clause, so LEFT joins keep the parent row.
  • Computed columns can be group keys, conditions and sort columns, and a grouped report can be sorted by an aggregate's alias. A new count_distinct aggregate is available.
  • Custom expressions accept the JSON functions, DATE(), CAST(… AS DECIMAL(15,2)) and the other cast types, DISTINCT, IN, GROUP_CONCAT(… ORDER BY … SEPARATOR …), ->/->> and INTERVAL n UNIT.
  • public_id and internal_id are no longer hidden as foreign keys, so ID columns appear in the column picker.
  • Relationship columns are labelled with the whole relationship name ("Order Config Namespace", not "Order Namespace"), and aggregate labels use the column label ("Sum (Quantity)").
  • _key and _import_id are never listed or selectable, whatever a schema declares.

Fixes

  • Test SMS Provider and Test Twilio in Admin › System Config › Services use the credentials entered in the form (fleetbase/fleetbase#680). Under Octane the Twilio client was built once per worker, so a test failed with "Credentials are required to create a Client" or reported success for the saved account. The endpoints now rebuild the client from the request's config and release it after the send.

Security

  • Every computed column is validated up front, including in grouped reports. Names must be safe identifiers, and SELECT is forbidden.
  • Schema-declared columns always take their SQL from the registry, never from the request. Group keys, aggregate columns, sort columns and condition fields must be allowed or computed columns.
  • Sort direction is normalised to asc/desc, and grouped reports validate aggregateBy.computation.

Behaviour changes

  • In a grouped report, a selected column that is neither a group key nor aggregated is now an error instead of being dropped.
  • Invalid report shapes fail with a clear message instead of an SQL error.

A database migration is not required. No configuration change is needed. The FleetOps order report schema ships in fleetbase/fleetops v0.6.70, and the report builder changes in fleetbase/ember-ui v0.4.4.

Changes: #269, #270.

What's Changed

  • Send test SMS with the credentials entered in the console by @roncodes in #270
  • Report on order items, JSON totals and computed group keys by @roncodes in #269
  • Release v1.6.64 by @roncodes in #271

Full Changelog: v1.6.63...v1.6.64

v1.6.63

Choose a tag to compare

@github-actions github-actions released this 22 Sep 16:11
3e1e14a

v1.6.63 — Driver, customer and contact accounts stay out of the console

Improvements

  • Treat driver, customer and contact users as managed accounts: the FleetOps profile owns them, not IAM. User gains MANAGED_TYPES, isManagedAccount(), isStaffAccount(), canAccessConsole(), canHoldConsoleSession() and a managed() scope.
  • Promote instead of duplicating. When IAM creates or invites a team member whose email or phone belongs to a managed account in the organization, that account becomes a user. It gets the chosen role, permissions and policies and a join invite, and keeps its driver and customer profiles. The response carries promoted_from. Accepting any IAM invite also promotes a managed account and asks it to set a console password.

Improvements for IAM

  • Let IAM admins ask a user to verify their email or phone. POST users/{id}/send-verification sends a one-click link by email or SMS; it lasts 48 hours. The public auth/confirm-contact-verification confirms it without signing in, and refuses the link if the address changed since. users/verify/{id} takes a channel (email by default, or phone). UserFilter adds email_verified, phone_verified, country and timezone for the new IAM columns.

Fixes

  • Keep managed accounts out of the console:
    • auth/login refuses drivers, contacts and customers. Customers keep the customer_login_not_allowed code; drivers and contacts get console_access_not_allowed.
    • Session restore, bootstrap, 2FA verification, verify-email tokens and impersonation refuse drivers and contacts.
    • Customers are still allowed on those endpoints because the customer portal runs inside the console and restores its session through them.
  • Free a deleted user's email and phone so a new account can use them. On soft delete they move to meta.deleted_identity; restoring the user puts them back only if no other account has taken them.

Security

  • Never grant the Administrator role by default. Before this fix:

    • Creating or inviting a user without a role gave them the Administrator role, and so full organization access. Reported for IAM › Customers › Add customer with a blank Role.
    • Accepting an invite with no role also granted it, and joinOrganization ignored the invite's role altogether.

    Now:

    • A role is required when creating or inviting a user; without one the request returns 422.
    • Company::addUser, Company::assignUser and User::assignCompany assign no role unless one is given.
    • An invite without a role joins with no role.
  • Only admins or holders of the Administrator role may grant the Administrator role (403 otherwise), on create, invite and role update.

Reliability

  • Cover the console guards for each account type, identity release and restore, and promotion through create, invite and invite acceptance.

A database migration is not required. No configuration change is needed. The FleetOps side ships in fleetbase/fleetops#338.

Changes: #264, #266, #267.

What's Changed

  • Remove the abandoned CompanyScope global scope by @roncodes in #260
  • Keep driver, customer and contact accounts out of the console by @roncodes in #264
  • Never grant the Administrator role by default by @roncodes in #266
  • Let IAM admins request and record email/phone verification by @roncodes in #267
  • feat(oauth): OAuth/OIDC sign-in and signup by @roncodes in #261
  • Release v1.6.63 by @roncodes in #265

Full Changelog: v1.6.62...v1.6.63

v1.6.62

Choose a tag to compare

@github-actions github-actions released this 16 Sep 11:02
a67d303

v1.6.62 — Custom fields get a public id

Improvements

  • Give every custom field a public id, so an API that hands one out names it the way the rest of the platform names a resource rather than exposing an internal uuid. CustomField takes HasPublicId with the custom_field prefix, and public_id becomes fillable.
  • Mint an id on the one path that would otherwise miss it: HasCustomFields::setCustomField() saves a field it creates on the fly with saveQuietly(), which skips the hook that assigns the id.

Fixes

  • Let an observer's refusal reach the caller on the update and bulk-delete paths. An observer that refused a write by throwing FleetbaseRequestValidationException had its explanation discarded: HasApiModelBehavior::updateRecordFromRequest() rewrapped every exception from the save as a plain \Exception, and HasApiControllerBehavior::bulkDelete() caught \Exception ahead of its dedicated handler, so callers saw Invalid request or a generic update error instead of the message the observer wrote. The exception now passes through untouched on both paths and is rendered with getErrors(), as it already was on create and single delete. Every other exception is wrapped exactly as before. Reported in #256.

Reliability

  • Backfill existing rows in the migration, and add the column as nullable and indexed rather than unique-and-required, so it is safe on an already-populated custom_fields table.
  • Cover id generation for CustomField, and add the column to the in-memory schemas whose saves now probe it for uniqueness.

This is platform-wide: every custom field gains a public id, not only those used by inspections. Nothing reads the new column yet — withCustomFields()'s public projection emits field names and is unchanged — so the change is additive for existing consumers.

A database migration is required. No configuration change is needed.

Changes: #254, #259.

What's Changed

Full Changelog: v1.6.61...v1.6.62

v1.6.61

Choose a tag to compare

@github-actions github-actions released this 09 Sep 05:10
4c763ce

v1.6.61 — Faster IAM user authorization loading

Improvements

  • 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.

Reliability

  • 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.

No database migration or configuration change is required.

Changes: #251, #250, and release-branch CI updates in #252.

What's Changed

  • ci(release): adopt the release/v* branch convention by @roncodes in #250
  • perf(user): let authorization accessors honour eager-loaded relations (12 → 0 queries/row) by @dounisaur in #251
  • Release v1.6.61 by @roncodes in #252
  • Fix v1.6.61 release version and notes by @roncodes in #253

New Contributors

Full Changelog: v1.6.60...v1.6.61

v1.6.60

Choose a tag to compare

@github-actions github-actions released this 31 Aug 09:30
b7691c0

v1.6.60 ~ "Revoked keys stay revoked, transactions stop colliding, webhooks stop signing with the wrong secret"


Highlights

Three independent defects, each of which let the platform keep doing something an operator had already told it to stop.

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.

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.

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.


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.

  • 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?

What's Changed

  • fix(database): stop persistent PDO handles sharing one MySQL transaction by @roncodes in #243
  • fix(webhooks): a queue worker signed lifecycle webhooks with a stale secret by @roncodes in #245
  • fix(auth): api credentials survived revocation and creator removal by @roncodes in #246
  • Bugfix: Utils::apiUrl renders query params as path segments by @roncodes in #248
  • fix: add the report execution statistics columns by @roncodes in #249
  • v1.6.60 by @roncodes in #247

Full Changelog: v1.6.59...v1.6.60

v1.6.59

Choose a tag to compare

@github-actions github-actions released this 19 Aug 03:49
6317e91

v1.6.59 ~ "Verification and credentials email can render again"


Highlights

Both mail templates failed to compile, so every verification and credentials email threw instead of sending. Any flow that delivers a code — customer signup, SMS login with email fallback, password reset, account closure, driver login — returned a 400 carrying a Blade parse error.

Shipped in v1.6.56 and present in v1.6.57 and v1.6.58. Upgrade if you are on any of those.


Bug Fixes

  • verification.blade.php and user-credentials.blade.php did not parse. The greeting read Good Morning@if($user->name), ...@endif, and Blade only treats @ as a directive when the preceding character is not a word character — the rule that keeps foo@bar.com from compiling. So the @if was left as literal text while its @endif compiled anyway, leaving an unmatched endif that broke the enclosing if/elseif/else:

    syntax error, unexpected token "else", expecting end of file
    (View: .../core-api/views/mail/verification.blade.php)
    

    The greeting is now built in one expression, so no directive sits against a word.


Testing

  • Added a test that compiles every Blade view in the package and runs php -l over the result. Nothing caught the original break because no test ever compiled a view — the templates were only exercised through mocked mailers, which never render them.

Upgrade Steps

No migration and no configuration change. If verification emails were failing, they will work again once this is deployed; no codes need reissuing.


Need help?

What's Changed

Full Changelog: v1.6.58...v1.6.59