remote-dsh 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/dist/bin.js +119 -18
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -4,10 +4,11 @@ Secure remote access for [DeepSeek Harness](https://github.com/deepseek-ai/deeps
4
4
 
5
5
  ```bash
6
6
  npm install -g remote-dsh
7
- rdsh serve # LAN auth gateway in front of dsh web (M1 MVP)
7
+ rdsh host setup lan # write ~/.rdsh/host.json (LAN gateway: pair auth)
8
+ rdsh host serve # run the gateway in the foreground
8
9
  ```
9
10
 
10
- > **v0.1.0** is a name-reservation skeleton. Commands land with the M1 MVP.
11
+ > See `doc/overview/usage.md` for the full manual (`rdsh host setup lan|cloud`, `rdsh host join <hub>`, `rdsh hub serve`, ...).
11
12
 
12
13
  ## License
13
14
 
package/dist/bin.js CHANGED
@@ -10,7 +10,6 @@
10
10
  */
11
11
  import { createInterface } from "node:readline";
12
12
  import { rm } from "node:fs/promises";
13
- import { hostname as osHostname } from "node:os";
14
13
  import { dirname } from "node:path";
15
14
  import { serve, join, registerJoin, findDsh, selfRevoke, readPersistedToken, clearPersistedToken, UserManager, resolveConfigPath, loadConfig, saveConfig, installService, uninstallService, serviceStatus, HOST_SERVICE_NAME, JOIN_SERVICE_NAME, HUB_SERVICE_NAME, } from "rdsh-gateway";
16
15
  import { loadHubConfig, resolveHubConfigPath, HubDb, hashPassword, serveHub, } from "rdsh-hub";
@@ -25,7 +24,8 @@ Usage:
25
24
  rdsh host leave Unregister this machine from the hub
26
25
  rdsh host user ... Manage gateway users (LAN/cloud password auth)
27
26
  rdsh hub serve Start the hub server (cloud, multi-host)
28
- rdsh hub user add|passwd|rm|ls
27
+ rdsh hub user add|passwd|rm|ls|unlock|reset-2fa|ban|unban
28
+ rdsh hub audit ls Audit log (login/2FA/sharing/email events)
29
29
  rdsh hub host ls|revoke List / revoke hosts (revoke drops the tunnel instantly)
30
30
  rdsh hub service ... Run hub as a systemd/launchd service
31
31
 
@@ -60,7 +60,8 @@ Run the hub server or manage it.
60
60
 
61
61
  Subcommands:
62
62
  serve Start the hub server (TLS required)
63
- user add|passwd|rm|ls Manage users (registration closed; admin creates accounts)
63
+ user add|passwd|rm|ls|unlock|reset-2fa|ban|unban Manage users (admin creates accounts; unlock / reset-2fa / ban / unban)
64
+ audit ls [--user <n>] [--event <e>] [--since 24h|7d] Query the audit log
64
65
  host ls|revoke List / revoke hosts (revoke drops the tunnel instantly)
65
66
  service install|status|uninstall Run hub as a systemd/launchd service
66
67
 
@@ -284,7 +285,7 @@ async function handleHostJoin(args, configPath) {
284
285
  const config = await loadConfig(target);
285
286
  config.mode = "join";
286
287
  config.hub = hubUrl;
287
- config.name = opts.name ?? osHostname();
288
+ config.name = outcome.name;
288
289
  config.insecure = outcome.insecure;
289
290
  config.dshPath = opts.dshPath ?? findDsh() ?? config.dshPath;
290
291
  await saveConfig(target, config);
@@ -356,7 +357,7 @@ async function maybeRegisterForServiceInstall(args, target) {
356
357
  const cfg = await loadConfig(target);
357
358
  cfg.mode = "join";
358
359
  cfg.hub = maybeHub;
359
- cfg.name = joinOpts.name ?? osHostname();
360
+ cfg.name = outcome.name;
360
361
  cfg.insecure = outcome.insecure;
361
362
  cfg.dshPath = joinOpts.dshPath ?? findDsh() ?? cfg.dshPath;
362
363
  await saveConfig(target, cfg);
@@ -436,6 +437,56 @@ async function openHubDb(configPath) {
436
437
  const config = await loadHubConfig(hubConfigPath);
437
438
  return new HubDb(config.dbPath);
438
439
  }
440
+ /** `rdsh hub audit ls [--user <name>] [--event <e>] [--since 24h|7d]`。 */
441
+ async function handleHubAudit(args, configPath) {
442
+ const db = await openHubDb(configPath);
443
+ try {
444
+ // 兼容 `audit ls` 与 `audit`(ls 为可选位置参数)
445
+ const opts = args[0] === "ls" ? args.slice(1) : args;
446
+ const filter = {};
447
+ for (let i = 0; i < opts.length; i++) {
448
+ const flag = opts[i];
449
+ if (flag === "--user") {
450
+ const name = opts[++i];
451
+ if (name === undefined)
452
+ throw new Error("missing value for --user");
453
+ const u = db.getUserByName(name);
454
+ if (u === null)
455
+ throw new Error(`user '${name}' not found`);
456
+ filter.userId = u.id;
457
+ }
458
+ else if (flag === "--event") {
459
+ filter.event = opts[++i];
460
+ if (filter.event === undefined)
461
+ throw new Error("missing value for --event");
462
+ }
463
+ else if (flag === "--since") {
464
+ const v = opts[++i];
465
+ if (v === undefined)
466
+ throw new Error("missing value for --since");
467
+ const m = /^(\d+)([hd])$/.exec(v);
468
+ if (m === null)
469
+ throw new Error(`invalid --since '${v}' (use like 24h or 7d)`);
470
+ filter.since = Date.now() - Number(m[1]) * (m[2] === "h" ? 3_600_000 : 86_400_000);
471
+ }
472
+ else {
473
+ throw new Error(`unknown option '${flag}'`);
474
+ }
475
+ }
476
+ const events = db.listAudit(filter);
477
+ if (events.length === 0) {
478
+ console.log("(no events)");
479
+ return;
480
+ }
481
+ for (const e of events) {
482
+ const user = e.userId !== null ? db.getUserById(e.userId) : null;
483
+ console.log(`${new Date(e.createdAt).toISOString()} ${user?.name ?? "-"} ${e.event} ip=${e.ip} ${e.detailJson}`);
484
+ }
485
+ }
486
+ finally {
487
+ db.close();
488
+ }
489
+ }
439
490
  async function handleHub(args, configPath) {
440
491
  const sub = args[0];
441
492
  const rest = args.slice(1);
@@ -457,6 +508,10 @@ async function handleHub(args, configPath) {
457
508
  await handleHubHost(rest, configPath);
458
509
  return;
459
510
  }
511
+ case "audit": {
512
+ await handleHubAudit(rest, configPath);
513
+ return;
514
+ }
460
515
  case "service": {
461
516
  const action = rest[0];
462
517
  const hubConfigPath = resolveHubConfigPath(configPath);
@@ -476,7 +531,7 @@ async function handleHub(args, configPath) {
476
531
  }
477
532
  }
478
533
  default:
479
- throw new Error("usage: rdsh hub serve|user|host|service");
534
+ throw new Error("usage: rdsh hub serve|user|host|audit|service");
480
535
  }
481
536
  }
482
537
  function parseHubServeArgs(args, configPath) {
@@ -528,20 +583,21 @@ async function handleHubUser(args, configPath) {
528
583
  case "add": {
529
584
  const name = args[1];
530
585
  if (name === undefined)
531
- throw new Error("usage: rdsh hub user add <name> [--no-password]");
586
+ throw new Error("usage: rdsh hub user add <name> [--no-password] [--role <user|readonly|operator|admin>]");
532
587
  const noPassword = args.includes("--no-password");
588
+ const roleIdx = args.indexOf("--role");
589
+ const role = roleIdx >= 0 && args[roleIdx + 1] !== undefined ? args[roleIdx + 1] : "user";
590
+ if (!["user", "readonly", "operator", "admin"].includes(role))
591
+ throw new Error(`invalid role '${role}' (user|readonly|operator|admin)`);
533
592
  const hubConfigPath = resolveHubConfigPath(configPath);
534
593
  if (db.getUserByName(name) !== null)
535
594
  throw new Error(`user '${name}' already exists`);
536
- if (noPassword) {
537
- db.createUser(name, "scrypt:disabled", new Date().toISOString(), true);
538
- console.log(`rdsh: user '${name}' created they must set a password on first sign-in (${hubConfigPath})`);
539
- }
540
- else {
541
- const password = await promptPasswordTwice(`password for ${name}: `);
542
- db.createUser(name, await hashPassword(password));
543
- console.log(`rdsh: user '${name}' created (hub db: ${db.path})`);
544
- }
595
+ const user = noPassword
596
+ ? db.createUser(name, "scrypt:disabled", new Date().toISOString(), true)
597
+ : db.createUser(name, await hashPassword(await promptPasswordTwice(`password for ${name}: `)));
598
+ if (role !== "user")
599
+ db.setRole(user.id, role);
600
+ console.log(`rdsh: user '${name}' created (role: ${role}${noPassword ? " — must set password on first sign-in" : ""})`);
545
601
  return;
546
602
  }
547
603
  case "passwd": {
@@ -558,7 +614,7 @@ async function handleHubUser(args, configPath) {
558
614
  }
559
615
  case "ls": {
560
616
  const users = db.listUsers();
561
- console.log(users.length > 0 ? users.map((u) => u.name).join("\n") : "(no users)");
617
+ console.log(users.length > 0 ? users.map((u) => `${u.name}\t${u.role}`).join("\n") : "(no users)");
562
618
  return;
563
619
  }
564
620
  case "rm": {
@@ -572,8 +628,53 @@ async function handleHubUser(args, configPath) {
572
628
  console.log(`rdsh: user '${name}' removed (their hosts removed)`);
573
629
  return;
574
630
  }
631
+ case "unlock": {
632
+ const name = args[1];
633
+ if (name === undefined)
634
+ throw new Error("usage: rdsh hub user unlock <name>");
635
+ const user = db.getUserByName(name);
636
+ if (user === null)
637
+ throw new Error(`user '${name}' not found`);
638
+ db.unlockAccount(user.id);
639
+ console.log(`rdsh: user '${name}' unlocked`);
640
+ return;
641
+ }
642
+ case "reset-2fa": {
643
+ const name = args[1];
644
+ if (name === undefined)
645
+ throw new Error("usage: rdsh hub user reset-2fa <name>");
646
+ const user = db.getUserByName(name);
647
+ if (user === null)
648
+ throw new Error(`user '${name}' not found`);
649
+ db.clearTotpSecret(user.id);
650
+ db.bumpVersion(user.id);
651
+ console.log(`rdsh: 2FA reset for '${name}' (their sessions revoked)`);
652
+ return;
653
+ }
654
+ case "ban": {
655
+ const name = args[1];
656
+ if (name === undefined)
657
+ throw new Error("usage: rdsh hub user ban <name>");
658
+ const user = db.getUserByName(name);
659
+ if (user === null)
660
+ throw new Error(`user '${name}' not found`);
661
+ db.setAccountStatus(user.id, "banned");
662
+ console.log(`rdsh: user '${name}' banned (login + host access blocked)`);
663
+ return;
664
+ }
665
+ case "unban": {
666
+ const name = args[1];
667
+ if (name === undefined)
668
+ throw new Error("usage: rdsh hub user unban <name>");
669
+ const user = db.getUserByName(name);
670
+ if (user === null)
671
+ throw new Error(`user '${name}' not found`);
672
+ db.setAccountStatus(user.id, "active");
673
+ console.log(`rdsh: user '${name}' unbanned (plan restored)`);
674
+ return;
675
+ }
575
676
  default:
576
- throw new Error("usage: rdsh hub user add|passwd|ls|rm");
677
+ throw new Error("usage: rdsh hub user add|passwd|ls|rm|unlock|reset-2fa|ban|unban");
577
678
  }
578
679
  }
579
680
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-dsh",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Secure remote access for DeepSeek Harness: rdsh serve / rdsh join / rdsh hub",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,8 +23,8 @@
23
23
  "node": ">=22"
24
24
  },
25
25
  "dependencies": {
26
- "rdsh-gateway": "0.3.0",
27
- "rdsh-hub": "0.3.0"
26
+ "rdsh-gateway": "0.5.0",
27
+ "rdsh-hub": "0.5.0"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsc -p tsconfig.json",