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>