Commit Graph

10 Commits

Author SHA1 Message Date
mike 62a8870ea2 Implement user login logging
build / build-linux-amd64 (push) Successful in 1m54s
2026-05-22 20:10:11 +02:00
mike 6a0b698384 Implement remote execution
build / build-linux-amd64 (push) Successful in 1m58s
2026-05-22 14:18:48 +02:00
mike 475da0e950 Implement signed API communication to improve security
build / build-linux-amd64 (push) Successful in 1m50s
2026-05-22 13:05:50 +02:00
mike e22e4f6fb6 Implement RUSTDESK_UNATTENDED_PWD_VISIBILITY to enable visibility of unattended passwords within the Admin UI even when User is logged in.
build / build-linux-amd64 (push) Successful in 1m45s
2026-05-18 18:25:29 +02:00
mike 5ec9776207 Documentation of available strategy keys (Rustdesk Client)
build / build-linux-amd64 (push) Successful in 1m48s
2026-05-18 18:23:21 +02:00
mike 8ad3f43d21 ci(linux): add build workflow + Docker build instructions
build / build-linux-amd64 (push) Successful in 1m48s
2026-05-07 09:53:29 +02:00
mike 7e2c7a7e4c docs: refresh CONFIGURATION.md — TOTP self-service, new routes, bind flags
Caught up the docs to match what the dashboard actually does. Four spots
were stale enough to be misleading.

- TOTP / 2FA section rewritten. The doc still claimed admins enrolled
  TOTP from the Users action menu, but that button was removed when
  TOTP enrollment moved to the self-service profile page (two-step
  with QR + 6-digit confirmation; nothing written to user_totp_secrets
  until the user proves they have a working authenticator). Admins can
  disable a user's TOTP but can no longer enroll on someone's behalf.
  Also called out that OIDC-linked users skip local TOTP — their MFA
  lives at the IdP.

- Admin dashboard URLs table was missing nine routes that exist
  today: /admin/assets/{tailwindcss,htmx.min}.js (vendored CDN
  assets), /admin/pages/profile + four sub-routes (self-service
  profile flow), /admin/connect/:peer_id, and the two web-client SPA
  asset routes. Updated the Users-page row to mention the inline
  edit-profile + TOTP-disable controls.

- CLI flags / HTTP API & dashboard table now lists --http-listen and
  --ws-listen (they previously only appeared inside the nginx
  subsection — discoverability matters when an operator scans the
  flag tables looking for what's available). Added a one-liner about
  hbbr's matching --ws-listen flag.

- Security checklist gained a bind-flags hardening tip
  (--http-listen=127.0.0.1, --ws-listen=127.0.0.1 on both daemons
  when fronted by nginx) and a note about forwarding
  X-Forwarded-Proto: https so the dashboard generates wss:// URLs.

Sections cross-checked and confirmed accurate as-is: OIDC walk-through
+ role sync + troubleshooting, strategies, address books, recordings,
audit retention, SMTP, web client (routes / browser reqs / codec /
HUD diagnostics / build), database / backup notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:27:32 +02:00
mike aa40784dc6 feat(deploy): bind-address flags for browser-facing ports + nginx docs
By default hbbs and hbbr bind every port to the wildcard, which collides
with operators wanting to put nginx/Caddy in front of the dashboard
(443) and the two browser-facing WebSocket ports (21118 rendezvous,
21119 relay) for TLS termination. Operators reported having to choose
between exposing hbbs directly (no TLS for `wss://`, breaks browsers
since the page is HTTPS) or moving the daemon to a different port.

New flags:
- hbbs `--http-listen=<HOST>` pins the HTTP API + dashboard port.
- hbbs `--ws-listen=<HOST>`   pins the WS rendezvous port (port + 2).
- hbbr `--ws-listen=<HOST>`   pins the WS relay port (port + 2).

All default to the wildcard (current behaviour). Set to `127.0.0.1` to
free up the corresponding public port for nginx.

The plain TCP/UDP ports used by desktop clients (21115 NAT test, 21116
rendezvous, 21117 relay) intentionally stay on the wildcard — desktop
clients bring their own framing + secretbox encryption and don't go
through nginx.

Implementation: a small `bind_tcp_listener(host, port)` helper in
common.rs that falls through to the existing `listen_any` when host is
empty, otherwise binds explicitly. Reused for both ws_port (rendezvous +
relay) and the http_port; the latter just builds a `SocketAddr` inline
since axum::serve takes one.

Documentation: new "TLS deployment with nginx" section in
docs/CONFIGURATION.md covering the port plan, the bind flags, full
example nginx vhost config (three server blocks: 443 dashboard,
21118 WSS rendezvous, 21119 WSS relay) with the WebSocket Upgrade
plumbing and bump-up timeouts that long sessions need, plus the
firewall list and the four common failure modes (SSL protocol error,
connection refused, 502, hung 200 instead of 101).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:43:20 +02:00
mike 4308a2f112 feat: M6 web client QoL — clipboard, multi-monitor, quality, reconnect, H.264, docs
Builds on the M6a-g MVP (d07e98e) with five user-facing features that
take the in-browser remote-control client from "demo toy" to "workable
for daily use", plus user-facing documentation.

M6h — Text clipboard sync (both directions)
- Host → browser via Clipboard{format: Text} → navigator.clipboard.writeText.
- Browser → host via Cmd-V/Ctrl-V keydown intercept → navigator.clipboard
  .readText → Message{clipboard}, sent BEFORE the V keystroke so the
  host's paste hotkey lands on the freshly-synced text.
- Handles both single-format Clipboard (older peers) and MultiClipboards
  wrapper (peers ≥ 1.3.0; gated by clipboard.rs:is_support_multi_clipboard).
- Switched printable-with-modifier hotkeys (Ctrl-C etc.) from Unicode to
  Chr payload — host's process_unicode does key_sequence(char) which
  ignores modifiers, breaking copy/cut; process_chr respects them.
- Firefox refuses navigator.clipboard.readText() by default — accepted as
  a known browser limitation, host → browser direction works regardless.

M6i — Multi-monitor switching
- HUD picker shown when peer_info.displays > 1.
- On change: SwitchDisplay + CaptureDisplays{set:[idx]} two-message dance
  — required for clients ≥ 1.2.4 (we send "1.4.0"). Without the follow-
  up, switch_display_to leaves both video services subscribed and
  switching display 0 → 1 → 0 doesn't restore display 0.
- Mouse coords offset by the active display's virtual-desktop origin
  (DisplayInfo.x, .y). Without this, clicks on display 2 landed on
  display 1 because both share canvas (0,0) but only display 1 has
  origin (0,0) in virtual-desktop space.

M6j — Quality / FPS / mute controls
- Image quality preset (Low/Balanced/Best) → Misc{option: {image_quality}}.
- Custom FPS (15/30/60) → Misc{option: {custom_fps}}; host caps at 30
  unless allow_more_fps is advertised.
- Mute toggle additionally sends Misc{option: {disable_audio: Yes/No}}
  so the peer stops encoding audio while muted (saves CPU + bandwidth).

M6k — Auto-reconnect on transient drops
- session.recv() throw → reconnect with exponential backoff: 1s, 2s, 4s,
  8s, 16s, 30s, 30s, capped at 30s, max 10 attempts.
- Dim overlay sits on top of the canvas during retry; canvas keeps
  last-known frame for visual continuity.
- Auth errors (password/signature) bail immediately — no point retrying.
- User options (mute, image_quality, custom_fps, current display)
  re-applied to host on each successful reconnect, since host treats
  every session as fresh and resets to defaults.
- Architecture: `session` is a let-binding mutated on reconnect; HUD
  button closures read it at click-time so they automatically retarget.
  Input modules (mouse/keyboard/clipboard) get a Proxy that forwards
  method calls to whatever session is current — avoids re-binding
  window/canvas listeners on each reconnect.

M6l — H.264 video decode (Annex-B + SPS-derived codec string)
- decode/bitstream.ts: iterate Annex-B NAL units, derive avc1.PPCCLL
  from the keyframe's inline SPS (host's hwcodec defaults to high
  profile; a hardcoded baseline string would make WebCodecs refuse the
  stream).
- Defer H.264 decoder configure until first keyframe arrives.
- VP9 codec string corrected from level 1.0 (vp09.00.10.08) to level
  5.0 (vp09.00.50.08) — wrong level was probably forcing software
  decode in some browsers.
- Default prefer flipped to VP8 (cheapest software encoder; H.264 path
  stays implemented for hosts with hwcodec/nvenc).

M6m — docs/CONFIGURATION.md "Web client" section: routes, browser
matrix, network requirements (relay reachability + reverse-proxy WS
upgrade), feature status table, codec selection rationale, the
recv/dec/draw HUD diagnostic, build commands.

Bundle: 535 KB / ~75 KB gzipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:43:23 +02:00
mike 98b55e138e feat(admin): OIDC sign-in, role sync, deploy/delete UX, and docs
This commit lights up the missing pieces of the admin dashboard and the
OIDC flow that the desktop client already speaks. It bundles several
independent fixes that share enough touch points (oidc/callback,
admin/mod, database schema) that splitting was more churn than help.

OIDC — desktop client
- /api/login: when TOTP is enrolled, return type:"email_check"
  + tfa_type:"tfa_check" instead of type:"tfa_check". The Flutter
  client's switch only branches on access_token / email_check; the
  prior shape silently fell into "bad response from server".
- /api/login dispatcher: route the second leg to login_tfa_code when
  tfaCode + secret are both present, regardless of the declared type.
  The desktop client sends type:"email_code" for both email-code AND
  TOTP second legs and distinguishes by which field is set.
- /api/oidc/auth-query: drop the bogus extra {"body": "..."} envelope.
  The desktop client's http_request_sync already wraps every response
  in {status_code, headers, body}, and HbbHttpResponse::parse expects
  the auth payload at that level. Our extra envelope made the parser
  fail silently as DataTypeFormat and the poll loop spun until the
  180 s client timeout.
- UserPayload: add a required info: {} field; the Rust-side polling
  deserializer at src/hbbs_http/account.rs expects it (no
  #[serde(default)]). Without it the AuthBody parse failed on every
  poll, producing the same forever-pending symptom as above.
- Add an always-on info-level log line at the poll handler so this
  family of "client never advances" bugs is observable from hbbs.log.

OIDC — admin dashboard
- New unauthenticated entry points:
    GET /admin/oidc/providers      JSON list for login.html
    GET /admin/login/oidc/:provider 302 → IdP authorization endpoint
  The session is marked admin-flow via a sentinel ("__admin_ui__") in
  client_id_str / client_uuid so the existing /oidc/callback can tell
  it apart from a desktop device flow.
- /oidc/callback finishes admin sessions by setting the
  rd_admin_session cookie + 303 to /admin/. Non-admin users get a
  helpful error page instead of a session.
- Admin-flow callbacks SKIP device_claim() so the dashboard sign-in
  no longer inserts a phantom "__admin_ui__" device row in
  device_sysinfo, and the token's peer_id / peer_uuid columns stay
  blank instead of carrying the sentinel.
- admin_ui/login.html fetches the providers list on load and renders
  one button per enabled provider beneath the password form.

OIDC — role-based admin sync
- New per-provider config fields admin_role + roles_claim (in
  oidc.toml AND oidc_providers, via soft ALTER TABLE). When set, the
  callback evaluates the userinfo claim and forces users.is_admin
  accordingly on every login. Promotion AND demotion at the IdP
  propagate. Two claim shapes supported:
    - object key match  (Zitadel:
        urn:zitadel:iam:org:project:roles -> { "admin": {...} })
    - string-array contains (generic: roles -> ["admin","user"])
- user_upsert_oidc gains a desired_admin: Option<bool> arg so the same
  upsert path handles "leave admin alone" (desired_admin = None) and
  "force from IdP" (Some(bool)). Three unit tests cover both shapes
  plus the missing-claim case.

Admin dashboard — Address books
- Full CRUD for shared books from the dashboard:
  create, list shares, add/upgrade/remove a per-user share with
  read / read+write / full rules, delete the book.
- Personal books also get a Delete action — confirms with a stronger
  message that the user's desktop client will recreate an empty
  personal book on next sync if it's still signed in (deletion is
  effectively "reset to empty", not "permanently revoked"). Use in
  combination with user-delete to fully revoke.
- New DB methods: ab_create_shared, ab_delete (cascades peers/tags/
  peer_tags/shares), ab_get_owner_kind, ab_list_shares, ab_share_set
  (idempotent upsert), ab_share_remove.

Admin dashboard — Devices
- Delete action in the per-row menu. device_delete cascades through
  device_sysinfo, peer (rendezvous identity), heartbeat_commands and
  peer-scoped strategy_assignments. Audit logs, recordings, and AB
  entries that reference the peer are intentionally preserved
  (historical/manual data).

Admin dashboard — Deploy page
- New page that generates the unsigned CustomServer blob the desktop
  client accepts via `rustdesk --config <blob>` (see
  rustdesk/src/custom_server.rs:get_custom_server_from_config_string;
  the unsigned-JSON path is a real codepath, no Pro signing key
  needed). Form prefills the public key from id_ed25519.pub in CWD.
- Also emits the equivalent renamed-installer filename
  (rustdesk-host=...,key=... .exe). Strips api= from the filename
  when it equals the default http://<host>:21114 (Windows can't store
  : or / in filenames); warns when the API URL is non-default.

Login form fixes
- TOTP form field: rename serde wire field from tfa_code to tfaCode
  so the dashboard's HTMX form (input name="tfaCode") actually
  populates it. The previous mismatch silently dropped the code and
  the server kept asking for it.
- TOTP redirect guard: only redirect on empty 2xx body (real login).
  The TOTP-required path returns 2xx with an HTML prompt fragment
  that must NOT be redirected away from.

Docs
- New docs/CONFIGURATION.md covering all CLI flags, OIDC setup
  (generic + Zitadel walk-through), role-based admin sync, TOTP,
  strategies, address books, dashboard URL map, DB notes, and a
  pre-prod security checklist.

Schema
- soft ALTER TABLE oidc_providers ADD COLUMN admin_role / roles_claim
  (guarded by the duplicate-column-name swallower for SQLite < 3.35).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 01:05:52 +02:00