Skip to content

Collaborative tasking layers, Beacon auth hardening, and fixes - #405

Merged
OSPFNeighbour merged 27 commits into
masterfrom
master-dev
Aug 10, 2026
Merged

Collaborative tasking layers, Beacon auth hardening, and fixes#405
OSPFNeighbour merged 27 commits into
masterfrom
master-dev

Conversation

@OSPFNeighbour

@OSPFNeighbour OSPFNeighbour commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds collaborative map layers to the tasking map: creation, HQ/event attachment, owner/moderator-managed permissions (read-only, delete, comments), a subscriptions model, marker comments, a dedicated map pane, and UI polish for the create/settings/picker flows
  • Hardens Beacon authentication: requires Beacon auth on all lambda.lighthouse-extension.com Lambdas, logs the authenticated user on each lad_v2 invocation, and supports multiple trusted Beacon token issuers
  • Fixes several bugs: map popups rendering behind markers, SMS send request built from the SMS modal, wrong apiHost used for the transport incidents ops log lookup, BMB unit HQ coordinates, map-layers index.json retry not catching S3's actual conflict status, and a flash of unbound placeholder content before the config modal shows
  • Bumps js-yaml, tmp, immutable, and postcss to patched versions

Test plan

  • Create a collaborative layer and verify HQ/event attachment, moderator permission changes, and subscriptions work end-to-end
  • Verify Beacon-authenticated requests succeed and unauthenticated requests are rejected on lambda.lighthouse-extension.com
  • Send an SMS via the SMS modal and confirm the request payload is correct
  • Confirm map popups render above markers and marker comments/icons display correctly

🤖 Generated with Claude Code

OSPFNeighbour and others added 27 commits July 2, 2026 11:02
Extract hardcoded width values ('280px' and '30px') into constants for better maintainability. Add comprehensive state synchronization in the in-place update path to ensure collapsed/open states, classes, and UI elements are properly kept in sync when rules are updated. This prevents stale classes and styles from persisting during updates.
Adds shared tasking layers with marker sync, right-click marker creation, and popup editing/deletion. Also adds the collaborative layer list and visibility controls in the config modal, plus Beacon person-name lookup for marker attribution.
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.10 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.10...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.3.8 to 4.3.9.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](immutable-js/immutable-js@v4.3.8...v4.3.9)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 4.3.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.6 to 0.2.7.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](raszi/node-tmp@v0.2.6...v0.2.7)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.3.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.1.1...4.3.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

The lambda.lighthouse-extension.com/lad/... endpoints accepted requests
from anyone with the URL. Add /lad_v2/... copies of all five Lambdas
(share, default-assets, map-layers, route, geocode) that verify the
caller's existing Beacon access token (RS256 JWT, verified locally
against SES's identity server JWKS) before doing anything -- no new
login step, no token minted by us.

route/geocode also required threading a token into two places outside
the tasking SPA: src/contentscripts/jobs/create.js (a real content
script, reads the token from chrome.storage.local) and
src/injectscripts/jobs/view.js (a true page-context inject script,
uses the page's own `user.accessToken` global directly). The
injectscripts/jobs/create.js counterpart now also forwards
`user.accessToken` via postMessage to its content-script pair, which
needed it for its own route-Lambda call.

Also fixes three call sites in models/Team.js that were still calling
fetchSharedDefaults() with the pre-auth two-arg signature, sending a
literal "Bearer undefined" header.

Also un-ignore lambda/ (it was fully git-ignored, so none of this
source -- old or new -- was previously tracked).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Each of the five auth-gated Lambdas discarded the verified token's
claims after checking them. Capture the returned claims and emit one
structured log line (sub/client_id + route) per successful auth so
usage can be broken down per user in CloudWatch Logs Insights, not
just aggregate invocation counts.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…387)

updateIndex()'s optimistic-concurrency retry only treated HTTP 412 as
a lost race and retried; everything else (including the actual error)
was rethrown straight to the caller. S3's conditional-write feature
(IfMatch/IfNoneMatch on PutObject) reports a lost race as 409
ConditionalRequestConflict, not 412 -- so the retry never fired, and
every real race surfaced as a 500 to the client. Confirmed via
CloudWatch: dozens of these on LH-MapLayersv2's GET /{id} route (which
bumps lastUsedAt on every view), all against the same org's
index.json.

Applied to both map-layers-v2 (currently deployed to LH-MapLayersv2)
and map-layers (the pre-auth v1 copy, still live on LH-MapLayers with
real traffic) since they share the identical bug.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Beacon has separate identity server deployments per environment (prod,
train, ...) with their own signing keys, so a single TRUSTED_ISS/JWKS_URI
pair can't validate tokens from more than one. TRUSTED_ISS is now a
comma-separated allow-list; the JWKS endpoint and expected audience are
derived per-issuer from the matched trusted value instead of needing
separate lists kept in sync.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
)

Markers on the collaborative tasking layer now support a threaded
comment log, resolved through the Beacon Operations Log (title/
description and comments are pointers into Ops Log entries rather than
free text stored on the marker itself).

Also replaces the marker icon picker's 42-icon/7-group set with a
curated 26-icon/6-group set (Hazards & Weather, Vehicles & Rescue,
People & Animals, Resources & Supplies, Observation & Comms, Status &
Markers), and updates the map-layers-v2 Lambda's icon allowlist and
default icon to match -- it must stay in sync with the client's
MARKER_ICON_GROUPS or new icons get silently rejected server-side.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ive map layers (#390)

Creators can now restrict a layer at creation time: only they can add
markers, only they can delete the layer, and/or comments are disabled
entirely. Enforced server-side in lambda/map-layers-v2 against the Beacon
member id from the caller's verified JWT (claims.sub), stored as
createdByMemberId on the layer -- not the client-supplied actorId, which
stays purely for display/audit. Adds a soft-delete DELETE
/lad_v2/map-layers/{id} route/handler for removing a layer.

The tasking UI gains matching controls: a collapsed-by-default "New layer
permissions" section with Anyone/Only-I radio pairs for markers, layer
deletion and comments, a per-row Delete-layer button, and marker
popup/add-marker gating that gets out of the way once the Lambda would
reject the action anyway. Also fixes a pre-existing bug where layer
creation was attributed to a different Beacon identity (userId) than every
marker/comment action on that layer (personId), which would have silently
broken creator checks.

Already deployed to the live LH-MapLayersv2 Lambda and its API Gateway
route.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
)

The page reveal (body opacity 1) fired on the plain DOMContentLoaded
event, racing ahead of the async require(["knockout", ...]) load and
ko.applyBindings call. That let raw, unbound markup (and the config
modal popping in afterward) flash briefly on page load. Now the
reveal happens right after bindings are applied and the modal is
already open.
Collab markers previously fell into Leaflet's default markerPane
(fixed z-index 600), so they weren't part of the layer-order drawer
and could render inconsistently relative to Incident/Asset markers
depending on the user's pane order config.

Adds a pane-collab pane, defaulted to sit under Incident markers but
above Asset markers, and makes it a first-class entry in the
draggable layer order list. Existing saved pane orders get the new
pane inserted at its default relative position instead of always
appended at the bottom.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ptions model for collaborative map layers (#393)

- Every layer must now belong to an HQ (required, defaults to ?hq=)
  and can optionally be attached to a Beacon event; both are
  searchable via new BeaconClient modules (entities-backed HQ search,
  new events.js, new users.js for moderator search).
- Marker/delete/comment permissions are now a 3-way mode
  (anyone/creator/moderators) instead of booleans, with a shared
  moderator list per layer. Moderators can now manage that list
  themselves, not just the creator.
- Replaced the HQ-filtered "list with a View toggle" model with a
  subscriptions model: a "My layers" list (always cross-HQ, so
  unsubscribing never depends on knowing which HQ a layer came from)
  plus a separate "Find a layer" search to discover and subscribe.
  Show/hide is now solely the map's own Layers control's job.
- listLayers.js supports server-side HQ filtering; permission checks
  centralized in lib/permissions.js and reused across handlers.
- Marker/comment Ops Log entries now carry the layer's EventId when
  one is attached.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…r creation (#394)

Previously markerMode/deleteMode/commentMode were fixed at creation. Adds a
PUT /permissions Lambda route (creator-or-moderator authorized, same as
moderator management) and a standalone Config modal to edit them, with
poll-driven sync so other subscribers see permission changes without a
manual refresh.

Also includes the getWritableCollabLayers rename and Add-marker disabled
state that were already uncommitted in the working tree and are required
for the collab layer permission model to be consistent.
Every Leaflet popup on the tasking map (collab layer, incidents,
assets, dams, WaterNSW, hazard watch, BOM weather, transport cameras/
roadworks, SES unit locations, and context-menu search/geocode
results) fell back to Leaflet's built-in popupPane, which loses
z-index ties against this app's custom marker panes since it's
created earlier in the DOM. Give all of them a dedicated
pane-popup-top pane fixed above every marker pane.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…t after creation (#396)

Both were previously fixed at creation with no later "reassign HQ" or
"attach/detach event" flow. Adds a PUT /map-layers/{id}/attachment
route (same creator-or-moderator authorization as permissions/
moderators) and extends the existing "Manage permissions" modal with
HQ and event pickers, reusing the same widgets from the create-layer
form.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
)

* Move collaborative layer moderator editing into the settings modal

Moderators had their own inline expanding panel and Save/Cancel
separate from the permissions/HQ/event modal, despite being gated by
the same creator-or-moderator authorization. Folds it into
#collabPermissionsModal alongside them so one Save click updates
permissions, attachment and moderators together.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Use the pencil icon on this branch too

This branch forked from master-dev before the pencil-icon fix (#397)
merged, so the settings button still showed the old padlock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Shows each candidate's unit alongside their member number in the
moderator search dropdown, closes the create-layer form automatically
after a successful create (it previously stayed open with the fields
reset), and makes the selected HQ/event chip itself clickable to
reopen search rather than only the small "x" button, so it reads more
like the searchable dropdown pattern used elsewhere.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…400)

Move the collaborative marker create/edit popup's audit disclaimer
below the button row as a quiet caption instead of a boxed callout.
Also make the "Create layer" button show its in-flight and success
states (spinner, then a brief checkmark) instead of the form closing
the instant the request resolves.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
- SMSRecipient no longer discards the beaconContact passed to its
  constructor, so recipients added via the recipient search box keep
  their contact info instead of always ending up empty.
- Search-added recipients now build a beaconContact entry with the
  real Id/Detail/ContactTypeId/Description fields instead of the
  lossy {id, name, detail} row used for display.
- messages.js now derives the Recipients[i][Description] field from
  either FirstName/LastName (team-member contacts) or Description
  (search-added contacts), since the two contact shapes differ.
- attachSendSMSModal's job branch now sets taskId, fixing the empty
  JobId on the "Task & SMS Details" button.
- Team-member recipients start as loading so the Send button is
  disabled until each one's SMS number finishes fetching, instead of
  allowing a send before beaconContact is populated; a failed lookup
  now clears loading too so it can't lock the button permanently.
- Recipient search results now show which headquarters each contact
  belongs to.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.13 to 1.1.18.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@v1.1.13...v1.1.18)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Fix wrong apiHost passed to transport incidents ops log lookup

An extra hardcoded argument shifted every parameter in the
getTransportApiKeyOpsLog call, so it resolved the wrong ops log ID
and passed a non-function as the callback.

* remove debug
@OSPFNeighbour
OSPFNeighbour merged commit 574a7e8 into master Aug 10, 2026
2 checks passed
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