New clients join a shared 'open' role (pub/sub on #, $CONTROL stays protected)
instead of per-client topic-scoped roles. All passwords are now stored encrypted
with admin reveal + reset endpoints so one login can be reused across device
batches. Username is the name slug (no random suffix). Legacy scoped clients
can be moved onto the open role.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
First guess (Forecast key -> hourly specifically) was tried and confirmed
wrong live. Rather than guess again, the test route now checks every
configured key against every endpoint and reports the full 2x3 matrix, so
the actual working combination is visible on screen instead of assumed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Met Office DataHub sells 'Global Spot' and 'Forecast' as separate
subscriptions with separate keys. api_key (existing) is Spot, used for the
daily endpoint (confirmed live via hvac). forecast_api_key is new and
optional -- weather's hourly/three-hourly endpoints try it first, falling
back to api_key, then Open-Meteo, per tier.
Test route now checks each key against the endpoint it's actually used for
(Spot -> daily, Forecast -> hourly) rather than only ever testing api_key
against daily.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two new schema-driven integrations for hvac's weather compensation
feature: hotel_location (lat/lon, plaintext config) and met_office
(encrypted api_key). Both surface automatically on the portal's
Integrations tab via the existing generic integration UI/routes — no
portal changes needed. Deliberately central here rather than hvac-local
so other apps can reuse the hotel's location later. Added a met_office
connection test (DataHub daily-forecast call) alongside the others.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A single client sometimes legitimately needs several unrelated topic trees
(e.g. hvac-backend needs both shellies/# for TRVs and hvac/mhi/# for MHI
aircon) — the previous model only supported one topic_scope per client,
forcing either an overbroad '#' grant or multiple broker logins for one app.
topic_scope is now newline/comma-parsed into a list; each pattern gets its
own ACL grant on the client's role (createDynsecClient loops per topic).
Stored newline-joined (still a single TEXT column, no migration). Added
PATCH /mqtt-clients/:id (additive: add_topic_scope) to grant a client more
topics later without revoking/recreating it — backed by a new
addTopicsToRole() in mqtt-dynsec.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MQTT Broker Clients gained a 'service consumer' flag. Device clients stay
show-once (a human copies the password into hardware). Service clients (a
stack app like hvac-backend that must re-read its own broker login on every
restart) get a STABLE username (= name slug, no random suffix) and their
password stored AES-256-GCM encrypted (crypto.js, same as integration
secrets), served at runtime via:
GET /settings/api/internal/mqtt-client/:username (bearer SETTINGS_SECRET)
This is the credential path the hvac plan always intended — show-once
genuinely can't feed a machine consumer. Revoke/delete drop the stored
secret; the internal endpoint refuses revoked clients; secret_enc is never
returned to the admin UI. Reject duplicate usernames up front with 409.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-app MQTT slugs meant a confusing mix of shared and per-app
credentials in one settings page. Consolidating on a single shared
identity instead — matches the original "MQTT is shared infra"
decision, and publish access stays scoped per-device regardless.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets plant have its own dedicated MQTT credentials instead of sharing
the single central mqtt integration with utilities/hvac, so its
dynsec client can be scoped to plant/# without touching other apps'
access. Generalizes the test-connection dispatch to match any
mqtt_* slug, not just the literal 'mqtt' one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New /settings/api/mqtt-clients routes (list/create/revoke/delete) manage
per-consumer dynamic-security identities on the shared broker (LXC 104) —
name + topic scope + publish/subscribe flags in, generated username/password
out (shown once, never stored). Replaces manually running mosquitto_ctrl
over SSH by hand for every new device or app that needs broker access.
Implementation SSHs into the broker LXC and runs mosquitto_ctrl inside a
throwaway container on its Docker network (src/lib/ssh.js + mqtt-dynsec.js)
rather than reimplementing the dynamic-security plugin's JSON wire protocol
from scratch — reuses the exact commands verified by hand while wiring up
the water-softener and mqtt-inspector clients this session. Needs the
shared deploy SSH key mounted (Dockerfile/compose changes) and
MQTT_ADMIN_USER/PASS threaded in via stack-init.
mqtt_clients table is bookkeeping only (name/scope/active) — the broker's
own dynamic-security.json remains the source of truth for auth.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
schema.js already had an uncommitted mqtt entry (host/port config,
username/password secrets) from a prior session — commits it now, adds
the missing test-connection case (routes/integrations.js), and the mqtt
package dependency it needs. The existing generic integration card UI in
portal's AdminSettings.tsx needs no changes — it renders any schema
entry automatically. initDb() auto-seeds the 'mqtt' row on next restart,
so the "MQTT Broker" card will appear in Settings -> Integrations.
Also adds a .gitignore — node_modules was untracked in this repo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets the Claude API key be managed once in Portal → Settings →
Integrations and shared across apps (forecasting, wages), instead of
each app storing its own copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registers hosted_tables in the integration schema with a single site_url config
field. On save, syncHostedTables() PATCHes auth to update restaurant_bookings
base_path and active state, so the portal app appears only when a URL is set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /settings/api/integrations/nextcloud/browse?path=<path>
Uses stored Nextcloud credentials to PROPFIND the given path and
returns a sorted list of subdirectories for the UI picker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows a Nextcloud subdirectory to be specified as the target for
stack-level backups run by the management backup service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Repurpose the azure slot from Azure AD (tenant_id/client_id/client_secret)
to Azure Document Intelligence (endpoint/api_key), matching the kitchen
app's OCR credential fields. The kitchen can now delegate its OCR credentials
to the central settings service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a successful PUT to /integrations/workforce or /integrations/smtp,
fire-and-forget POST to auth's /internal/cache-bust so credential changes
(including default location) propagate instantly without a container restart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds bearer_token secret field — if configured, the auth service uses it
directly and skips the OAuth password grant entirely. Falls back to
email/password flow if no token is set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows other services (e.g. room-planner) to read shared config
stored in settings_db without cross-database queries. Auth via
SETTINGS_SECRET bearer token, same pattern as /internal/integration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config object starts empty so the frontend had nothing to render for
plaintext fields (region, username etc). Now populates all schema-defined
fields with empty string when not yet saved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fastify API for storing third-party integration credentials (Newbook, Resos,
Nextcloud, Azure, SambaPOS) with AES-256-GCM encryption for sensitive fields.
Includes Newbook room sync endpoint and global_config store for shared app data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>