Commit Graph

58 Commits

Author SHA1 Message Date
mike d3c1128f23 Fix docker image caching. Add version indicator to admin UI
build / build-linux-amd64 (push) Successful in 1m57s
2026-05-24 23:02:34 +02:00
mike 4ccfe7a0e6 feat(admin): user-administration QoL — last-seen, profile page, OIDC awareness
Bundles the dashboard improvements that landed since 782e4c5 into one
commit. None of these change wire protocols or DB schema; they're all
UI + handlers on top of existing tables.

Users page (/admin/#users)
- "Last seen" column derived from MAX(tokens.last_used_at) per user
  (single GROUP BY query in users_last_seen_map). Shows relative
  short-form ("5m ago", "3h ago", "2d ago") with the absolute UTC
  timestamp in the cell title= for hover.
- Per-row dropdown gains an inline "Edit profile" form (display name,
  email, Save) so admins can edit other users' info without going
  through the self-service profile.
- "Enroll TOTP" button removed from the dropdown — TOTP enrollment is
  now self-service only, so admin-side enroll (which generated a secret
  out-of-band with no QR/confirm) is dead UX. "Disable TOTP" stays,
  shown only when the user has it enrolled, with hx-confirm.
- Per-row action popover (the ··· menu) now closes on outside click,
  via a global handler in index.html that targets details.relative.
  Deploy page's collapsible help section is unaffected (no `relative`).

Self-service profile page (/admin/#profile)
- New page accessible to any signed-in user (no admin gate). Sections:
  * Profile info — display name, email
  * Change password — requires current password + new + confirm
  * Two-factor authentication — enroll/disable
- TOTP enrollment is two-step with QR confirmation. POST .../totp/start
  generates a fresh secret, renders a server-side SVG QR code (new
  `qrcode` crate dependency, no_std SVG renderer) plus the manual-entry
  base32 secret. The secret rides in a hidden form field; nothing is
  written to user_totp_secrets until the user submits a valid 6-digit
  code at .../totp/confirm. Wrong code re-renders the same QR with a
  "code didn't match" notice so the user can retry without re-scanning.
- TOTP removal requires the current password.
- Sidebar now has a "My profile" link at the bottom.

OIDC linkage awareness
- UserRow exposes oidc_subject (was already in schema, just not surfaced
  in the struct). UserRow::is_oidc_linked() returns true for non-empty.
- Admin Users page: for OIDC-linked rows the password-set form is
  replaced by a small italic note ("Linked to OIDC — password sign-in
  is disabled."). Server-side, reset_password also rejects with the
  same message — UI hide is cosmetic; the handler check is the actual
  guarantee.
- TOTP column doubles as an auth-path indicator: OIDC-linked users get
  a cyan "OIDC" badge instead of (or in preference to) the violet
  "enrolled" badge.
- Self-service profile page: change-password and TOTP sections become
  short notes ("Your account signs in via the identity provider …" /
  "MFA is managed by your identity provider") for OIDC users.
  change_password handler also short-circuits with the same message.

Login page error fragment
- The auth handler returns 401 with an HTML body for bad credentials /
  disabled / not-admin / bad-TOTP, but HTMX skips the swap on 4xx by
  default — so login errors silently never appeared. Form now has
  hx-on::before-swap that forces shouldSwap=true and clears isError on
  4xx, but only for this form (page-level htmx:responseError handler
  that bounces 401s to /admin/login.html still applies elsewhere — it
  wouldn't loop here since this form sets isError=false).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:55:29 +02:00
mike 8ecf05b106 feat: HTTP-over-rendezvous fallback (HttpProxyRequest)
Closes the M4 plan. When `OPTION_USE_RAW_TCP_FOR_API=Y` (typical in
locked-down networks where direct HTTPS to port 21114 is blocked), the
client wraps every /api/* request in an HttpProxyRequest protobuf and
ships it over the already-encrypted rendezvous TCP channel. We now decode
those messages on hbbs and dispatch them through the *same* axum Router
the HTTPS listener uses — so every existing handler (login, AB, audit,
TOTP, OIDC, devices/cli, plugin-sign, …) is reachable through this path
with zero per-route plumbing.

Components
==========
- libs/hbb_common (submodule, pro-features-httpproxy branch): backports
  HeaderEntry / HttpProxyRequest / HttpProxyResponse + union tags 27/28
  from upstream @87b11a7 onto our pinned @83419b6. Proto-only — the rest
  of hbb_common is unchanged so we keep the tokio 1.x / axum 0.5 / pinned
  reqwest fork intact (a full submodule bump risked breaking those).
- src/api/http_proxy.rs: the dispatch shim. Holds a `Mutex<Option<Router>>`
  populated by `api::serve` before the HTTPS listener starts, builds an
  `http::Request<Body>` from the proto fields (sanitizing hop-by-hop
  headers, defaulting Content-Type: application/json), runs it through
  `router.oneshot(req)`, and serializes the response into HttpProxyResponse.
  Tower added as a direct dep with the `util` feature for ServiceExt.
- src/api/mod.rs: pub mod http_proxy; install_router(app.clone()) before
  axum::Server::bind to share the router.
- src/rendezvous_server.rs::handle_tcp: new match arm right before the
  catch-all that decodes HttpProxyRequest and replies with an
  HttpProxyResponse via the existing Sink::TcpStream(..., Encrypt) path.
  The reply is automatically secretbox-sealed by `send_to_sink`, so the
  end-to-end channel is encrypted symmetrically with secure_tcp.
- examples/http_proxy_test.rs: end-to-end smoke test that opens a TCP
  connection, walks the secure_tcp handshake by hand (read server's
  signed box pubkey, derive symmetric key, send sealed reply), then
  ships an HttpProxyRequest GET /api/login-options and verifies the
  response is 200 + ["account"]. Used as the validation gate.

New crate deps
==============
- tower = "0.4"     (features = ["util"]) — for ServiceExt::oneshot
- http-body = "0.4" — for the Body trait import in dispatch

Verification
============
1. cargo build --release — clean.
2. examples/http_proxy_test against a fresh hbbs:
   [ok] secure_tcp handshake complete
   [ok] sent HttpProxyRequest GET /api/login-options
   [ok] response status = 200
   [ok] response body   = ["account"]
   [pass] full HTTP-over-rendezvous round trip verified
3. hbbs log confirms the secure_tcp handshake completed and the dispatch
   went through the standard axum router.

Notes on cherry-pick vs submodule bump
======================================
The plan flagged the bump as the riskiest M4 item because newer
hbb_common pulls newer tokio that breaks axum 0.5. The proto-only cherry
pick keeps everything stable; the upstream-divergence cost is one extra
commit in the hbb_common submodule that we own.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:29:46 +02:00
mike 3e89d61566 feat: add Pro-equivalent management API on top of OSS hbbs
Brings the rustdesk-server up to feature parity with RustDesk Server Pro for
the API surface the desktop client expects (CONSOLE_API.md). Implemented as
an in-process axum router mounted by hbbs alongside its existing
rendezvous + relay TCP/UDP/WS listeners; everything persists in the existing
SQLx + SQLite database via additional CREATE TABLE IF NOT EXISTS migrations.

================================================================================
M1 — Auth foundation + heartbeat + sysinfo
================================================================================
- New tables: users, tokens, device_sysinfo.
- Endpoints: HEAD+GET /api/login-options, POST /api/login, POST /api/logout,
  POST /api/currentUser, POST /api/heartbeat, POST /api/sysinfo_ver,
  POST /api/sysinfo.
- Bearer-token auth: tokens are 32 random bytes (base64url); only the
  sha256 of the token is stored. `tokens.last_used_at`/`expires_at` slide
  forward on every authenticated request (30-day TTL by default).
- Bcrypt-cost-10 password hashing, always wrapped in
  tokio::task::spawn_blocking to keep the runtime responsive.
- New CLI flags --http-port, --bootstrap-admin-username,
  --bootstrap-admin-password.
- Heartbeat returns the `sysinfo: true` flag on first contact and after
  cfg.sysinfo_ver bumps; sysinfo upload returns the bare-string body
  ("SYSINFO_UPDATED" / "ID_NOT_FOUND") the client expects.

================================================================================
M2 — Address book, device groups, accessible peers
================================================================================
- New tables: address_books, address_book_shares, address_book_peers,
  address_book_tags, address_book_peer_tags, device_groups,
  device_group_members. Soft-ALTER adds device_sysinfo.user_id (the
  binding from a device to its enrolled user, set by /api/login).
- Endpoints: POST /api/ab/settings, POST /api/ab/personal,
  POST /api/ab/shared/profiles, POST /api/ab/peers, POST /api/ab/tags/{guid},
  POST /api/ab/peer/add/{guid}, PUT /api/ab/peer/update/{guid},
  DELETE /api/ab/peer/{guid}, POST /api/ab/tag/add/{guid},
  PUT /api/ab/tag/rename/{guid}, PUT /api/ab/tag/update/{guid},
  DELETE /api/ab/tag/{guid}, GET+POST /api/ab (legacy single-blob fallback),
  GET /api/device-group/accessible, GET /api/users, GET /api/peers.
- Share-rule enforcement (1=read, 2=read/write, 3=full) at the top of every
  AB mutation. Owners are full; other rules come from
  address_book_shares (direct or via device_group). Rejection is HTTP 200 +
  {"error":"read-only"} so the client doesn't yank the session.
- New CLI flags --ab-legacy-mode, --ab-max-peers-per-book.
- Action endpoints (peer add/update/delete, tag CRUD) return HTTP 200 with
  EMPTY body on success — matches the Flutter _jsonDecodeActionResp at
  ab_model.dart:2002 which treats {} as the literal error string "null".

================================================================================
M3 — Audit, recording, strategy push
================================================================================
- New tables: audit_conn (PK guid echoed back to client),
  audit_file, audit_alarm, recordings, strategies, strategy_assignments,
  heartbeat_commands.
- Endpoints: POST /api/audit/conn (returns {"guid":"..."}),
  POST /api/audit/file, POST /api/audit/alarm, PUT /api/audit (note update),
  POST /api/record?type={new|part|tail|remove}.
- Recording uploader: filesystem state machine under --recording-dir;
  filenames sanitized to a single Normal path component to block traversal;
  `tail` writes the first ≤1024 bytes at offset 0 after all `part` chunks.
- Heartbeat extended to:
  * resolve a per-peer strategy (peer > device-group > user, highest
    priority wins) and emit `strategy.config_options` + `extra` +
    `modified_at`.
  * read-and-delete heartbeat_commands rows so an admin can queue
    `disconnect: [conn_id]` or force `sysinfo: true` via SQL and have it
    delivered on the next 15-second tick.
- New CLI flags --recording-dir (default ./recordings),
  --recording-max-size-mb, --audit-retention-days.

================================================================================
secure_tcp on the rendezvous TCP listener (M3 polish)
================================================================================
A logged-in client conditionally calls secure_tcp() on its TCP rendezvous
connection (src/client.rs:427-431, gated on `key && token` both non-empty).
OSS hbbs had no KeyExchange handler at all on TCP rendezvous, so the
client's secure_tcp_impl read timed out with "Failed to secure tcp:
deadline has elapsed". Added:
- A try_secure_tcp_handshake helper that, on every accepted TCP connection,
  generates an ephemeral box keypair, signs the box public key with the
  server's Ed25519 sk (already loaded for relay-response signing), sends
  KeyExchange, then waits 5s for the client's reply.
  - Reply is KeyExchange[client_box_pk, sealed_sym_key] -> decrypt the
    sealed key, install Encrypt on both halves of the stream.
  - Reply is any other RendezvousMessage -> buffer it and replay through
    the normal handle_tcp dispatcher (plain-mode clients filter unsolicited
    KeyExchange via get_next_nonkeyexchange_msg, so our preceding KX is
    harmless).
  - Reply never comes (timeout) -> fall through to plain mode.
- Sink::TcpStream now carries an Option<Encrypt>; outgoing writes are
  sealed when keyed. Symmetric Encrypt is cloned for inbound (`dec`) and
  outbound (`enc`) so the two directions track independent counters.

================================================================================
M4 — Advanced auth (TOTP, email-code, OIDC), CLI assign, plugin signing
================================================================================
- New tables: user_totp_secrets, pending_tfa_challenges,
  pending_email_codes, oidc_providers, oidc_sessions. Soft-ALTER adds
  users.oidc_subject.
- /api/login extended:
  * type:"account" (existing) — issues an `tfa_check` challenge (5-min
    nonce in `secret`) when the user has TOTP enrolled.
  * type:"tfa_code" — verifies the nonce + the 6-digit TOTP code against
    user_totp_secrets.secret_b32.
  * type:"email_code" — passwordless. First leg mints a 6-digit code and
    sends it via SMTP (or logs to stdout when --smtp-host is empty);
    second leg verifies. Brute-force capped at 5 attempts per code, then
    the row is purged.
- /api/oidc/auth + GET /oidc/callback + GET /api/oidc/auth-query implement
  the standard OAuth2 authorization-code flow with userinfo. Discovery via
  <issuer>/.well-known/openid-configuration with an in-memory cache.
  --oidc-config TOML upserts providers at startup; --public-base-url builds
  the redirect_uri.
- New endpoints: POST /api/2fa/enroll (admin-only, returns secret_b32 +
  otpauth_url), POST /api/2fa/unenroll, POST /api/devices/cli (used by
  `rustdesk --assign`; binds device to user, ensures device-group, adds
  AB entry, attaches peer-scoped strategy), POST /lic/web/api/plugin-sign
  (Ed25519 over the request body using the same id_ed25519 secret).
- /api/login-options is now dynamic: returns ["account"], plus "email_code"
  when SMTP or ALLOW_DEV_EMAIL_CODE is set, plus an "oidc/<name>" entry
  per enabled provider in oidc_providers.
- New CLI flags --smtp-host, --smtp-port, --smtp-user, --smtp-pass,
  --smtp-from, --smtp-tls, --public-base-url, --oidc-config.
- New crate deps: tokio (fs/io-util features), totp-rs, lettre (rustls +
  builder + smtp-transport, no defaults), toml.

================================================================================
Code organization
================================================================================
- src/api/                 axum router + shared state + error envelope
  ├── ab/                  address book endpoints (settings/profiles/peers/
  │                        tags/legacy/rules)
  ├── audit/               conn/file/alarm/note
  ├── oidc/                providers/discovery/auth/callback/poll
  ├── record/              storage state machine + handler
  ├── strategy/            resolver wrapper around DB
  ├── auth.rs              login/logout/currentUser
  ├── devices_cli.rs       /api/devices/cli
  ├── email.rs             SMTP transport (lettre) + dev-mode stdout fallback
  ├── error.rs             ApiError enum -> HTTP 200/401/403/404 + JSON envelope
  ├── groups.rs            /api/device-group/accessible
  ├── heartbeat.rs         /api/heartbeat
  ├── middleware.rs        AuthedUser extractor (Bearer -> sha256 -> token row)
  ├── pagination.rs        Page<T> + PageQuery
  ├── peers.rs             /api/peers
  ├── plugin_sign.rs       /lic/web/api/plugin-sign
  ├── state.rs             AppState + ApiConfig (incl. EmailConfig)
  ├── sysinfo.rs           /api/sysinfo, /api/sysinfo_ver
  ├── twofa.rs             /api/2fa/enroll, /unenroll
  └── users.rs             UserPayload + /api/users + bcrypt helpers

================================================================================
Conventions enforced throughout
================================================================================
- All new SQL uses the runtime sqlx::query("...") form (NOT the query!
  macro) so first-time builds don't require DATABASE_URL to point at a DB
  containing the new tables.
- Soft-ALTER helper (try_alter) swallows "duplicate column name" errors so
  schema migrations are idempotent across re-runs and existing-DB upgrades.
- Bcrypt compares always via spawn_blocking.
- Tokens (Bearer access_token, TFA challenge nonce, OIDC poll handle) are
  always 24-32 random bytes from sodiumoxide::randombytes; the Bearer is
  stored only as its sha256.
- Constant-time hash comparison for email codes.
- Action endpoints return HTTP 200 with empty body on success; HTTP 200 +
  {"error": "..."} for business errors so the client doesn't get logged
  out; 401 only from the auth middleware.

Tested end-to-end via curl + a stock RustDesk client (M1-M2 verified
against two laptops; M3 verified against the strategy-push and
force-disconnect paths; M4 verified via direct flow tests + a mock IdP for
OIDC). Stock client connect now works whether the user is signed in or
not (the secure_tcp regression that blocked logged-in connect is fixed).

The remaining piece on the M4 plan — HttpProxyRequest, the TCP-over-
rendezvous fallback for clients with OPTION_USE_RAW_TCP_FOR_API=Y — is
gated on bumping the OSS server's vendored hbb_common to a commit that
includes proto tags 27 and 28. That work lives on a separate branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:07:01 +02:00
rustdesk 34cbd57e71 bump version 2026-01-13 10:20:40 +08:00
rustdesk 34946219c3 update workflow 2026-01-09 19:12:49 +08:00
rustdesk 1fd1a2a3b6 1.1.14 2025-01-25 20:37:16 +08:00
rustdesk b9e5968299 1.1.13 2025-01-21 01:09:21 +08:00
21pages 7a509f6975 replace libs/hbb_common with submodule (#502)
cargo update -p schannel to fix crash on higher rust toolchain, https://github.com/seanmonstar/reqwest/issues/2311

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-20 17:34:22 +08:00
XLion 4c74586ce0 Borrow Cargo.toml's profile.release from RustDesk for better binary (#481) 2024-10-14 11:00:38 +08:00
rustdesk 6f18a97644 v1.1.12 2024-10-07 16:21:36 +08:00
rustdesk 5078a1f797 reuse port, and revert hbbr -k 2024-05-24 18:37:11 +08:00
rustdesk 3cf0f6560f bump to 1.1.11 2024-05-24 17:59:53 +08:00
rustdesk 778c89efb1 bump to 1.1.10-3 2024-01-31 11:20:04 +08:00
rustdesk a7a0fa7cb5 bump to 1.1.10-2 2024-01-30 19:23:36 +08:00
Denis Fondras 2e06125974 Update mac_address
The latest version of mac_address allows to compile rustdesk-server on OpenBSD
2023-12-24 18:21:51 +01:00
rustdesk 4c27143125 bump 1.1.9 2023-12-05 17:09:07 +08:00
rustdesk 3a7904fa8e fix test_hbb and bump version 1.1.8 2023-06-08 13:42:34 +08:00
n-connect 178ff59623 Fix for core dumps on FreeBSD
Flexi_logger options for async writemode https://github.com/rustdesk/rustdesk-server/pull/232#issuecomment-1491347232
2023-04-01 11:14:51 +02:00
n-connect fe805e8554 Update Cargo.toml for FreeBSD build
Crate.io package local-ip-address from v0.5+ is Freebsd compatible, eg. it compiles and works. Simply changing the version to v0.5.1 in the Cargo.toml was possible to make a successful release build on FreeBSD 13 with prepackaged Rust 1.67.1.
2023-03-02 13:19:10 +01:00
rustdesk 4d6d439b1a 1.1.7-1 2023-02-18 13:44:25 +08:00
elilchen 388ae586ec crt-static 2023-02-16 15:23:31 +08:00
elilchen ad40d65070 issues #192 add MicrosoftEdgeWebview2Setup and fix the "VCRUNTIME140.dll Is Missing" error on windows server 2022 2023-02-16 13:39:08 +08:00
elilchen 7a0e300ff9 UI 2023-02-12 00:48:38 +08:00
rustdesk 5aaad36729 1.1.7 2023-01-10 16:26:06 +08:00
Paolo Asperti d878222fc1 rustdesk-server doctor 2022-08-07 00:25:18 +02:00
Paolo Asperti 06409279f4 rustdesk-utils 2022-07-14 15:59:39 +02:00
rustdesk 39153ce147 fix slow connection, '/' in pub key, and hbbr wait for key, and possible
solution for https://github.com/rustdesk/rustdesk-server/issues/24
2022-07-13 00:22:45 +08:00
rustdesk 03ca2a9517 missed files 2022-05-12 20:07:45 +08:00
rustdesk b3f39598a7 change sled to sqlite and remove lic 2022-05-12 20:00:33 +08:00
rustdesk 4774093ef2 refactor dep 2021-05-15 15:57:40 +08:00
open-trade 411e963187 some guys use empty id 2021-05-11 20:28:43 +08:00
rustdesk 26d926838f nonce 2021-05-04 20:42:13 +08:00
opentrade ea98675b21 minreq to remove libssl dep so that easy to deploy 2021-04-10 10:56:58 +08:00
opentrade c5e102fa6e Only ed25519 2021-04-10 10:41:27 +08:00
opentrade 4662b05d73 dec not working yet 2021-04-09 18:02:10 +08:00
opentrade da372f276d check_email 2021-04-09 12:27:22 +08:00
opentrade 1282e730af working on lic 2021-04-08 18:59:57 +08:00
opentrade 25554dd318 lic draft 2021-04-08 17:53:56 +08:00
rustdesk a10bb34f6e rename 2021-04-03 22:32:14 +08:00
open-trade f864e6988f refactor 2021-03-19 16:48:21 +08:00
open-trade 482b5e0a66 change to rocksdb 2021-03-19 11:32:28 +08:00
open-trade f1646522d1 license 2020-10-31 14:33:25 +08:00
hzhou 3d5d219a8c version 2020-09-24 17:26:58 +08:00
open-trade 33accfb2ca rendezvous_servers 2020-09-18 17:07:17 +08:00
open-trade ba9862cd7f clap 2020-09-18 13:04:39 +08:00
open-trade 0a1fbadb4f fix on request_relay 2020-09-18 00:47:15 +08:00
open-trade b32a948347 hbbf 2020-09-17 00:04:39 +08:00
open-trade f3554b153d sled_async 2020-05-14 19:16:46 +00:00
open-trade b06f9d22ac prepare sled 2020-05-14 09:18:07 +00:00