Skip to content

release: v1.6.69 - #294

Open
roncodes wants to merge 25 commits into
mainfrom
release/v1.6.69
Open

roncodes wants to merge 25 commits into
mainfrom
release/v1.6.69

Conversation

@roncodes

@roncodes roncodes commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Release v1.6.69

Release branch for core-api v1.6.69. Merging into main triggers release.yml, which validates that composer.json and the first line of RELEASE.md name 1.6.69, then pushes the v1.6.69 tag.

This branch collects:

Before merging

- Drop the 'public' visibility from Utils::urlToStorefrontFile: a bucket with
  BucketOwnerEnforced rejects any PUT carrying an ACL, so put() returned false.
- Add File::signStoredUrl()/s3KeyFromUrl(): turn absolute URLs stored as strings
  (legacy unsigned bucket URLs, expired signed URLs) back into a key and re-sign.
- Re-sign template builder image src at render time.
- Cache signed URLs for 60 of their 120 minutes so every URL handed out has at
  least an hour left; cut Extension icon_url cache from 24h to 30m.
Replace the db:backup command, which piped mysqldump through gzip without
pipefail, swallowed upload errors and returned success on a failed dump.
That is how production uploaded 20-byte empty dumps on 2026-09-24/25 (no
mysqldump in the image) and then nothing at all while reporting DONE.

- DatabaseBackupService streams the dump client's stdout into gzip in PHP
  (no shell pipeline), passes the password via MYSQL_PWD, and fails a run
  on a non-zero exit, a missing '-- Dump completed' marker, a dump under
  min_size_bytes, or an uploaded object whose size differs from the file.
- Uploads go to any filesystem disk (bucket override for s3, key prefix);
  retention by age and/or count runs only after a fully successful run and
  always keeps each database's newest backup.
- Every attempt is recorded in database_backups (status, size, duration,
  error, trigger); failures can email configured addresses.
- Settings live in system.database-backups (env defaults in
  config/database-backups.php) and drive the schedule; disabled by default.
- Admin endpoints under int/v1/database-backups: settings get/save/reset,
  recent runs, and a queued 'run now'.
- db:backup exits non-zero on any failure; --force runs while disabled.
Add VerificationCode::issue(), check() and attemptsLeft() for flows where a
leaked table must not give away live codes:

- issue() stores an HMAC of the code, keyed by the app key, and hands the
  plain code back once on the instance (plainCode), defaulting to a 10-minute
  expiry and an 'active' status.
- check() compares with hash_equals, counts wrong attempts in meta and locks
  the code on the last allowed one. Expired and locked codes report as such.
- The creating hook keeps a code that was already set, so issue() is not
  overwritten; codes made the old way still get a random one and still check.

Existing generators and their callers are unchanged. First user: the FleetOps
public tracking page's one-time codes.
Adds the realtime channel authentication core, switched on by
SOCKETCLUSTER_AUTH_KEY (config auth_key, publish_url, token_ttl):

- SocketToken mints and verifies HS256 socket tokens (iss/aud/iat/nbf/exp/jti
  plus kind, sub, cid, cpid, env, ids, adm, scp, sid); anything not HS256 with
  the configured key, or with a wrong issuer/audience or out-of-range time
  claims, is rejected. Includes the scoped public tracking token.
- SocketSignature derives per-purpose HMAC keys and signs/verifies the
  timestamped requests exchanged with the socket server.
- SocketPrincipal, ChannelDecision, the SocketChannelResolver contract and the
  SocketChannelRegistry extensions register their channel prefixes with.
- ChannelAuthorizer applies install, expiry, scope, system and self rules,
  then the prefix resolver, caching decisions per token and channel.
- Core resolvers for company, api, user, test, install/uninstall, chat,
  chat_channel, chat_participant, chat_message and file channels.
- The registry and authorizer are container singletons.
- POST int/v1/socket/token (console session): a user token for the current
  company, in the sandbox environment when the console is in sandbox mode.
- POST v1/socket/token (public API): an api token for an API credential; for a
  Sanctum user token, the principal a registered resolver claims, else a user
  token.
- POST int/v1/socket/authorize: called by the socket server only, admitted by
  its signature (VerifySocketSignature) rather than a session; re-verifies the
  token and returns {allow, ttl, reason}. Exempt from the configured-instance
  check so an install page can follow the install channel before setup ends.
- Every mint route answers 404 while SOCKETCLUSTER_AUTH_KEY is unset.
When SOCKETCLUSTER_AUTH_KEY is set, the broadcaster and
SocketClusterService::publish()/send() post every channel of a broadcast in a
single request to {SOCKETCLUSTER_PUBLISH_URL}/publish, signed with the derived
publish key and short timeouts. Without the key the websocket publisher is
used as before.

Channels ending in "." (an empty suffix, e.g. a session read in a queue
worker) and names the socket server would reject are dropped before
publishing.
… channel

The SocketCluster settings test ignored nothing: any admin could publish an
arbitrary payload to any channel. It now always publishes to
test.{current user uuid} and returns that channel so the console can
subscribe to it.
POST v1/socket/system-token in the fleetbase.platform-api group mints a
system token. A separate path because the platform and public API groups
share the v1 prefix, where v1/socket/token is the public API mint route.
…annel decision

The install-channel test skipped the users table but still seeded it. The
cross-company and driver tests now compare every channel's decision reason
(and any resolver error logged) in one assertion.
… model

ChatMessage always eager loads its attachments, so authorizing a chat_message
channel queried chat_attachments for nothing (and failed where that table is
absent). Channel lookups now load only the model's own row.
getCountryCodeByCurrency() and getCountryCodeByName() rebuilt the full
countries dataset (a 4.7 MB JSON file plus flag hydration) on every call.
Storefront serializes a country per store, so listing stores paid that cost
once per record and network store lists could take minutes.

The name/ISO2/currency rows are now built once, kept in the application
cache and memoized per process. Cache failures fall back to building the
lookup, and flushCountryLookup() resets it.
Store media, product images and proofs of delivery are looked up by
subject_uuid, which had no index, so each lookup scanned the whole files
table.
fix(files): support a fully private S3 media bucket
feat(backups): settings-driven database backups that fail loudly
feat(verification): hashed one-time codes with attempt counting
perf: cache the country lookup and index files.subject_uuid
Socket authentication was on as soon as SOCKETCLUSTER_AUTH_KEY was set. That
also moved every broadcast to the socket server's HTTP publish endpoint, so
provisioning the key before every client fetched tokens (or before the new
socket server was deployed) would break existing socket clients.

- New `broadcasting.connections.socketcluster.auth_enabled`
  (SOCKETCLUSTER_AUTH_ENABLED, default false). SocketToken::enabled() now
  needs the switch and a valid key. Every gated path follows: token routes,
  the authorize endpoint and its signature check, and HTTP publishing.
- With the switch off, the console's socket test publishes to the requested
  channel (default `test`) as before. With it on, only to `test.{user}`.
- README: the switch and the rollout order (ship clients that fall back on
  404, switch on with the socket server in log mode, then enforce).
RunDatabaseBackup::dispatch() comes from the Dispatchable trait. Several test
files define an empty Illuminate\Foundation\Bus\Dispatchable shim, so when one
of them loads first the static dispatch() is missing and DatabaseBackupsTest
fails depending on test order (PHP CI on release/v1.6.69 fails this way).
Dispatching through the bus contract behaves the same at runtime and does not
depend on the trait.
fix(backups): queue the manual backup through the bus dispatcher
…er handshake

The PHP publisher connected without an Origin header, which socketcluster-server
treats as '*'. With origins restricted (scripts/docker-install.sh restricts them
to the console host), every server broadcast was refused with 'Invalid origin: *'.
SOCKETCLUSTER_ORIGIN now sets the header through phrity/websocket's headers
option. Reported in #290.
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (ab33100) to head (0a08e12).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##                main      #294    +/-   ##
============================================
  Coverage     100.00%   100.00%            
- Complexity      7931      8269   +338     
============================================
  Files            438       453    +15     
  Lines          25665     26441   +776     
============================================
+ Hits           25665     26441   +776     
Flag Coverage Δ
backend 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

feat(socket-auth): authenticated realtime channels (tokens, authorizer, signed publish)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant