sync: auto-sync from GURU-5070 at 2026-06-05 08:06:47

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 08:06:47
This commit is contained in:
2026-06-05 08:06:54 -07:00
parent ce9744832d
commit 90e2cb2dd7
5 changed files with 468 additions and 1 deletions

Binary file not shown.

View File

@@ -0,0 +1,257 @@
# Power Monitor Demo — Gateway & Publishing Spec
**Status:** DRAFT v0.1 (living document) · **Date:** 2026-06-05
**Owner:** Arizona Computer Guru (advisory) · **Implementer:** Georg Haubner (Dataforth)
**Subject:** Standing up a gated, proxied demo of the Power Monitor UI at `PWM.dataforth.com`
> ACG's role here is to **shape the design via feedback**, not to modify the application
> code. This document is the reference architecture we hand to Georg. The SPA itself
> ("ExternalCodeReview" build) is unchanged except where called out under
> **Feedback for Georg**.
---
## 1. Purpose & Scope
Publish a demo of the Power Monitor dashboard so **technology partners and Dataforth
staff** can run feature-testing and feedback sessions. This is **not** the shipped
client product — it is a hosted, controlled preview.
In scope: hosting model, auth, the proxy/policy gateway, per-user permissioning,
and the consolidated code-review feedback. Out of scope: rebuilding the meter
firmware API or the SPA's internals.
**Delivery model:** the shipped product is a **computer-run application** that connects to
meters over the network and displays their data — it is **not** flashed onto the meter
(hardware limits). The demo is the hosted, browser-accessible version of that same app.
This corrects the "served from embedded flash" rationale in the source's own `project.md`
(see Feedback). Practically: the **shipped product = a local app on the customer's network**
(the hardened `dev_server.exe` lineage); the **demo = the hosted version of that app** behind
our gateway. Same proxy concept, two trust levels — which is why the SSRF/open-proxy concern
is **High for the public demo** but **much lower for the local product** (own LAN, own meters).
## 2. Architecture Overview
```
Browser ──HTTPS──> PWM.dataforth.com
│ (outbound tunnel; no meter is ever publicly exposed)
┌──────────────────────────┐
│ Demo Gateway │ hosted INSIDE the Dataforth network
│ • serves static SPA │
│ • passwordless sessions │
│ • POLICY enforcement pt │
│ • proxies ONLY live mtrs │
└───────────┬──────────────┘
│ (allowlisted)
Live DTF meters (LAN)
Simulated units = generated client-side in the browser; never touch the gateway.
```
Key insight from code review: **simulated units are produced client-side**; only
**live meters** generate network traffic (`/api/Data?ip=`). Therefore the gateway's
security boundary is precisely *"what may this user do to a real meter,"* and that
must be enforced **on the wire** at the gateway — never trusting the app's
(cosmetic) client-side button gating.
## 3. Device Classes (all three visible in the demo)
| Class | Source | Marker | Reaches gateway? |
|---|---|---|---|
| **Simulated** | baked into the build | **"SIMULATION" disclaimer** (see Feedback) | No — client-side only |
| **Live (curated)** | real DTF meters, fixed allowlist | live indicator | Yes — proxied |
| **Explorable** | user-entered IP within the meter subnet | — | Yes — internal users only |
The demo data Georg built into the executable **stays**. Live units **stay visible**
so staff can validate that real readings populate the dashboard correctly.
## 3.5 Demo Behavior & Build Approach (feedback for Georg)
### Show everything; simulate what the user can't really do
For a *feedback* demo, partners and staff must SEE the full feature set — firmware,
config, and IP screens included. So non-admins are **not** shown 403 errors and controls
are **not** hidden:
- Every screen and control renders for everyone.
- Capability held → the write is **real** (proxied to the meter).
- Capability absent → the write is routed to a **client-side simulation** returning a
believable result. Full interactive experience; no real meter touched.
(IP visibility for external users is the one exception — soft-hidden per the `see_ip`
nice-to-have.)
### Build it as an isolated DEMO MODULE — not a fork
The application is **actively evolving** and the point is to test *current* features.
- **Do NOT maintain a separate demo fork** — it drifts immediately; partners would test
stale features, and two codebases must be kept in sync. Wrong cost.
- **Do add a `demo` mode driven by an isolated module** (one scoped component, enabled by
a build flag / env). It applies the SIMULATION disclaimers, reads the user's
capabilities (gateway `/me` endpoint), and provides the write-simulation shim above.
The shipped product builds **without** the module, so none of it can activate in
production.
### A clean strip point for the production build (consideration)
Georg will eventually want a production build with **no demo/mock code** in it. Since this
is a **computer-run app, not meter firmware**, the motivation is **code hygiene + safety**
(not flash size): removing any path by which fabricated values could render on the shipped
product — the flip side of the "mock engine fused into the live data path" finding.
Two clean options, his call:
- **Stay buildless** (current style) -> draw the boundary at the **file level**: extract all
sim/mock/demo logic out of `w3js_main.js` into a standalone `demo_module.js`; the demo
`index.html` includes it, the production bundle omits it. A runtime `if (DEMO_MODE)` flag
is NOT a strip — the code still ships in the bundle.
- **Add a small build step** — now a real option, since the original "no Node on embedded
hardware" rationale no longer applies. A bundler can dead-code-eliminate a `DEMO_MODE`
flag at compile time.
Either way it's the same extraction that resolves the "simulation fused into the live path"
finding — do it once, serve both demo and product. Strictly necessary is Georg's judgment;
cheap now, costly to retrofit.
### Two layers, on purpose (defense in depth)
- **Demo module (in the app) = UX layer** — show-all, simulate writes, disclaimers.
Trusted only for presentation.
- **Gateway = hard security boundary** — independently blocks any real write a user isn't
entitled to, on the wire, regardless of what the browser does. Never trusts the app.
The two are independent: even with a bug in the demo module, the gateway still prevents a
non-privileged user from modifying a real meter.
## 4. Authentication — passwordless magic links
- Admin (internal) adds a user (name + permission checkboxes); the gateway mints a
**one-time magic link**. Admin delivers it however is convenient (email, Teams,
in person). Clicking it establishes a durable signed session cookie.
- **No passwords** are ever set, stored, rotated, or emailed.
- **Revoke** = set the user inactive; the session is invalidated.
- v1 needs **no email infrastructure** — the admin copies the link. Automated send
via Dataforth M365 SMTP can be added later without changing the model.
- **Session lifetime:** Georg's call (a sane default + optional auto-expiry for
external users after the feedback window).
## 5. Authorization — `internal` is the master gate
| | External user | Internal user |
|---|---|---|
| Simulated units | full (harmless) | full |
| Live meters | **read-only, IPs hidden** | read-only + (per checkbox) write |
| See / explore / add / modify IPs | **never** | yes (within meter subnet) |
| Destructive on real meters (config/reboot/firmware) | **never** | per-user checkbox |
| Manage user list (`is_admin`) | never | internal only (Georg + Mike) |
- **All IP-related capability is gated by `internal`** — external users never see,
explore, add, or modify anything IP-related.
- The per-user checkboxes (`write_config`, `reboot_restore`, `update_firmware`) are
finer control **among internal users**, applied to real meters.
- **Visibility != capability:** non-admins **see every option**; lacking a capability
means that action is **simulated**, not hidden or errored (see §3.5).
- **New-user defaults:** `internal=false`, `is_admin=false`, all checkboxes off →
read-only + simulated units only.
## 6. User Store (gateway-owned)
```yaml
user:
name: "Jane Partner"
login_handle: jane@partner.example # used to mint/deliver the magic link
internal: false # master gate for IP + destructive surface
is_admin: false # manage user list (internal only)
active: true # uncheck to revoke
capabilities: # only meaningful when internal=true
write_config: false
reboot_restore: false
update_firmware: false
# IP-related capability (see/explore/modify) is implied by internal=true, not a flag.
```
Flat file or SQLite the gateway owns. Managed via an internal-only `/admin` UI.
## 7. Gateway Enforcement
**Request flow:**
1. Browser → tunnel → gateway. No valid session → magic-link login.
2. Gateway serves the static SPA bundle.
3. App issues `/api…?ip=` or `/Control…`. Gateway intercepts every request:
a. Resolve user + capabilities from the session.
b. **Classify the target IP:** curated-live / explorable-subnet / out-of-range.
c. Apply policy (table below). Deny → `403 "not permitted in this demo."`
d. Allowed → proxy to the real meter; relay response.
**Policy (live-meter traffic only):**
| Action | Endpoints | Allowed for |
|---|---|---|
| Read telemetry/config | `GET /api/*`, `GET /Control` | everyone (curated-live); internal (explorable) |
| Target an explorable IP | any, with `?ip=` outside curated list | **internal only**, and **only within the meter subnet allowlist** |
| Write config | `PUT/POST /api/Config/*` | internal + `write_config` |
| Reboot / restore | `POST /Control/SystemRestart`, `/Control/RestoreToDefault` | internal + `reboot_restore` |
| Firmware update | `PUT /Control/FirmwareUpdate`, `POST /UpdateFile/*` | internal + `update_firmware` |
**SSRF prevention:** the gateway proxies **only** to IPs inside a configured **meter
subnet allowlist** — never arbitrary hosts. This structurally eliminates the open-proxy
behavior in the current `dev_server.py`. External users are further restricted to the
**curated** live list (a subset of the allowlist).
## 8. Hosting & Network
- **Host the gateway INSIDE the Dataforth network** (small VM/container on the meter
VLAN) so it can reach live meters directly.
- **Expose only the gateway** publicly via an **outbound tunnel** (Cloudflare Tunnel
or ACG NPM) → `PWM.dataforth.com`. No meter is ever directly reachable from the
internet. This is the correct answer to "need an internal IP available for an
external connection" — publish the *gateway*, not a meter IP.
- **TLS:** terminated at the tunnel/edge (Cloudflare or Let's Encrypt).
- **DNS:** `PWM.dataforth.com` → edge → tunnel → gateway.
## 9. Build / Publish
The SPA is static; "build" = produce a clean bundle:
- Remove dev/forensic artifacts: `search_transcript_*.py`, `query_all_devices.py`,
and the `original_ui/` duplicate tree (also leaks internal meter IPs + a developer
username — must not ship externally).
- Pin CDN dependencies (jQuery, Chart.js) **locally** for reliability and offline
operation (a customer-site app shouldn't depend on public CDNs).
- Gateway serves the bundle; live data flows through the policy proxy.
## 10. Feedback for Georg (consolidated from the code review)
**A. Demo-publish items**
1. **Simulation disclaimer** — mark simulated units with a clear "SIMULATION —
representative of real product data" badge. (Keep the demo data; just label it.)
2. **No open proxy** — the public demo must use the allowlisted gateway, never the
current `dev_server.py` (binds all interfaces, proxies arbitrary `ip=`).
3. **Strip dev/forensic artifacts** before any external handoff (see §9).
4. **Enforce permissions at the gateway**, not via client-side button-disabling
(which is cosmetic and bypassable).
**B. Product-ship items (for the eventual client product, not the demo)**
- On-device **TLS** (login/creds/SMTP currently traverse plain HTTP).
- **Firmware image signature verification** on the meter (the UI validates nothing).
- Confirm `RestoreToDefault` is server-authorized (callable without a session client-side).
- **Escape device-supplied strings** before `innerHTML` (stored DOM-XSS via TagName/name).
- Replace the **numeric session token** with an opaque/expiring token; add idle timeout.
- De-duplicate the hardcoded IP allowlists (copied across 56 files) and collapse the
`original_ui/` + `_v2` duplicate trees.
- Separate the client-side mock engine from the live data path so real meters never
show fabricated values.
## 11. Open Parameters (need Georg / Dataforth input)
1. **Curated live-meter allowlist** — which real meters are visible in the demo
(names + IPs), and which are external-visible vs internal-only.
2. **Meter subnet allowlist** — the IP range `explore_ip` (internal) may target.
3. **Session lifetime** — Georg's call (+ external auto-expiry?).
4. **Magic-link delivery** — manual copy for v1, or wire M365 SMTP now?
5. **Hosting specifics** — VM/container location on the DTF network; tunnel choice
(Cloudflare Tunnel vs ACG NPM).
## 12. Future / Out of Scope (noted, not required for the demo)
- **Opaque device handles** (gateway issues IDs; maps ID→IP) for *true* IP hiding
from external users — deferred; v1 uses simple UI-hiding (`see_ip` nice-to-have).
- On-device TLS, firmware signing, full session hardening — product-phase.
---
*Living draft — iterating with Mike before handoff to Georg.*

View File

@@ -0,0 +1,73 @@
# Power Monitor Demo — Questions for Georg
**Status:** DRAFT (for Mike's review before sending) · **Date:** 2026-06-05
Companion to `GATEWAY-SPEC.md`. Where we have a recommendation it's noted as **(our take)**
so Georg can confirm or redirect rather than start from scratch.
---
## A. Intent, audience, timeline
1. **Feedback format** — is a session a live demo you walk partners through, or self-service
access they poke at on their own? (Drives how much auth friction is acceptable.)
2. **Testers** — roughly how many, and the internal/external split? Any names/emails you
already know, so we can size the user list?
3. **Timeline** — when do you want `PWM.dataforth.com` reachable for the first round?
4. **Shipped product form** — is the eventual customer product a **packaged desktop app
they install** (the `dev_server.exe` lineage) or an **on-prem server** they browse to?
Doesn't block the demo; it shapes the product-side advice (install/update, where auth lives).
## B. Hosting & access (the "external connection" from your email)
5. Your *"need an internal IP available for an external connection"* note — what did you
picture for letting outside partners reach this? **(Our take:** host a small gateway
**inside** your network and tunnel it out to `PWM.dataforth.com` — no meter is ever
exposed publicly. Does that fit?)
6. Where can a small always-on box (VM/container) live **on the meter network** to host
that gateway?
7. Is `dataforth.com` on **Cloudflare** (we'd use a Cloudflare Tunnel), or should we front
it with **our reverse proxy / NPM** for the tunnel + TLS?
## C. Meters & demo data
8. **Which real meters** should appear in the demo (names + IPs)?
9. For `explore / add a meter by IP` (internal users only), **what IP range** should that
be limited to?
10. **Simulated units stay**, with a clear **"SIMULATION — representative data"** marker —
agreed? **(Our take:** yes — keep them, just label them.)
11. Should **external partners see live meter *data*** at all (IPs always hidden), or should
externals be **simulation-only**? You mentioned staff validating live data — confirming
whether that extends to outside partners.
## D. Users & permissions
12. Confirm the model: **simple self-managed user list** (no AD/SSO), **passwordless
magic-link** logins, **you + Mike as admins**. **(Our take:** yes.)
13. Per-internal-user destructive toggles we'd gate: **firmware update, config write,
reboot/restore**. Anything else you want as a switch (e.g. CSV/data export, clearing
event logs)?
14. Should **firmware update even be reachable** in the demo (as a *simulated* screen so
partners can give feedback on it), or **hidden entirely**?
15. **New external users default to read-only + simulated-only** — agreed?
## E. Code structure & build (so the demo doesn't fight the product)
16. Open to **extracting the simulation/mock logic out of `w3js_main.js` into a standalone
"demo module"** — so the same code drives the demo *and* can be cleanly omitted from a
production build? **(Our take:** yes; it also resolves the "mock data fused into the live
path" item.)
17. **Buildless** (include/omit the demo file) vs **adding a small build step** — preference?
Note: since this is **never getting flashed onto the meter**, the "no build tools"
rationale in your `project.md` no longer applies — a build step is back on the table if
you'd find it useful.
18. How are you iterating with **Antigravity** — should the demo **always track your latest
build**, or be a **pinned snapshot** per feedback round?
## F. What you want from ACG
19. Concretely, what's our role: **(a)** stand up + host the gated demo gateway, **(b)** a
formal external code-review writeup, **(c)** advise/shape only — or some combination?
---
### Heads-up worth mentioning to Georg regardless
- `project.md` still justifies the whole vanilla-JS / zero-dependency design on *"serve from
embedded hardware flash."* If it's never flashed, that rationale is outdated — which quietly
**reopens technology choices** (a light framework or build step) he may have ruled out.
- The package as-sent contains dev/forensic artifacts (Gemini "Antigravity" transcript
scrapers with his local path, a script listing live internal meter IPs) and an
`original_ui/` duplicate — none of which should travel in an external deliverable.