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:
BIN
clients/dataforth/ExternalCodeReview.zip
Normal file
BIN
clients/dataforth/ExternalCodeReview.zip
Normal file
Binary file not shown.
257
clients/dataforth/power-monitor-demo/GATEWAY-SPEC.md
Normal file
257
clients/dataforth/power-monitor-demo/GATEWAY-SPEC.md
Normal 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 5–6 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.*
|
||||
73
clients/dataforth/power-monitor-demo/QUESTIONS-FOR-GEORG.md
Normal file
73
clients/dataforth/power-monitor-demo/QUESTIONS-FOR-GEORG.md
Normal 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.
|
||||
Reference in New Issue
Block a user