Repository navigation
Conversation
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.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #290 +/- ##
============================================
Coverage 100.00% 100.00%
- Complexity 7931 8137 +206
============================================
Files 438 448 +10
Lines 25665 26129 +464
============================================
+ Hits 25665 26129 +464
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Related to the websocket publish path this keeps when phrity/websocket already sends // config/broadcasting.connections.php, socketcluster options
'headers' => array_filter(['Origin' => env('SOCKETCLUSTER_ORIGIN')]),with the installer writing |
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).
…t order Socket authentication is now off until SOCKETCLUSTER_AUTH_ENABLED=true on the API containers and the socket server (fleetbase/core-api#290). System Setup → Socket: env table entry, what the switch gates (token routes, authorize endpoint, signed publishing, socket server mode), enforce requiring the switch on the API, installer defaults (switch off, log), the rollout order, and two troubleshooting entries. Migration guide and Socket Events: the switch and the self-hosted rollout, so self-hosters set it.
What
Authenticated realtime channels for the API side. Today any socket client can subscribe to any channel and publish to it. With
SOCKETCLUSTER_AUTH_KEYset, clients present a short-lived token, the socket server asks the API whether that token may follow a channel, and the API publishes through a signed internal endpoint. Without the key nothing changes.Tokens and authorization (
Fleetbase\Support\SocketCluster)SocketToken:issue(SocketPrincipal, ?int $ttl),verify(string): ?SocketPrincipal,system(),enabled(), plusforTracking()/trackingId()for scoped public tracking tokens. HS256 vialcobucci/jwt. Verification rejects any algorithm other than HS256 (includingnoneand RS256), a wrong key, issuer or audience, and missing or out-of-rangeiat/nbf/exp. Lifetimes:SOCKETCLUSTER_TOKEN_TTL(default 900 s), tracking 1800 s, system 300 s, capped at 3600 s.SocketPrincipal: an immutable value object (forUser,forApiCredential,system,fromClaims/toClaims).SocketChannelRegistry(singleton):register,registerModel,registerPrincipalResolver,resolve. Extensions use it to register their channel prefixes.ChannelAuthorizer(singleton) checks in this order:fleetbase.install, and only until setup has created a user.scpmay follow exactly the listed channels.Decisions are cached per token id and channel.
Core resolvers:
company,api(credential, or a personal access token owned by a company user),user,test,install/uninstall,chat,chat_channel,chat_participant,chat_messageandfile. Users and API credentials see their own company's records. Drivers and customers only see chats they take part in.SocketSignature: derives a key for each purpose (HMAC(auth key, "fleetbase-socket:{purpose}")). Requests are signed over{timestamp}.{body}and rejected when the timestamp is more than 60 s off or the signature does not match (compared in constant time).Endpoints
POST int/v1/socket/token(fleetbase.protected) returns a user token for the current company. When the console is in sandbox mode, the token is for the sandbox environment.POST v1/socket/token(fleetbase.api) returns an api token for an API credential. For a Sanctum user token it returns the principal a registered resolver claims (FleetOps: driver), or else a user token.POST v1/socket/system-token(fleetbase.platform-api) returns a system token, valid for 300 s. It has its own path because the platform and public API groups share thev1prefix.POST int/v1/socket/authorizeis called by the socket server only. It accepts only requests signed with the socket server's authorize key, takes no session or user token, verifies the token again, and returns{allow, ttl, reason}. It is exempt from the configured-instance check so the install page can followfleetbase.installbefore setup finishes.SOCKETCLUSTER_AUTH_KEYis unset (or shorter than 32 bytes). Clients treat a 404 as "connect anonymously".Publishing
When the key is set, a broadcast becomes one signed
POST {SOCKETCLUSTER_PUBLISH_URL}/publishcarrying all of its channels, with short timeouts.SocketClusterService::publish()keeps working and takes the same path. Without the key, the websocket publisher is used as before. Channels ending in.(for examplecompany.from a session read in a queue worker) and names the socket server would reject are dropped first.Hardening
The admin SocketCluster test (
settings/test-socketcluster-config) used to accept any channel from the request. It now always publishes totest.{current user uuid}, ignores the channel input, and returns the channel it used so the console can subscribe to it.Config
broadcasting.connections.socketclustergainsauth_key(SOCKETCLUSTER_AUTH_KEY),publish_url(SOCKETCLUSTER_PUBLISH_URL, defaulthttp://{SOCKETCLUSTER_HOST}:8001) andtoken_ttl(SOCKETCLUSTER_TOKEN_TTL, default 900). The README has a short section on the feature for extension authors.Why
Realtime channels carry company data (orders, drivers, chats, users), and today nothing stops a client from subscribing to another company's channels. This gives the socket server something to enforce. It is off by default, so it can be rolled out gradually:
off, thenlog, thenenforceon the socket server.Test plan
Verified by CI. New Pest tests cover:
alg: none, RS256, missing or wrongaud/issHttp::fake), and the websocket fallback without a keyRelated PRs
Part of the authenticated realtime channels rollout (socket auth), one PR per repo:
fleetbase/core-api ^1.6.69)fleetbase/fleetbase-socketserver, compose/helm/installer, console socket test pageAuth switch (
SOCKETCLUSTER_AUTH_ENABLED)Socket auth used to turn on as soon as
SOCKETCLUSTER_AUTH_KEYwas set. Setting the key also moved every broadcast to the socket server's HTTP publish endpoint. That makes it hard to ship this before every socket client (released mobile apps, Navigator, integrations) fetches tokens.broadcasting.connections.socketcluster.auth_enabled(SOCKETCLUSTER_AUTH_ENABLED), defaultfalse.SocketToken::enabled()now requires the switch and a valid key. Every gated path follows it, including fleetbase/storefront's and fleetbase/fleetops's socket code:logmode.enforce.UtilsTestreadingcomposer.lockmetadata, also fails without this change).