Repository navigation
v1.6.69
v1.6.69 — SMS send guard, authenticated realtime channels, API cache invalidation, comment visibility and database backups
Added
- SMS send guard. Every SMS passes
Fleetbase\Support\Sms\SmsGuardbefore a provider is called, and before a verification code row is written: destination validation with libphonenumber (mobile numbers only; premium-rate, shared-cost, satellite and blocked ranges are refused), a per-destination cooldown and caps, a per-IP layer, store/network and company budgets (200 a day per company by default, 20 for companies younger than 7 days), a platform-wide bucket, and a circuit breaker that pauses a tenant and alerts the system administrators when it uses up its daily budget, hits its hourly budget twice in a day, texts a burst of distinct new numbers, texts a country it has never texted, or issues codes that nobody verifies. Trusted purposes (2FA, admin sends) skip tenant pauses and budgets so staff can still sign in. Refusals areSmsBlockedException, rendered in the usual error envelope with 422 (number can never be texted), 429 withRetry-After(cooldown or budget), 423 (paused) or 503 (counters unreachable). Limits, mode, pauses and alert recipients live in thesystem.sms-guardsetting;config/sms.phpsupplies the defaults, andsms:mode,sms:pauseandsms:unpausechange them without a deployment. (#305, #307) - SMS send ledger. Every allowed send is reserved in
sms_sendsand settled with the provider outcome, and every refusal is written with its reason, with tenant, purpose, source, client address, destination country and type.sms:ledger:prunemasks numbers after 30 days and deletes rows after 90. Writes are best effort: a missing table never fails a send. (#307) - SMS guard admin API under
int/v1/sms-guard: settings (mode, budgets, blocklists, alert recipients, per-company exceptions), reset, pauses (create and lift), usage per organization against its budget, and the ledger with filters. The console page ships in fleetbase/fleetbase v0.7.69. (#307) SmsServiceChannel, a notification channel that delivers throughSmsService, so notification texts are guarded and ledgered too. (#307)- Verification code tracing and single-use hashed codes.
verification_codesgainscompany_uuid,identity,channel,ip_address,user_agent,sms_send_uuidandconsumed_at.generateSmsVerificationFor(..., ['hashed' => true])stores an HMAC and hands the message callback the plain code;findActiveFor()andfindActiveForSubject()return the newest live code,check()verifies it andconsume()uses it up.VerificationCode::issue()stores an HMAC and returns the plain code once, andcheck()counts attempts and reportsvalid,invalid,expiredorlocked. The existing generators are unchanged. (#289, #307) - Authenticated realtime channels. Socket tokens, a channel authorizer with per-resource resolvers, and signed HTTP publish.
POST int/v1/socket/tokenmints a user token,POST v1/socket/system-tokenserves platform API callers, andPOST int/v1/socket/authorizeis called only by the socket server, with signed requests. Extensions register channel resolvers for their own resources. It is off by default and stays off untilSOCKETCLUSTER_AUTH_ENABLED=true, even with a key set, so existing socket clients (mobile apps, the console, integrations) keep working. The publisher now sends a configurableOrigin(SOCKETCLUSTER_ORIGIN) on its handshake, so a socket server with restricted origins accepts server broadcasts. (#290) - Comment visibility and
comment.created. Comments carrymeta.visibility(internalorcustomer), accepted asvisibilityon create. A reply takes its parent's visibility; a comment on an order isinternalfrom the console andcustomerfrom anywhere else; comments on other subjects keep no visibility and stay visible. The public v1 comments API excludes internal comments, and the comment resource gains a top-levelvisibility. A newcomment.createdbroadcast goes tocompany.{uuid}for every comment, and customer-visible comments also go to the subject's own channels (order.order_x). (#306) - Chat participant roles and system messages.
chat_participantsandchat_messagesgain ametacolumn; participants exposerole(customer,driver,store,system) and messages exposesystemandmeta(for examplekind: order_updated). (#306) - API cache dependencies. Cache keys carry the versions of every table they embed, so a write to a related table invalidates the lists, records and relationships that embed it. Dependencies come from
protected $apiCacheDependencieson the model, the request'swith,expandand counts, and the lookups' own paths.withoutInvalidating()scopes coalesced high-frequency writes, andAPI_CACHE_STRICT_DEPENDENCIESmakes a bad declaration throw in test suites. (#301) - Database backups. Settings-driven backups (
db:backup) on a configurable schedule, with environment defaults inconfig/database-backups.php(DB_BACKUP_*) and an admin API underint/v1/database-backups. Failures email the configured addresses and are logged. (#288, #295, #296) LogApiRequest::RELATED_ATTRIBUTE(api_request_log_related): an extension sets it on the request and the ids land in the log row'srelatedcolumn, so a storefront request traces back to its store or network key. (#302)
Changed
- API cache invalidation. Invalidation now changes the keys of every cache: lists moved on the first write only after a second write, and single records and relationships relied on a tag flush that leaves values behind on phpredis 6.1 and later. Versioned keys are the invalidation mechanism, list keys cover the request path and the session scope (
api.cache.scope.session), writes inside a transaction bump again on commit, and an invalidation failure never fails the write that triggered it. On deploy every key changes once. (#299, #300, #301) - API request logs. Any route that attaches
LogApiRequestsis logged, not only routes under the corev1/prefix, so storefront API writes are traceable. Internal routes are still skipped. (#302) without[]usesmakeHidden(), so it no longer un-hides columns the model hides. Requested relation names come back as declared, sosubfleetsloads assubFleetsand renders.withCounts()accepts an array. (#303)- Private media buckets. Stored file URLs that point into the configured
s3bucket are signed again on read, so the bucket can be fully private. (#287) - Faster lookups. The country lookup is cached, and
files.subject_uuidis indexed. (#291) - The admin SocketCluster test always publishes to
test.{current user uuid}and returns the channel it used. (#290) - The manual database backup is dispatched through the bus, and
DB_BACKUP_BUCKETno longer has a hard-coded default: with no bucket set, thes3disk's own bucket is used. (#295, #296)
Security
- Requested relations must be relations.
with,expand,with_countandcountreached Eloquent unchecked, so a request could run any public method with no required arguments on a model (with[]=dispatchdispatched a blank order). A name is now accepted only when the method declares an EloquentRelationreturn type (plain or union), is anExpandableclosure that does, or is listed in the model'sprotected $withable. Nested paths are checked segment by segment and cut at the first that fails; rejected names are dropped without an error. (#303) - Only registered notifiables are resolved from saved notification settings. A stored
dynamic:name or class that no extension registered throughNotificationRegistry::registerNotifiable()is skipped instead of being called or built. (#304) - Deletes are scoped to the company.
deleteRecordcompared a qualified column name against the table's columns and never matched, so the company constraint was skipped. Reports are filtered by company through the newReportFilter. - A failed comment publish no longer leaves every model unguarded in a long-lived Octane worker. (#298)
- Order lifecycle events reach
driverAssigned.{driver uuid}again; they went to a deaddriverAssigned.channel. (#306)
Removed
MysqlS3Backup,S3BackupTrimmerandconfig/laravel-mysql-s3-backup.php, replaced by the new database backups. (#288)- The unrouted legacy Twilio-direct SMS login methods on
AuthController. (#307)
Upgrade Steps
- Run migrations:
database_backups(#288), thefiles.subject_uuidindex (#291),metaonchat_participantsandchat_messages(#306),sms_sendsand theverification_codestracing columns (#307). Existing comments and codes are untouched. - The SMS guard is on (
SMS_GUARD_ENABLED, defaulttrue) inenforcemode (SMS_GUARD_MODE). To watch before refusing, deploy withSMS_GUARD_MODE=monitoror runphp artisan sms:mode monitor, review thesms.would_blocklog entries, thenphp artisan sms:mode enforce. The counters must live in a store shared by every worker (Redis in production);SMS_GUARD_CACHE_STOREpins one. SetSMS_GUARD_ALERT_EMAILSfor alerts beyond the system administrators, andSMS_GUARD_BLOCKED_COUNTRIES/SMS_GUARD_BLOCKED_PREFIXESas needed. Schedulephp artisan sms:ledger:prunedaily (SMS_GUARD_LEDGER_RETENTION_DAYS,SMS_GUARD_LEDGER_MASK_AFTER_DAYS). - Socket auth (#290) adds
SOCKETCLUSTER_AUTH_ENABLED(defaultfalse),SOCKETCLUSTER_AUTH_KEY,SOCKETCLUSTER_PUBLISH_URL(defaulthttp://{SOCKETCLUSTER_HOST}:8001),SOCKETCLUSTER_TOKEN_TTL(default 900) andSOCKETCLUSTER_ORIGINtobroadcasting.connections.socketcluster. SetSOCKETCLUSTER_ORIGINto the console origin when the socket server restricts origins. Nothing changes for socket clients untilSOCKETCLUSTER_AUTH_ENABLED=true. Roll out in this order:- Ship clients that fall back to connecting without a token when the token route answers 404.
- Set
SOCKETCLUSTER_AUTH_ENABLED=trueon the API and the socket server, with the socket server inlogmode. - Switch the socket server to
enforce.
- Extensions: a relation that clients request through
with,expandor counts must declare an EloquentRelationreturn type or be listed in$withable, or it stops loading (#303). Everydynamic:notifiable an extension resolves must be passed toregisterNotifiable()(#304). Models whose cached queries eager-load other tables should declare$apiCacheDependencies, and writes that skip Eloquent events need an explicit invalidation (#301). fleetbase/fleetops v0.6.72 and fleetbase/storefront v0.4.25 carry these and require this release (fleetbase/core-api ^1.6.69). - Public API clients:
GET v1/commentsandGET v1/comments/{id}no longer return internal comments,POST v1/commentsacceptsvisibility, and comment, chat participant and chat message responses gain fields (#306). - To make the media bucket private, remove any public
s3:GetObjectstatement from the bucket policy and turn on Block Public Access (#287). - Database backups replace the old S3 backup settings; configure them with
DB_BACKUP_*or in the admin settings (#288).
What's Changed
- fix(files): support a fully private S3 media bucket by @roncodes in #287
- feat(backups): settings-driven database backups that fail loudly by @roncodes in #288
- feat(verification): hashed one-time codes with attempt counting by @roncodes in #289
- perf: cache the country lookup and index files.subject_uuid by @roncodes in #291
- fix(backups): queue the manual backup through the bus dispatcher by @roncodes in #295
- feat(socket-auth): authenticated realtime channels (tokens, authorizer, signed publish) by @roncodes in #290
- fix: no default backup bucket; example bucket names in tests by @roncodes in #296
- fix(comments): a failed comment publish no longer leaves every model unguarded by @roncodes in #298
- fix(api-cache): invalidation changes list, model and relationship cache keys by @roncodes in #299
- fix(api-cache): list cache keys cover the request path and session scope by @roncodes in #300
- feat(api-cache): writes invalidate every cache that embeds the written table by @roncodes in #301
- fix(api-logs): log every route that opts in to LogApiRequests and let extensions relate ids by @roncodes in #302
- fix(api): only real relations can be requested through with, expand and counts by @roncodes in #303
- fix(notifications): resolve only registered notifiables from saved notification settings by @roncodes in #304
- feat(sms): guard every SMS send with destination validation, budgets and a circuit breaker by @roncodes in #305
- Fix driver uuid broadcast channel; comment visibility and comment.created; chat participant roles and system message meta by @roncodes in #306
- feat(sms): ledger, hashed single-use codes, learned breaker signals and the admin API by @roncodes in #307
- test(auth): drop the authenticateSmsCode tests the method's removal left behind by @roncodes in #308
- docs(release): v1.6.69 notes cover everything merged since #291 by @roncodes in #309
- release: v1.6.69 by @roncodes in #294
Full Changelog: v1.6.68...v1.6.69