LinkO'Star

Permission Model

LinkOStar implements a hybrid permission model where each supplier picks the SSOT. The default is LinkOStar-SSOT; when a supplier registers a permission endpoint, that surface is delegated to the supplier (V13). Each integrator decides whether their supplier registers a permission endpoint.

SSOT split

ConditionPermission SSOTScope
supplier_oauth_config.permission_query_url nullLinkOStar (default)Native authorisation by tenant_uuid + UserPersona
permission_query_url setSupplierThe supplier's view of which user can do what on its hubs/devices

Within the same LinkOStar instance, supplier A can run on LinkOStar-SSOT while supplier B runs on supplier-SSOT.

Permission enum (V13)

PermissionMeaningImplies
ownerEverything + deleteoperator · viewer
operatorRead + write commands; no deleteviewer
viewerRead-only

Unknown values are dropped silently — extending the enum requires a LinkOStar code change.

1. Supplier-SSOT lookup (D-Sync-1/2/3)

  1. User hits /app/hubs or /app/devices.
  2. LinkOStar gathers candidates, groups them by supplier_uuid.
  3. For supplier-SSOT groups, check SupplierPermissionCache:
    • cache hit within TTL → filter directly
    • cache miss / stale → call permission_query_url with the user's supplier access_token, fill the cache, then filter
  4. For LinkOStar-SSOT groups, filter by native auth (tenant_uuid + persona).
  5. Merge both groups into the response.

2. permission_query_url response shape

GET https://supplier.example.com/api/m2m/users/{sub}/permissions
Authorization: Bearer <supplier-issued user access_token>

Response 200:
[
  {"resource_type": "HUB",    "resource_uuid": "550e8400-...", "permissions": ["owner"]},
  {"resource_type": "DEVICE", "resource_uuid": "660e8400-...", "permissions": ["operator","viewer"]}
]

A {sub}placeholder in the URL is substituted with the user's supplier sub. Under the n:1 model (one LinkOStar user linked to multiple accounts of the same supplier), the service queries each sub and unions the responses.

3. Cache + invalidation

  • TTL: linkostar.supplier-permission.cache-ttl (5 min default).
  • Webhook invalidate: a supplier change posts to /webhook/supplier-permission/{supplierUuid}with the HMAC signature. Every active SupplierLink with the body's user_subgets that user's cache dropped.
  • Implicit on revoke: when a user revokes a supplier-link, every cache row for that (user, supplier) pair is deleted.

4. Users without a supplier link (D-Sync-6)

If a supplier requires OAuth (it has a supplier_oauth_config) and the user hasn't linked yet, that supplier's hubs are hidden from /app/hubs. The PUBLISHED bundle is still visible via /app/bundle-catalog with requiresSupplierLink: true and currentUserLinked: false, so the app can show the link entry point.

To avoid the "hidden → can't link → still hidden" deadlock, BLE proximity and similar near-field signals should also trigger the supplier-link flow from the mobile app.

5. Permission hierarchy at a glance

required \ have      none    viewer    operator    owner
viewer                ❌      ✅        ✅           ✅
operator              ❌      ❌        ✅           ✅
owner                 ❌      ❌        ❌           ✅

Mutating endpoints like AppDeviceService.sendCommand require at least OPERATOR when the supplier is the SSOT.

Operational notes

  • A short TTL (5 min or less) combined with webhook invalidation is the safest mix. Per-request lookups against the supplier are expensive — keep the cache.
  • If permission_query_url 5xx's, LinkOStar treats the response as empty and the user sees "no permission". Supplier endpoint availability directly impacts user UX.
  • In the n:1 model permissions are unioned — if any of the user's accounts at the same supplier has access, it is granted. Stricter policies are a separate sprint.