Closes#118. Unblocked by #110, which is what makes SSH on sixty devices
practical.
Arbitrary RouterOS console commands over SSH, because `:put`, `:foreach` and
`/interface print` are console syntax that the binary API cannot express, and
because a curated command library cannot cover "add a channel to LACP" or
"rewrite a WireGuard endpoint" — the things people actually asked for.
**Waves are the feature, not the safety rail around it.** A shell loop already
runs a command on many hosts, costs nothing, and prints errors as they happen.
What it cannot do is stop. Devices run together within a wave, waves run strictly
in sequence, and halt-on-failure ends the run before a mistake becomes sixty
mistakes. Without that, this would be a worse bash.
Proved on hardware rather than argued: a bad command on two devices, one per
wave, failed on the first and left the second `skipped`. RouterOS reports most
errors in output text rather than an exit code, so output is read for failure
markers — otherwise a rejected command and a successful one are indistinguishable
and the run would sail on. Conservatively: only unambiguous markers count, since
misreading ordinary output would halt a run that was working.
Change Guard wraps each device by default, so one that stops answering after the
command restores itself. It is one click to turn off, because a sharp tool that
refuses to cut is not a tool — the requester's argument, and a fair one. The
combination that gets an explicit acknowledgement is a command that can sever
management *with the guard disabled*; that is the only case where nothing catches
a mistake.
A preview names devices, wave assignment, and the reasons a command looks capable
of cutting management access. It warns, never blocks. Devices with no SSH
credential are listed before the run rather than producing a wave of identical
authentication failures.
Per-device output is captured and kept, because "it failed" without the device's
own words is not actionable, and because some commands exist purely for output.
Verified end to end against real hardware: a read-only command across two
devices, halt-on-failure stopping a bad command after one device, and a genuine
write applied under Change Guard, confirmed, then reverted — with both devices
returned to their original configuration afterwards.
Also adds services/sshExec.ts, shared with the key work: it selects a verified
key or a password, and throws rather than swallowing. That last part is
deliberate — a swallowed error is exactly how the data-cap feature spent a week
doing nothing while looking healthy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add OpenID Connect single sign-on so the platform can authenticate against
any standards-compliant provider (Entra ID, Okta, Google Workspace, Keycloak,
Authentik, …) alongside local accounts. Resolves the feature request in #73.
Flow (backend-driven Authorization Code + PKCE):
- GET /api/auth/oidc/login builds the authorize URL and stores the PKCE/nonce
transaction in Redis (single-use, 10m TTL); the callback validates the ID
token via openid-client (JWKS signature, iss/aud/exp/nonce), resolves the
identity to a local user, and mints the platform's normal session JWT —
handed to the SPA via URL fragment. No sessions/cookies introduced.
Identity mapping (users table gains email/oidc_subject/oidc_issuer/
auth_provider; password_hash now nullable; partial unique indexes):
- First sign-in auto-provisions a user, or links to an existing local account
when the IdP email is verified and matches.
- Roles derive from a configurable IdP group -> role map with a default
fallback; existing users are never auto-demoted on a groupless login.
Configuration (admin-only, no env/redeploy needed):
- Stored as an app_settings row with the client secret encrypted at rest
(utils/crypto); masked in responses and excluded from GET /api/settings.
- New UI: Settings -> SSO / OIDC panel (issuer/client, claim + group->role
mapping, provisioning toggles, discovery test, copyable redirect URI), a
login-page SSO button, and an SSO badge in Users & Roles.
Security: local break-glass login preserved; PKCE + single-use state; email
linking requires email_verified; same-site-only post-login redirect.
Adds openid-client@^5 (CommonJS). 17 new unit tests (claim/role mapping,
username derivation, config secret round-trip/masking); 114 backend tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI/UX consolidation pass to reduce nav sprawl (~24 destinations -> ~17):
- Merge Router Settings + Switch Settings (copy-paste duplicates) into one
Network Services > Discovery & SNMP page with an All/Routers/Switches
scope selector; LLDP + SNMP status tables merge both fleets with a
type pill, apply fans out per scope
- Retire Routers/Switches overview pages; the Devices type filter now
lives in the URL (?type=AP|SW|RTR) and legacy routes redirect to the
pre-filtered view. Wireless overview kept (RF Health / Rogue APs are
unique content)
- Fold Wireless Clients into Clients: All/Wired/Wireless segmented
filter (URL-synced); wireless view swaps the Type column for SSID +
signal quality columns
- Regroup sidebar into Monitor / Network / Services / Operations
sections; Devices and Wireless are collapsible groups (auto-open on
active section, manual collapse always wins); Wireless group renders
only when wireless APs exist
- Rename Syslog -> Logging with a cross-link to live Events; group the
Settings tabs into Platform / My Account / Fleet sections
- Legacy routes (/routers, /switches, /routers/settings,
/switches/settings, /wireless/clients) redirect so bookmarks keep
working
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Guest WiFi (Hotspot): one-click captive-portal wizard that creates the guest
SSID (virtual AP per radio), VLAN/bridge segregation + L3 VLAN interface, pool,
DHCP, portal, rate-limited guest profile, and masquerade; vouchers with limits +
printable sheets; live guest sessions; walled garden.
Network intelligence: per-client device fingerprinting (vendor/hostname →
category) with a persisted per-client override on the detail page; rogue/neighbor
AP detection from stored AP scans (evil-twin flagging); Mist-style anomaly
insights (client-count/CPU vs same-hour 14-day baseline + error bursts) in the
Operations engine.
Firmware orchestration: staged wave-based RouterOS rollouts (canary first) with
pre-upgrade backup, reboot-ride-out, version verification, halt-on-failure, and
scheduling; live per-device progress + cancel.
Platform & automation: scoped API tokens (mtm_, read/write, hashed at rest,
Bearer auth in requireAuth); outbound webhooks (12 events, HMAC-signed) fired
from the alert pipeline + firmware orchestrator; scheduled email reports.
api-ssl: RouterOSClient negotiates TLS on port 8729 and accepts RouterOS's
default self-signed certs (rejectUnauthorized:false), so SSL-secured API devices
connect without manual cert trust. Fixes#57.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dedicated top-level Security section plus a Meraki/UniFi-grade firewall built
on the full RouterOS feature set.
Security Center (new /security page)
- Fleet hardening scores, per-device posture list, and a "Common Findings"
rollup aggregating identical issues across the fleet
- "Manage" deep-links into the device's Security tab (?tab= support added)
Firewall & NAT (per device + in Security Center)
- Friendly rule builder: Allow/Drop/Reject chips, Any/Address/Address-List
pickers, port presets, connection-state chips, logging, live plain-English
preview
- Address lists as reusable objects, referenced from rules
- Rule reordering (/ip/firewall/{filter,nat}/move); per-rule hit counters
(print stats) with dead-rule highlighting + reset
- NAT wizards: Port Forward / Masquerade / 1:1 NAT + Custom
- Safe-apply lockout guard: backend 409 on an unscoped input drop/reject that
would sever management; UI confirms before forcing (detectLockoutRisk, tested)
Access & security
- Security posture audit: graduated, de-duplicated scoring that never proposes
disabling the management service the platform connects through (no self-
lockout); switch-aware input-firewall severity; service hardening table
- Bandwidth: simple-queue CRUD + one-click "Limit this client" on client detail
- Active connections viewer
These L3 features are available on all managed RouterOS devices (routers,
switches, APs), not gated by device type.
Backend: DeviceCollector address-list/move/stats/connections/queues/service
methods; device routes for all of the above; firewallSafety + firewallSummary
utils with unit tests (19 new).
Security: bump nodemailer 8 -> 9.0.1 to clear GHSA-p6gq-j5cr-w38f (our
createTransport/sendMail usage is unaffected by the advisory's raw-option path).
Verified live against RouterOS devices; lint/typecheck/build/jest green in both
workspaces; npm audit --audit-level=high clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New Syslog page under Network Services exposes RouterOS logging actions
and routing rules with full CRUD. Logging actions define destinations
(remote syslog server, memory, disk, echo); logging rules define which
topics route to which action.
Single Device and All Devices modes are functionally identical — All
Devices applies changes across every online device in parallel and
shows per-entry coverage badges indicating which devices have each
action or rule. Toggle enable/disable works in both modes.
Remote actions display address:port with facility/severity in a single
Destination column; memory/disk/echo rows show no irrelevant remote
fields. Syslog KPI card added to the Network Services overview.