diff --git a/content/_index.md b/content/_index.md index dcf5316..5c930ec 100644 --- a/content/_index.md +++ b/content/_index.md @@ -5,7 +5,7 @@ hero_tagline: "Liberate your location data" hero_title: "Open source location infrastructure that is free to use, adapt, and white-label" hero_text: "Open Location Stack gives RTLS vendors, integrators, and solution providers a permissively licensed MIT foundation for interoperable location products. Open Location Hub is the centerpiece: an open hub for connecting mixed RTLS deployments and surfacing their location flows on one operational map." hero_aside_title: "Commercially usable by design" -hero_aside_text: "The stack is intended to be free to use, adaptable to existing delivery models, and practical for embedded, branded, and customer-specific RTLS solutions." +hero_aside_text: "The stack is free to use, adaptable to existing delivery models, and practical for embedded, branded, and customer-specific RTLS solutions." hero_highlights: - "Permissive MIT licensing" - "Business-friendly white labeling" @@ -19,16 +19,16 @@ opensource_points: description: "Start from open hub, connector, and mapping building blocks instead of rebuilding the same infrastructure for every deployment." - title: "Credible shared foundation" description: "An open codebase makes it easier to evaluate, adapt, extend, and integrate the platform across mixed RTLS estates." -opensource_license_note: "Open Location Stack is intended to be released under the permissive [MIT License](https://opensource.org/license/mit), making it practical to embed, adapt, extend, and white-label while still building on a shared ecosystem." +opensource_license_note: "Open Location Stack is released under the permissive [MIT License](https://opensource.org/license/mit), making it practical to embed, adapt, extend, and white-label while still building on a shared ecosystem." opensource_repos: - title: "Floor Plan Editor" link: "/floor-plan-editor/" github_link: "https://github.com/Open-Location-Stack/floorplan-editor" - description: "Early access editor for creating and maintaining indoor floor plans and IMDF-based venue data. [Product page](/floor-plan-editor/) · [GitHub](https://github.com/Open-Location-Stack/floorplan-editor)" + description: "Open editor for creating and maintaining indoor floor plans and IMDF-based venue data. [Product page](/floor-plan-editor/) · [GitHub](https://github.com/Open-Location-Stack/floorplan-editor)" - title: "Open Location Hub" link: "/open-location-hub/" github_link: "https://github.com/Open-Location-Stack/open-location-hub" - description: "Early access interoperability hub for OMLOX-compatible position and event exchange. [Product page](/open-location-hub/) · [GitHub](https://github.com/Open-Location-Stack/open-location-hub)" + description: "Open interoperability hub for OMLOX-compatible position and event exchange. [Product page](/open-location-hub/) · [GitHub](https://github.com/Open-Location-Stack/open-location-hub)" mission_title: "Mission" mission_text: "Open Location Stack is an open source initiative focused on the missing shared layer between RTLS hardware, hubs, indoor maps, and downstream applications. Real-world deployments still rebuild the same venue models, connectors, hub adapters, and operational map capabilities vendor by vendor. We are building shared components so vendors and integrators can spend more effort on customer-facing products and less on proprietary plumbing. The project is initiated by [FORMATION](https://tryformation.com), where we build production RTLS solutions and encounter these integration gaps directly." problem_title: "Why Open Location Stack is needed" @@ -46,11 +46,11 @@ hub_points: - title: "Reduce lock-in" description: "An open hub creates a cleaner way to work across vendor-specific deployments, custom APIs, and hybrid infrastructure." - title: "Connect mixed estates" - description: "The hub is intended to aggregate location flows from different plants, providers, and technologies instead of forcing a single-vendor stack." + description: "The hub aggregates location flows from different plants, providers, and technologies instead of forcing a single-vendor stack." - title: "Support product and integration teams" description: "Vendors, integrators, and enterprise teams get a reusable foundation for auth, event exchange, interoperability, and operational map integration." connectors_title: "Connectors turn existing RTLS deployments into an open ecosystem" -connectors_intro: "Open Location Hub is intended to support a wide range of connectors so existing RTLS deployments and location technologies can participate without a rip-and-replace project." +connectors_intro: "Open Location Hub supports a wide range of connectors so existing RTLS deployments and location technologies can participate without a rip-and-replace project." connectors_points: - title: "Connect existing hubs" description: "Plug in on-premise or cloud hubs such as Corriva Hub, Deephub, or other existing deployments where location data is already flowing." @@ -72,12 +72,12 @@ mapping_points: - title: "Reusable map capabilities" description: "Semantic hierarchy and the location graph provide the foundation for reusable authoring, validation, rendering, and map maintenance instead of repeated project-specific work." ai_readiness_title: "AI readiness for agentic logistics and manufacturing" -ai_readiness_intro: "Agentic logistics and manufacturing requires a fully integrated and accessible view of the workplace. Open Location Hub is intended to enable that by bringing together map context, live location flows, system events, and connector-based interoperability in one unified operational view." +ai_readiness_intro: "Agentic logistics and manufacturing requires a fully integrated and accessible view of the workplace. Open Location Hub enables that by bringing together map context, live location flows, system events, and connector-based interoperability in one unified operational view." ai_readiness_points: - title: "Accessible operational context" description: "AI systems need a usable picture of facilities, fleets, flows, and constraints rather than disconnected location feeds." - title: "Integrated workplace visibility" - description: "Open Location Hub is intended to connect workplace systems, indoor maps, and mixed tracking technologies so automation has one unified operational view to act on." + description: "Open Location Hub connects workplace systems, indoor maps, and mixed tracking technologies so automation has one unified operational view to act on." - title: "Foundation for agentic workflows" description: "A shared hub, semantic map structure, and connector ecosystem create a better base for orchestration, monitoring, and AI-assisted decision making." roadmap: @@ -128,7 +128,7 @@ component_groups: description: "Reusable integrations for RTLS hubs, hardware vendors, software systems, AGV environments, and enterprise workflows so each deployment does less bespoke integration work." - title: "MQTT Support" description: "Built-in [MQTT](https://mqtt.org/mqtt-specification/) connectivity for ingesting and distributing real-time updates around the operational map." -components_note: "These components are available as early access versions and are not yet feature complete. Please try them out, share feedback, and help spread the word. Pull requests and issues are very welcome." +components_note: "These components are being built in the open. Explore them, share feedback, and help spread the word. Pull requests and issues are very welcome." github_org_link: "https://github.com/Open-Location-Stack" audience_title: "Opening up the location ecosystem" audience_intro: "Open Location Stack aims to lower the cost of interoperability and raise the quality of map-aware RTLS products across the ecosystem by giving the market an open hub, open map foundation, and reusable connector model." diff --git a/content/floor-plan-editor.md b/content/floor-plan-editor.md index 9aaa6d7..dd2f70f 100644 --- a/content/floor-plan-editor.md +++ b/content/floor-plan-editor.md @@ -23,7 +23,7 @@ It is part of Open Location Stack's mapping foundation and focuses on the practi Most RTLS and indoor-location projects still treat map authoring as a side problem, which usually leads to ad hoc tooling, brittle import flows, and inconsistent map quality. -Floor Plan Editor is intended to reduce that friction by providing a reusable foundation for indoor map creation and maintenance. Instead of rebuilding a one-off editor for every deployment, teams can start with a focused tool that already understands the shape of the problem. +Floor Plan Editor reduces that friction by providing a reusable foundation for indoor map creation and maintenance. Instead of rebuilding a one-off editor for every deployment, teams can start with a focused tool that already understands the shape of the problem. Just as importantly, it shifts teams away from bitmap-first indoor mapping. Static image floor plans may be workable for small demos, but they do not scale well to industrial campuses, terminals, hospitals, and factories that span tens or hundreds of thousands of square meters. Vector map data stays crisp at every zoom level, is easier to validate and edit, and provides the structure needed for downstream applications instead of just a picture of a building. @@ -62,11 +62,9 @@ Floor Plan Editor gives Open Location Stack a practical authoring front end for For integrators, that means less time spent stitching together fragile map tooling. For product teams, it means a cleaner starting point for map-aware workflows such as wayfinding, zoning, asset visualization, and operational UX. -## Early access +## Get involved -The project is still evolving quickly, and compatibility-breaking changes may still happen while the data model and workflows are being refined. - -That said, this is exactly the stage where outside feedback is useful. Try it, stress it, file issues, and send pull requests if you want to help shape the editor into a stronger shared foundation. +Try it, stress it, file issues, and send pull requests if you want to help shape the editor into a stronger shared foundation. [Learn about Open Location Hub](/open-location-hub/) diff --git a/content/open-location-hub.md b/content/open-location-hub/_index.md similarity index 72% rename from content/open-location-hub.md rename to content/open-location-hub/_index.md index e1c3e1d..14cce52 100644 --- a/content/open-location-hub.md +++ b/content/open-location-hub/_index.md @@ -11,13 +11,15 @@ Open Location Hub is an OpenAPI-first hub implementation for interoperable RTLS It is the integration-layer counterpart to Open Location Stack's mapping work: once indoor spaces are modeled clearly, for example with the [Floor Plan Editor](/floor-plan-editor/), the hub becomes the place where live location data, events, security, connectors, and system-to-system interoperability come together. -The current repository is still evolving quickly, but the intended direction is already clear: a production-grade, open source hub that teams can run, extend, adapt, and integrate without being locked into vendor-specific middleware. +The project is moving toward a production-grade, open source hub that teams can run, extend, adapt, and integrate without being locked into vendor-specific middleware. + +The software documentation for the current implementation is published at [Open Location Hub Docs](/open-location-hub/docs/). That section is generated from the repository's `docs/` directory and is intended to stay aligned with the code as the project evolves. ## Business value -Open Location Hub is intended to give vendors, integrators, and enterprise teams an open integration backbone they can actually use in commercial delivery models. +Open Location Hub gives vendors, integrators, and enterprise teams an open integration backbone they can actually use in commercial delivery models. -Because the project is part of Open Location Stack's MIT-licensed foundation, it is meant to be free to use, adapt, extend, embed, and white-label. That matters for companies that need a shared technical base without giving up their own service model, product packaging, deployment approach, or customer-specific integration layer. +Because the project is part of Open Location Stack's MIT-licensed foundation, it is free to use, adapt, extend, embed, and white-label. That matters for companies that need a shared technical base without giving up their own service model, product packaging, deployment approach, or customer-specific integration layer. The point is not to replace all commercial offerings with a single open product. The point is to reduce repeated work in the non-differentiating layers so more effort can go into customer value, operational UX, deployment quality, and reusable partner ecosystems. @@ -27,13 +29,13 @@ Many RTLS deployments end up depending on proprietary hub behavior, custom APIs, Open Location Hub exists to give the ecosystem a transparent, standards-oriented foundation. The goal is not just to expose an API, but to provide a credible base for secure, testable, deployable hub infrastructure that can participate in real deployments. -It is also intended to be cloud-native, horizontally scalable, and lightweight enough to run in very different environments. You should be able to run it on premises close to local devices, in a regional cloud deployment, or as part of a larger multi-region setup that aggregates multiple sites and multiple continents. The federation work in the repository is explicitly aimed at supporting that progression from small local setups to larger distributed deployments. +It is cloud-native, horizontally scalable, and lightweight enough to run in very different environments. Teams can run it on premises close to local devices, in a regional cloud deployment, or as part of a larger multi-region setup that aggregates multiple sites and multiple continents. The federation work in the repository supports that progression from small local setups to larger distributed deployments. ## Connectors for existing RTLS estates -Open Location Hub is not intended as a rip-and-replace story. In many cases, the practical requirement is to work with the hubs and deployments that already exist across plants, warehouses, terminals, and production environments. +Open Location Hub is not a rip-and-replace story. In many cases, the practical requirement is to work with the hubs and deployments that already exist across plants, warehouses, terminals, and production environments. -That is why connectors are an important part of the product direction. The hub is intended to support integration with existing on-premise and cloud-based hubs such as Corriva Hub, Deephub, and other RTLS deployments already used for AGVs, fork lift trucks, and site-specific operational systems. +That is why connectors are an important part of the product direction. The hub supports integration with existing on-premise and cloud-based hubs such as Corriva Hub, Deephub, and other RTLS deployments already used for AGVs, fork lift trucks, and site-specific operational systems. The business value is straightforward: instead of asking every customer to standardize on one vendor stack immediately, Open Location Hub can become the open layer that aggregates, normalizes, and routes location flows from multiple sources. That gives integrators and solution providers a practical path to interoperability while preserving existing investments. @@ -53,7 +55,7 @@ For RTLS vendors and location solution providers, this also creates a partner op ## Technology overview -The hub is written in Go, which is a strong fit for the intended deployment model: low footprint, high performance, simple operations, and a clean path to containerized or cloud-hosted runtime environments. The current architecture separates durable storage, transient state, protocol surfaces, and shared hub logic so the same core behavior can support multiple transports and deployment shapes. +The hub is written in Go, which is a strong fit for the deployment model: low footprint, high performance, simple operations, and a clean path to containerized or cloud-hosted runtime environments. The current architecture separates durable storage, transient state, protocol surfaces, and shared hub logic so the same core behavior can support multiple transports and deployment shapes. At the API layer, the repository already shows a broad protocol surface: @@ -66,7 +68,7 @@ That protocol mix matters because real RTLS deployments rarely have just one int ## Security and control plane -The authentication model is standards-based JWT bearer auth with support for `oidc`, `static`, and `hybrid` verification modes. In OIDC mode, the hub discovers provider metadata and JWKS automatically, caches verifier state, and validates standard JWT claims. The repository includes a Dex setup for development, but the same model is intended to work with production OIDC providers such as Keycloak and other enterprise identity systems. +The authentication model is standards-based JWT bearer auth with support for `oidc`, `static`, and `hybrid` verification modes. In OIDC mode, the hub discovers provider metadata and JWKS automatically, caches verifier state, and validates standard JWT claims. The repository includes a Dex setup for development, and the same model works with production OIDC providers such as Keycloak and other enterprise identity systems. Authorization is more than simple bearer validation. The hub already includes role-based and ownership-aware authorization, route-level permissions, RPC-specific method permissions, and separate WebSocket topic permissions. That makes it possible to use the hub as the policy boundary between user-facing applications and lower-level RTLS infrastructure. @@ -84,16 +86,16 @@ In practical terms, the open location hub can sit above local hubs, beside exist ## Product pitch -Open Location Hub is meant to be the open integration backbone of Open Location Stack: a hub that can sit between RTLS infrastructure, applications, enterprise systems, and federated site deployments while staying aligned with omlox-style interoperability. +Open Location Hub is the open integration backbone of Open Location Stack: a hub that can sit between RTLS infrastructure, applications, enterprise systems, and federated site deployments while staying aligned with omlox-style interoperability. For integrators and vendors, that means a shared place to build the non-differentiating but critical parts of the stack: API contracts, auth, event exchange, deployment scaffolding, connectors, federation patterns, and operational discipline. For customers, it creates a better path toward portability, mixed-technology aggregation, and less dependence on closed middleware. -## Early access - -This project is still early and not yet feature complete. It should be treated as an active implementation effort rather than a finished hub product. +## Get involved If you care about interoperable RTLS infrastructure, now is the right time to get involved. Try the code, review the API direction, open issues, and contribute pull requests to help shape the implementation. +[Browse the generated docs](/open-location-hub/docs/) + [Learn about Floor Plan Editor](/floor-plan-editor/) [View the repository on GitHub](https://github.com/Open-Location-Stack/open-location-hub) diff --git a/content/open-location-hub/docs/_index.md b/content/open-location-hub/docs/_index.md new file mode 100644 index 0000000..b196ca1 --- /dev/null +++ b/content/open-location-hub/docs/_index.md @@ -0,0 +1,16 @@ +--- +title: "Software Documentation" +description: "Use the documents in this directory for software behavior, runtime configuration, and integration guidance." +draft: false +generated: true +generated_from: "docs/index.md" +github_url: "https://github.com/Open-Location-Stack/open-location-hub/blob/main/docs/index.md" +--- +_This page is generated from the Open Location Hub source documentation and should not be edited in the website repository._ + +Use the documents in this directory for software behavior, runtime configuration, and integration guidance. + +- `architecture.md`: system structure, processing flows, and trust boundaries +- `configuration.md`: environment variables and runtime tuning +- `auth.md`: JWT auth modes, authorization model, and permission file behavior +- `rpc.md`: REST-facing RPC usage and control-plane behavior diff --git a/content/open-location-hub/docs/architecture.md b/content/open-location-hub/docs/architecture.md new file mode 100644 index 0000000..884586a --- /dev/null +++ b/content/open-location-hub/docs/architecture.md @@ -0,0 +1,100 @@ +--- +title: "Architecture" +description: "Generated documentation page for Architecture." +draft: false +generated: true +generated_from: "docs/architecture.md" +github_url: "https://github.com/Open-Location-Stack/open-location-hub/blob/main/docs/architecture.md" +--- +_This page is generated from the Open Location Hub source documentation and should not be edited in the website repository._ + +## Layers +- `cmd/hub`: process bootstrap and wiring +- `internal/config`: environment-driven configuration +- `internal/httpapi`: API surface and handlers +- `internal/ws`: OMLOX WebSocket wrapper protocol, subscriptions, and fan-out +- `internal/storage/postgres`: durable store +- `internal/mqtt`: MQTT topic mapping and broker integration +- `internal/auth`: token verification middleware +- `internal/rpc`: local-method dispatch, MQTT RPC bridging, announcements, and aggregation +- `internal/hub`: shared CRUD, ingest, derived event generation, collision evaluation, and internal event bus emission + +## Metadata And Hot State +- Postgres is the durable source of truth for hub metadata, zones, fences, trackables, and location providers. +- The runtime resolves the singleton hub metadata row before the service starts so one stable `hub_id` and label are available for startup validation, internal event provenance, and identify responses. +- The hub loads those resources into an immutable in-memory metadata snapshot before it accepts traffic. +- Successful CRUD writes update Postgres first, then update the in-memory snapshot, invalidate any affected derived metadata such as zone transforms, and emit a `metadata_changes` bus event. +- A background reconcile loop reloads durable metadata periodically and emits the same `metadata_changes` notifications when it detects out-of-band create, update, or delete drift. +- Decision-critical ingest state is kept in process memory: + - dedup windows + - latest provider-source locations + - latest trackable locations and WGS84 motions + - proximity hysteresis state + - fence membership state + - collision pair state + +## Event Fan-Out +1. REST, MQTT, or WebSocket ingest enters the shared hub service. +2. The hub validates, normalizes, deduplicates, updates in-memory transient state, and derives follow-on events. +3. The hub emits normalized internal events for locations, proximities, trackable motions, fence events, optional collision events, and metadata changes. +4. MQTT and WebSocket consume that same event stream and publish transport-specific payloads. + +Implications: +- ingest logic is shared across REST, MQTT, and WebSocket +- MQTT is no longer the only downstream publication path +- the internal event seam decouples downstream publication from MQTT-specific topics +- hub-issued UUIDs for REST-managed resources, derived fence/collision events, and RPC caller IDs now use UUIDv7 so emitted identifiers are time-sortable +- internal hub events carry the persisted `origin_hub_id` so downstream transports preserve source provenance + +## RPC Control Plane +1. A client calls `GET /v2/rpc/available` or `PUT /v2/rpc` over HTTP. +2. REST auth verifies the bearer token and route-level access. +3. The RPC bridge applies method-level authorization for discovery or invocation. +4. The bridge looks up the method in a unified registry containing: + - hub-owned local methods + - MQTT-discovered external handlers +5. The bridge either: + - handles the method locally + - forwards it to MQTT + - or does both and aggregates responses +6. The bridge returns a JSON-RPC result or JSON-RPC error payload to the HTTP caller. + +Built-in identify behavior: +- `com.omlox.identify` returns the persisted hub label as its `name` +- `com.omlox.identify` also returns the stable persisted `hub_id` + +Trust boundaries: +- HTTP clients should talk to the hub, not directly to MQTT devices +- MQTT should be restricted to the hub and trusted device/adaptor components +- the hub is the policy, audit, and handler-selection boundary for control-plane actions + +## Proximity Resolution Path +1. A REST, WebSocket, or MQTT `Proximity` update enters the shared hub service. +2. The hub resolves the referenced zone by `zone.id` or `zone.foreign_id`. +3. Only proximity-oriented zones are accepted for this path (`rfid` and `ibeacon`). +4. The hub loads transient per-provider proximity state from the in-memory processing state. +5. The resolver applies hub defaults plus any `Zone.properties.proximity_resolution` overrides. +6. Hysteresis rules decide whether to stay in the current zone or switch to the new candidate zone. +7. The hub emits a derived local `Location` using the resolved zone position and then continues through the normal location pipeline. + +Resolver notes: +- durable configuration lives in Postgres as part of the zone resource +- transient proximity membership state lives in the in-memory processing state +- derived location metadata includes hub extension fields such as `resolution_method`, `resolved_zone_id`, and `sticky` + +Resolver scope: +- the resolver emits the configured zone position as the derived point +- proximity resolution supports static proximity zones +- resolution policy is driven by hub defaults and zone-specific overrides + +## Contract-first flow +1. Update OpenAPI spec. +2. Regenerate generated server/types. +3. Implement handler behavior. +4. Validate with tests and check pipeline. + +## WebSocket Notes +- `GET /v2/ws/socket` is implemented outside the REST OpenAPI contract because it is a protocol companion surface rather than a generated REST endpoint. +- When auth is enabled, WebSocket messages authenticate with `params.token` and apply dedicated topic publish/subscribe authorization. +- `collision_events` is a known topic but remains configuration-gated by `COLLISIONS_ENABLED`. +- `metadata_changes` is a subscribe-only topic that carries lightweight metadata replication notifications shaped as `{id,type,operation,timestamp}`. diff --git a/content/open-location-hub/docs/auth.md b/content/open-location-hub/docs/auth.md new file mode 100644 index 0000000..bdeec1c --- /dev/null +++ b/content/open-location-hub/docs/auth.md @@ -0,0 +1,236 @@ +--- +title: "Authentication and Authorization" +description: "This project supports standards-based JWT bearer authentication for the REST API and an authorization model built around JWT claims plus a server-side permissions file." +draft: false +generated: true +generated_from: "docs/auth.md" +github_url: "https://github.com/Open-Location-Stack/open-location-hub/blob/main/docs/auth.md" +--- +_This page is generated from the Open Location Hub source documentation and should not be edited in the website repository._ + +This project supports standards-based JWT bearer authentication for the REST API and an authorization model built around JWT claims plus a server-side permissions file. + +The same token verifier is also used for the OMLOX WebSocket surface, but WebSocket authentication happens per message through `params.token` instead of the HTTP `Authorization` header. + +## Modes + +- `none`: disable auth checks +- `oidc`: verify bearer tokens through OIDC discovery and JWKS +- `static`: verify bearer tokens against static PEM keys or JWKS URLs +- `hybrid`: accept either OIDC-verified or static-key tokens + +## OIDC and JWKS + +For `oidc` mode, the hub loads issuer metadata from `AUTH_ISSUER`, discovers the provider JWKS endpoint, and verifies JWT signatures and standard claims. Provider metadata and verifier state are cached and refreshed according to `AUTH_OIDC_REFRESH_TTL` instead of being reloaded on every request. + +Relevant settings: + +- `AUTH_ISSUER` +- `AUTH_AUDIENCE` +- `AUTH_ALLOWED_ALGS` +- `AUTH_CLOCK_SKEW` +- `AUTH_HTTP_TIMEOUT` +- `AUTH_OIDC_REFRESH_TTL` + +## Authorization Model + +Authorization uses a role and ownership based model: + +- authenticate the bearer token first +- extract a role-like claim from the JWT via `AUTH_ROLES_CLAIM` +- load path permissions from `AUTH_PERMISSIONS_FILE` +- optionally enforce ownership checks with the claim configured by `AUTH_OWNED_RESOURCES_CLAIM` + +Supported permission values: + +- `CREATE_ANY` +- `READ_ANY` +- `UPDATE_ANY` +- `DELETE_ANY` +- `CREATE_OWN` +- `READ_OWN` +- `UPDATE_OWN` +- `DELETE_OWN` + +Method mapping: + +- `GET` and `HEAD` require `READ_*` +- `POST` requires `CREATE_*` +- `PUT` and `PATCH` require `UPDATE_*` +- `DELETE` requires `DELETE_*` + +`*_OWN` permissions apply to routes that include explicit path identifiers, such as `/v2/providers/:providerId`. Collection routes such as `/v2/zones` use the corresponding `*_ANY` semantics. + +## Permissions File + +The permissions file is YAML. Top-level keys are values from the configured role claim. In production this would usually be a role or group claim. For the included Dex development fixture, the role claim is set to `email` because Dex's local password database produces deterministic user identity claims without extra role mapping. That is a development convenience, not a production recommendation. + +Example: + +```yaml +admin@example.com: + description: Full access + /v2/*: + - CREATE_ANY + - READ_ANY + - UPDATE_ANY + - DELETE_ANY + rpc: + discover: true + invoke: + "*": true + +reader@example.com: + description: Read-only access + /v2/zones: + - READ_ANY + /v2/zones/:zoneId: + - READ_ANY + /v2/rpc/available: + - READ_ANY + rpc: + discover: true + invoke: + com.omlox.ping: true + com.omlox.identify: true +``` + +Path placeholders are used for ownership checks. The hub derives claim keys from route parameter names. For example `:providerId` maps to `provider_ids`. + +RPC policy entries are evaluated after route-level authorization. They use a +dedicated `rpc` section per role: + +- `discover: true` allows `GET /v2/rpc/available` +- `invoke` lists allowed method names +- `invoke` entries may be: + - exact method names such as `com.omlox.ping` + - prefix wildcards such as `com.vendor.*` + - `*` for full RPC invocation access + +This means a role can be allowed to reach the RPC endpoint path but still be +blocked from invoking a specific method. + +WebSocket policy entries are evaluated separately from REST route permissions. They use a dedicated `websocket` section per role: + +- `subscribe` lists topic names or wildcard patterns the role may subscribe to +- `publish` lists topic names or wildcard patterns the role may send `message` events to + +Example: + +```yaml +admin@example.com: + websocket: + subscribe: + "*": true + publish: + location_updates: true + proximity_updates: true +``` + +The WebSocket policy matcher supports exact topic names and suffix-style wildcard patterns such as `location_*`. + +Subscribe-only topics include: +- `location_updates` +- `location_updates:geojson` +- `proximity_updates` +- `trackable_motions` +- `fence_events` +- `fence_events:geojson` +- `collision_events` when collisions are enabled +- `metadata_changes` for resource create, update, and delete notifications on zones, fences, trackables, and location providers + +## Ownership Claims + +Ownership-aware rules use the claim configured by `AUTH_OWNED_RESOURCES_CLAIM`. + +Expected shape: + +```json +{ + "": { + "provider_ids": ["provider-1"], + "trackable_ids": ["trackable-1"], + "zone_ids": ["zone-1"], + "fence_ids": ["fence-1"], + "source_ids": ["source-1"] + } +} +``` + +For `*_OWN` permissions, the request path parameter must be present in the matching owned-resource list. + +## Error Handling + +- `401 Unauthorized`: missing bearer header, malformed token, invalid signature, bad issuer, bad audience, expired token, or other authentication failure +- `403 Forbidden`: authenticated token lacks a matching permission or ownership claim +- `403 Forbidden` on RPC also covers missing method-level discovery or invocation permission +- WebSocket auth failures are returned as OMLOX wrapper `error` events with code `10004` + +Authentication failures return a `WWW-Authenticate: Bearer` header and the API error body. + +## WebSocket Authentication + +When auth is enabled: +- every WebSocket `subscribe` and `message` event must carry the JWT access token in `params.token` +- the hub authenticates and authorizes each message independently +- the WebSocket upgrade itself is intentionally allowed without an HTTP bearer header so the OMLOX `params.token` model can be used +- route-style REST permissions do not grant WebSocket topic access automatically; the `websocket` policy block must allow the topic + +If a topic is valid but disabled by configuration, the WebSocket layer returns an OMLOX wrapper `error` event with code `10002` and a descriptive message instead of treating it as an unknown topic. + +## Dex Development Setup + +This repository includes a Dex fixture at [tools/dex/config.yaml](https://github.com/Open-Location-Stack/open-location-hub/blob/main/tools/dex/config.yaml) and a matching permissions file at [config/auth/permissions.yaml](https://github.com/Open-Location-Stack/open-location-hub/blob/main/config/auth/permissions.yaml). + +`docker compose` starts Dex on port `5556` and configures the app container to verify Dex-issued tokens with: + +- `AUTH_MODE=oidc` +- `AUTH_ISSUER=http://dex:5556/dex` +- `AUTH_AUDIENCE=open-rtls-cli` +- `AUTH_ROLES_CLAIM=email` + +Included test users: + +- `admin@example.com` / `testpass123` +- `reader@example.com` / `testpass123` +- `owner@example.com` / `testpass123` + +Fetch a token: + +```bash +curl -sS -X POST http://localhost:5556/dex/token \ + -u open-rtls-cli:cli-secret \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + --data 'grant_type=password&scope=openid%20email%20profile&username=admin@example.com&password=testpass123' +``` + +Use the returned `access_token` as the bearer token when calling the hub. + +## Other Providers + +Keycloak and similar OIDC providers fit the same model if they expose: + +- issuer discovery +- JWKS +- a stable audience for the hub +- a claim that can be mapped via `AUTH_ROLES_CLAIM` + +For production deployments, prefer a real role or group claim instead of the Dex development fixture's email-based mapping. The hub is intended to verify JWT access tokens from the production IdP, not development-specific token handling. + +## RPC Security Guidance + +For RPC in production: +- require JWT auth +- treat `GET /v2/rpc/available` as sensitive metadata +- grant `com.omlox.ping` and `com.omlox.identify` more broadly only if operators really need them +- grant `com.omlox.core.xcmd` only to tightly controlled roles or automation identities +- keep MQTT broker access narrow so user-facing applications cannot bypass the hub's policy and audit layer + +## End-to-End Coverage + +The integration suite boots Dex and the hub, obtains a bearer token from Dex, and proves: + +- authenticated requests reach protected endpoints +- missing or invalid tokens return `401` +- insufficient permissions return `403` +- ownership-restricted routes reject tokens that lack owned-resource claims diff --git a/content/open-location-hub/docs/configuration.md b/content/open-location-hub/docs/configuration.md new file mode 100644 index 0000000..8ff44a5 --- /dev/null +++ b/content/open-location-hub/docs/configuration.md @@ -0,0 +1,110 @@ +--- +title: "Configuration" +description: "All runtime configuration is environment-driven." +draft: false +generated: true +generated_from: "docs/configuration.md" +github_url: "https://github.com/Open-Location-Stack/open-location-hub/blob/main/docs/configuration.md" +--- +_This page is generated from the Open Location Hub source documentation and should not be edited in the website repository._ + +All runtime configuration is environment-driven. + +Runtime lifecycle behavior: +- the hub process runs from a single signal-aware root context created from `SIGINT` and `SIGTERM` +- startup failures return structured process errors instead of panicking during early config or logger initialization +- graceful shutdown uses a bounded timeout so HTTP shutdown and internal event-publisher fan-out can complete deterministically after a stop signal + +## Core +- `HTTP_LISTEN_ADDR` (default `:8080`) +- `HTTP_REQUEST_BODY_LIMIT_BYTES` (default `4194304`) +- `LOG_LEVEL` (default `info`) +- `HUB_ID` (optional UUID bootstrap or reset value for the persisted hub identity) +- `HUB_LABEL` (optional bootstrap or reset value for the persisted human-readable hub label) +- `RESET_HUB_ID` (`true`/`false`, default `false`; when `true`, overwrite stored hub metadata with explicitly supplied env values) +- `POSTGRES_URL` (default `postgres://postgres:postgres@localhost:5432/openrtls?sslmode=disable`) +- `MQTT_BROKER_URL` (default `tcp://localhost:1883`) +- `WEBSOCKET_WRITE_TIMEOUT` (duration, default `5s`) +- `WEBSOCKET_OUTBOUND_BUFFER` (default `32`) + +Hub metadata bootstrap behavior: +- the hub persists one durable metadata row in Postgres containing the stable `hub_id` and operator-facing label +- on first startup, `HUB_ID` seeds that row when provided; otherwise the hub generates a UUIDv7 +- on first startup, `HUB_LABEL` seeds that row when provided; otherwise the hub defaults to the machine hostname and falls back to `open-rtls-hub` if hostname lookup is unavailable +- on later startups, the stored row is the source of truth when `HUB_ID` and `HUB_LABEL` are omitted +- if supplied env values disagree with the stored row, startup fails with a clear mismatch error unless `RESET_HUB_ID=true` +- when `RESET_HUB_ID=true`, only explicitly supplied values overwrite the stored row; omitted fields are preserved + +HTTP request decoding behavior: +- JSON request bodies are capped by `HTTP_REQUEST_BODY_LIMIT_BYTES` before decode work proceeds +- the REST/RPC handler layer accepts exactly one JSON document per request body and rejects trailing JSON tokens +- unknown JSON object fields remain allowed so forwards-compatible clients are not rejected solely for extension data + +## Stateful Processing +- `STATE_LOCATION_TTL` (duration, default `10m`) +- `STATE_PROXIMITY_TTL` (duration, default `5m`) +- `STATE_DEDUP_TTL` (duration, default `2m`) +- `METADATA_RECONCILE_INTERVAL` (duration, default `30s`) +- `RPC_TIMEOUT` (duration, default `5s`) +- `RPC_ANNOUNCEMENT_INTERVAL` (duration, default `1m`) +- `RPC_HANDLER_ID` (default `open-rtls-hub`) +- `COLLISIONS_ENABLED` (`true`/`false`, default `false`) +- `COLLISION_STATE_TTL` (duration, default `2m`) +- `COLLISION_COLLIDING_DEBOUNCE` (duration, default `5s`) + +Stateful ingest behavior: +- duplicate location/proximity payloads inside `STATE_DEDUP_TTL` are suppressed in the in-memory processing state before fan-out work +- latest provider-source location state, trackable latest-location state, proximity hysteresis state, fence membership state, and collision pair state are all kept in process memory with the configured expiry semantics +- metadata is loaded from Postgres at startup, updated immediately after successful CRUD writes, and reconciled in the background every `METADATA_RECONCILE_INTERVAL` +- durable hub metadata is also loaded from Postgres at startup before the service begins accepting traffic +- WebSocket delivery uses a per-connection outbound queue capped by `WEBSOCKET_OUTBOUND_BUFFER`; slow subscribers are disconnected instead of backpressuring the ingest path +- the `metadata_changes` WebSocket topic emits lightweight `{id,type,operation,timestamp}` notifications for zone, fence, trackable, and location-provider CRUD or reconcile drift +- when `COLLISIONS_ENABLED=true`, the hub evaluates trackable-versus-trackable collisions from the latest WGS84 motion state and keeps short-lived collision pair state in memory for `COLLISION_STATE_TTL` +- `COLLISION_COLLIDING_DEBOUNCE` limits repeated `colliding` emissions for already-active pairs + +RPC behavior: +- `RPC_TIMEOUT` is the default wait time for request-response style RPC calls when the client does not supply `_timeout` +- `RPC_ANNOUNCEMENT_INTERVAL` controls how often the hub republishes retained MQTT availability announcements for hub-owned methods +- `RPC_HANDLER_ID` is the handler identifier announced for hub-owned RPC methods and the identifier clients may use with `_handler_id` to target the hub directly +- `com.omlox.identify` returns the persisted hub label as `name` plus the stable `hub_id` + +## Proximity Resolution +- `PROXIMITY_RESOLUTION_ENTRY_CONFIDENCE_MIN` (number, default `0`) +- `PROXIMITY_RESOLUTION_EXIT_GRACE_DURATION` (duration, default `15s`) +- `PROXIMITY_RESOLUTION_BOUNDARY_GRACE_DISTANCE` (number, default `2`) +- `PROXIMITY_RESOLUTION_MIN_DWELL_DURATION` (duration, default `5s`) +- `PROXIMITY_RESOLUTION_POSITION_MODE` (default `zone_position`; supported value: `zone_position`) +- `PROXIMITY_RESOLUTION_FALLBACK_RADIUS` (number, default `0`) +- `PROXIMITY_RESOLUTION_STALE_STATE_TTL` (duration, default `10m`) + +Proximity resolution behavior: +- proximity updates are resolved to a zone before the hub emits a derived `Location` +- the first valid proximity observation enters immediately +- the hub keeps the current zone for a short grace period to reduce flapping between nearby zones +- zone-specific overrides may be supplied through `Zone.properties.proximity_resolution` +- `Proximity.properties` is preserved into derived location metadata but does not override configured policy + +## Auth +- `AUTH_ENABLED` (`true`/`false`, default `true`) +- `AUTH_MODE` (`none|oidc|static|hybrid`, default `none`) +- `AUTH_ISSUER` (OIDC issuer URL) +- `AUTH_AUDIENCE` (comma-separated) +- `AUTH_ALLOWED_ALGS` (comma-separated, default `RS256`) +- `AUTH_STATIC_PUBLIC_KEYS` (comma-separated PEM blocks or JWKS URLs) +- `AUTH_CLOCK_SKEW` (duration, default `30s`) +- `AUTH_OIDC_REFRESH_TTL` (duration, default `10m`) +- `AUTH_HTTP_TIMEOUT` (duration, default `5s`) +- `AUTH_PERMISSIONS_FILE` (YAML path, default `config/auth/permissions.yaml`) +- `AUTH_ROLES_CLAIM` (JWT claim used for role extraction, default `groups`) +- `AUTH_OWNED_RESOURCES_CLAIM` (JWT object claim for owned resource IDs; see `docs/auth.md` for format and usage) + +See [docs/auth.md](/open-location-hub/docs/auth/) for the full auth model, Dex setup, and permission file format. + +## RPC Security Defaults + +For production deployments: +- keep `AUTH_ENABLED=true` +- treat `GET /v2/rpc/available` as sensitive because it reveals reachable control functions +- use per-method RPC permissions in the auth registry to control who may invoke which methods +- grant `com.omlox.core.xcmd` only to tightly controlled operator or automation roles +- keep direct MQTT broker access limited to the hub and trusted device/adaptor components diff --git a/content/open-location-hub/docs/rpc.md b/content/open-location-hub/docs/rpc.md new file mode 100644 index 0000000..d9e4af1 --- /dev/null +++ b/content/open-location-hub/docs/rpc.md @@ -0,0 +1,224 @@ +--- +title: "RPC Guide" +description: "RPC is the hub's command and diagnostics interface." +draft: false +generated: true +generated_from: "docs/rpc.md" +github_url: "https://github.com/Open-Location-Stack/open-location-hub/blob/main/docs/rpc.md" +--- +_This page is generated from the Open Location Hub source documentation and should not be edited in the website repository._ + +## What RPC is + +RPC is the hub's command and diagnostics interface. + +Use it when you need to ask the hub or a downstream RTLS device/controller to +do something right now. Examples: +- check whether the control path is alive +- identify a reachable handler +- send an OMLOX core command through the hub + +Do not use RPC for normal CRUD resource management. Use: +- REST CRUD for zones, trackables, providers, and fences +- MQTT/WebSocket streams for ongoing updates and events +- RPC for targeted commands and diagnostics + +## The simple mental model + +Applications call `open-rtls-hub`. They do not call MQTT devices directly. + +Flow: +1. The client calls `GET /v2/rpc/available` to see what methods are reachable. +2. The client calls `PUT /v2/rpc` with a JSON-RPC request. +3. The hub either handles the method locally or forwards it to the right MQTT handler. +4. The hub applies authorization, logging, timeout, and aggregation rules. +5. The hub returns the JSON-RPC result or error to the client. + +This makes the hub the control-plane front door and audit point. + +## Available methods + +The hub exposes these OMLOX-reserved methods locally: +- `com.omlox.ping` +- `com.omlox.identify` +- `com.omlox.core.xcmd` + +The hub also exposes methods announced by external MQTT handlers when those handlers are reachable. + +Use: + +```bash +curl -sS http://localhost:8080/v2/rpc/available \ + -H "Authorization: Bearer $TOKEN" +``` + +The response maps method names to the reachable handler ids known to the hub. + +## Calling methods + +Call `PUT /v2/rpc` with a JSON-RPC 2.0 request body. + +Example: ping the hub + +```bash +curl -sS -X PUT http://localhost:8080/v2/rpc \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "ping-1", + "method": "com.omlox.ping", + "params": { + "_aggregation": "_return_first_success" + } + }' +``` + +Example: identify a specific handler + +```bash +curl -sS -X PUT http://localhost:8080/v2/rpc \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "identify-1", + "method": "com.omlox.identify", + "params": { + "_handler_id": "open-rtls-hub" + } + }' +``` + +Example: send an OMLOX core command through the hub + +```bash +curl -sS -X PUT http://localhost:8080/v2/rpc \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "xcmd-1", + "method": "com.omlox.core.xcmd", + "params": { + "_handler_id": "open-rtls-hub", + "command": "XCMD_REQ", + "payload": { + "example": true + } + } + }' +``` + +Note: +- `com.omlox.core.xcmd` already exists at the hub control-plane layer +- device execution is provided through the adapter configured for the target deployment +- if no adapter is configured, the hub returns a deterministic unsupported JSON-RPC error + +## What the built-in methods mean + +### `com.omlox.ping` + +Use it to prove the RPC path is alive. It returns a small result containing the +hub handler id, the message `pong`, and a timestamp. + +### `com.omlox.identify` + +Use it to learn what the hub exposes. It returns the service name, build +version, auth mode, stable `hub_id`, and built-in method list. + +Hub behavior: +- `name` is the persisted hub label from Postgres-backed hub metadata +- `hub_id` is the stable persisted hub UUID used for internal provenance + +### `com.omlox.core.xcmd` + +Use it when you need to send OMLOX core-zone commands through the hub instead +of opening direct MQTT control access to devices. The hub validates and logs +the call, then routes it through the configured adapter. + +## `_handler_id`, `_timeout`, and `_aggregation` + +### `_handler_id` + +Use `_handler_id` when one specific handler must receive the command. + +Examples: +- target the hub itself with `open-rtls-hub` +- target a specific external handler discovered from `GET /v2/rpc/available` + +### `_timeout` + +Use `_timeout` to override the default wait time in milliseconds for a specific +request. + +### `_aggregation` + +Use `_aggregation` only when the call may have multiple responders. + +Supported values: +- `_all_within_timeout` +- `_return_first_success` +- `_return_first_error` + +Rules: +- `_handler_id` and `_aggregation` cannot be combined +- if `_aggregation` is omitted, the hub defaults to `_all_within_timeout` + +## What happens when multiple handlers answer + +The hub collects responses according to `_aggregation`. + +- `_all_within_timeout`: return one result whose `responses` array contains all responses received before timeout +- `_return_first_success`: return as soon as a non-error response arrives +- `_return_first_error`: return as soon as an error response arrives + +If no suitable response arrives, the hub returns a JSON-RPC error. + +## Authorization + +RPC uses two layers of control: + +1. normal REST bearer-token authentication and route authorization +2. RPC-specific method authorization inside the hub + +That means a caller can be allowed to reach `/v2/rpc` and still be denied for a +specific method. + +Recommended policy shape: +- allow discovery only to trusted operators or automation +- allow `com.omlox.ping` and `com.omlox.identify` to a small support audience if needed +- allow `com.omlox.core.xcmd` only to tightly controlled roles +- deny unknown/custom methods by default until explicitly approved + +## How to secure it + +Plain language rule: + +Do not let every application talk directly to MQTT devices. Let them talk to +the hub. The hub checks identity, checks permissions, logs what was called, and +only then forwards the command. + +Operational guidance: +- require JWT auth for RPC in production +- treat `GET /v2/rpc/available` as sensitive because it shows reachable control functions +- grant invoke permission per method, not just generic RPC access +- restrict `com.omlox.core.xcmd` more tightly than `ping` or `identify` +- keep MQTT broker access narrow to the hub and trusted adapters/devices +- use the hub as the audit and policy boundary +- fail closed when authorization or handler selection is uncertain + +## Logging and audit expectations + +The hub logs: +- the method name +- whether the call was accepted or rejected +- handler selection and timeout/failure paths + +Operators should treat those logs as the primary audit trail for RPC use. + +## Repository behavior + +- the available-method response exposes handler ids for the methods the hub can reach +- the hub publishes retained MQTT availability announcements for hub-owned methods +- `com.omlox.core.xcmd` executes through the deployment adapter configured for the hub and returns a deterministic unsupported error when no adapter is configured diff --git a/i18n/en.yaml b/i18n/en.yaml index dd3e39a..862986e 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -10,6 +10,9 @@ - id: nav_open_location_hub translation: Open Location Hub +- id: nav_open_location_hub_docs + translation: Hub Docs + - id: nav_floor_plan_editor translation: Floor Plan Editor diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index e2f108a..692de0c 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -145,6 +145,7 @@
{{ i18n "nav_home" }} {{ i18n "nav_open_location_hub" }} + {{ i18n "nav_open_location_hub_docs" }} {{ i18n "nav_floor_plan_editor" }} {{ i18n "nav_faq" }} {{ i18n "nav_imprint" }} @@ -167,6 +168,7 @@ {{ i18n "email_link_text" }} {{ i18n "nav_open_location_hub" }} + {{ i18n "nav_open_location_hub_docs" }} {{ i18n "nav_floor_plan_editor" }} {{ i18n "nav_faq" }} {{ i18n "nav_imprint" }} diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..c107e81 --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,51 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} + {{ with .Params.github_url }} + + {{ end }} +
+ +
+ {{ .Content }} +
+ + {{ $sections := .Sections.ByTitle }} + {{ $regularPages := .RegularPages.ByTitle }} + {{ if or (gt (len $sections) 0) (gt (len $regularPages) 0) }} +
+
+ {{ range $sections }} +
+

+ {{ .Title }} +

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ {{ end }} + {{ range $regularPages }} +
+

+ {{ .Title }} +

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ {{ end }} +
+
+ {{ end }} +
+{{ end }}