dsh-remote-mobile 1.1.0 → 1.2.1

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.
package/README_EN.md CHANGED
@@ -1,99 +1,75 @@
1
+ <div align="center">
2
+
1
3
  # dsh-remote-mobile
2
4
 
3
- English Documentation | [简体中文](./README.md)
5
+ **DeepSeek Harness (DSH) Mobile & Remote Access Security Guard Plugin**
4
6
 
5
- `dsh-remote-mobile` is a remote and mobile security access plugin tailored for DeepSeek Hub (DSH).
7
+ [![npm version](https://img.shields.io/npm/v/dsh-remote-mobile.svg?style=flat-square&color=3b82f6)](https://www.npmjs.com/package/dsh-remote-mobile)
8
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-remote-mobile.svg?style=flat-square&color=22c55e)](https://www.npmjs.com/package/dsh-remote-mobile)
9
+ [![node](https://img.shields.io/badge/node-%3E%3D18.0.0-8b5cf6?style=flat-square)](https://nodejs.org)
10
+ [![license](https://img.shields.io/npm/l/dsh-remote-mobile.svg?style=flat-square&color=10b981)](https://github.com/IceApriler/dsh-remote-mobile/blob/master/LICENSE)
6
11
 
7
- By default, the DSH core service restricts Web UI access to localhost (`127.0.0.1`). This project safely enables Tailscale VPN and Local Area Network (LAN/Wi-Fi) remote connectivity using a custom authentication gate and context virtualization. It features QR code pairing, persistent passwords, brute-force rate-limiting, local data persistence, and realtime SSE push notifications.
12
+ <p align="center">
13
+ <b>Break Loopback Limits · QR Quick Pairing · Full Workspace Parity · Transport-Layer Encryption</b>
14
+ </p>
8
15
 
9
- ---
16
+ English Documentation · [简体中文](./README.md)
10
17
 
11
- ## 🌟 Key Features
12
-
13
- ### 1. Network Connectivity
14
- - **Tailscale Virtual Private Network**: Automatically detects Tailscale CGNAT IPs (`100.64.0.0/10`), generates a dedicated access QR code, and offers an end-to-end encrypted passwordless bypass option.
15
- - **Local Area Network (LAN / Wi-Fi)**: Automatically detects RFC 1918 private IPs (`192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`), provides an independent LAN QR code, and includes high-risk visual safety warnings.
16
- - **Quick QR Pairing**: Direct scanning with mobile camera or WeChat to open the authorization portal.
17
-
18
- ### 2. Multi-Tiered Authentication
19
- - **Dynamic 6-Digit Code**: Generates temporary one-time pairing codes (valid for 5 minutes). Entering it on mobile grants a 365-day persistent session cookie.
20
- - **Persistent Password (Secret)**: Set a custom password (letters + numbers, 6+ chars) to gain persistent authentication on any mobile device.
21
- - **Passwordless Bypass Mode**: Selectively enable bypass for Tailscale or LAN. Turning off bypass automatically purges temporary credentials and resets device statuses.
22
- - **Device Management**: View connected device types, OS, browsers, IP addresses, and last seen timestamps. Supports individual device revocation and one-click global purge.
23
-
24
- ### 3. Enterprise-Grade Security
25
- - **Transport Layer Encryption**: Login verification supports RSA encryption (RSA-OAEP-SHA256 & PKCS#1 v1.5 compatible). Passwords and codes are encrypted on the browser before network transit.
26
- - **Slow Salted Hashing**: Stores passwords locally using `scrypt` slow hashing (`scrypt:${salt}:${hash}`). Employs `crypto.timingSafeEqual` for constant-time comparisons against timing attacks.
27
- - **Brute-Force & Rate-Limiting Defense**:
28
- - Automatically locks out an IP for 15 minutes after 5 consecutive failed attempts (returns HTTP 429);
29
- - Sliding-window rate limiter (default: 60 requests/min) to prevent high-frequency abuse;
30
- - Audit logs and lockout states persist across DSH restarts;
31
- - Manual one-click IP unlock support from the management UI.
32
- - **Socket-Level Verification**: Only inspects low-level socket remote addresses, defending against forged `X-Forwarded-For` headers.
33
-
34
- ### 4. Realtime Push & Full Internationalization (i18n)
35
- - **Server-Sent Events (SSE)**: Delivers instant push notifications for new device pairing, reconnections, revocations, and security alerts without client polling.
36
- - **Bilingual Adaptive UI**: Dynamically switches between English and Simplified Chinese based on DSH preferences and browser settings.
37
- - **Non-HTTPS Crypto Polyfill**: Injects a lightweight `crypto.randomUUID` Polyfill into HTML headers to support mobile browsers running in HTTP non-secure contexts.
18
+ <p align="center">
19
+ <a href="#about">About</a> •
20
+ <a href="#advantages">Key Advantages</a> •
21
+ <a href="#quick-start">Quick Start</a> •
22
+ <a href="#config">Required Config</a> •
23
+ <a href="#preview">UI Preview</a> •
24
+ <a href="#features">Features</a> •
25
+ <a href="#install">Installation</a> •
26
+ <a href="#faq">FAQ</a>
27
+ </p>
38
28
 
39
29
  ---
40
30
 
41
- ## 🛠️ Installation & Setup
31
+ </div>
42
32
 
43
- ### Step 1: Install the Plugin
33
+ <span id="about"></span>
34
+ ## 📖 About
44
35
 
45
- #### Option 1: One-Click via DSH CLI (Recommended)
36
+ `dsh-remote-mobile` is a dedicated remote and mobile security governance plugin designed specifically for **DeepSeek Harness (DSH)**.
46
37
 
47
- Use the DSH built-in plugin manager to install from npm directly into the web profile:
48
-
49
- ```bash
50
- dsh plugin --profile web add dsh-remote-mobile
51
- ```
38
+ By default, DSH strictly listens only to the local loopback address (`127.0.0.1`), preventing mobile devices, tablets, or external computers from accessing the Web console. This plugin uses **access control middleware** and **request isolation** to securely open up **Tailscale Private Network** and **Local Area Network (LAN / Wi-Fi)** access, complete with RSA transmission encryption, QR code pairing, persistent password auth, and automated brute-force defense.
52
39
 
53
- #### Option 2: Visual Install via Web Settings (Plugin Manager)
40
+ ---
54
41
 
55
- Open the DSH Web UI in your browser:
56
- 1. Navigate to **Settings ⚙️ -> Plugins**;
57
- 2. Switch to the **"Plugin Manager"** (插件管理) tab at the top;
58
- 3. Enter the npm package name **`dsh-remote-mobile`** in the input field and click **"Install"** (安装);
59
- 4. Restart DSH to activate the plugin.
42
+ <span id="advantages"></span>
43
+ ## ⚡ Key Advantages
60
44
 
61
- #### Option 3: Manual Installation via Package Manager in Profile Directory
45
+ * 🚀 **Break Network Barriers with Full Workspace Parity**: Solves the core issue of accessing DSH Web over LAN and Tailscale. When accessed on mobile devices, **creating workspaces, switching workspaces, and executing terminal commands are supported** without feature degradation.
46
+ * 📱 **Unified Codebase with Web**: Mobile devices share the same DSH Web core runtime and ecosystem plugins (such as `dsh-pet`, task boards, etc.). No duplicate backend maintenance is needed; future enhancements only require standard responsive styling adjustments.
47
+ * 🛡️ **Security Gateway & Transport Encryption**: Built-in client-side RSA asymmetric public key encryption, `scrypt` salted slow-hashing password persistence, and automated IP lockout upon consecutive brute-force failures, blocking unauthorized access over public/LAN networks.
48
+ * 📲 **Quick QR Pairing**: Auto-detects Tailscale CGNAT and LAN IP addresses to generate dedicated pairing QR codes. Scan the code to authenticate and obtain persistent credentials.
49
+ * 🔄 **SSE Real-Time Push**: Device connection, reconnection, revocation, and security alert events are pushed in real time via Server-Sent Events without polling.
62
50
 
63
- ```bash
64
- # 1. Enter the DSH Web Profile directory
65
- cd ~/.dsh/profiles/web
51
+ ---
66
52
 
67
- # 2. Install via pnpm
68
- pnpm add dsh-remote-mobile
53
+ <span id="quick-start"></span>
54
+ ## 🚀 Quick Start
69
55
 
70
- # 3. Check ~/.dsh/profiles/web/package.json
71
- # Ensure "dsh-remote-mobile" is present in dsh.profile.bundles
72
- ```
56
+ ### 1. One-Command Installation
73
57
 
74
- #### Option 4: Local Development & Source Installation
58
+ Run the DSH official plugin management command in your terminal (Recommended):
75
59
 
76
60
  ```bash
77
- # 1. Clone or download the repository locally
78
- cd ~/Myfile/www-self/dsh-remote-mobile
79
-
80
- # 2. Install dependencies, build and pack
81
- npm install
82
- npm run build && npm test && npm pack
83
-
84
- # 3. Install the packed tarball into DSH
85
- cd ~/.dsh/profiles/web
86
- pnpm add /path/to/dsh-remote-mobile/dsh-remote-mobile-1.0.0.tgz
61
+ dsh plugin --profile web add dsh-remote-mobile
87
62
  ```
88
63
 
89
64
  ---
90
65
 
91
- ### Step 2: Configure Network Binding (Required)
66
+ <span id="config"></span>
67
+ ### 2. Required Configuration (Open External Listening)
92
68
 
93
- Since DSH defaults to listening only on `127.0.0.1` (localhost), you need to bind the webserver to `0.0.0.0` in `~/.dsh/profiles/web/cordis.patch.yml` to accept connections from Tailscale or LAN:
69
+ Because DSH defaults to `127.0.0.1`, ensure your `~/.dsh/profiles/web/cordis.patch.yml` includes the following configuration to allow external connectivity:
94
70
 
95
71
  ```yaml
96
- # 1. Override webserver to listen on 0.0.0.0:3080 (Required for remote access)
72
+ # 1. Allow webserver to listen for external connections (Required)
97
73
  - id: webserver
98
74
  name: '@deepseek-ai/dsh-host-webserver'
99
75
  inject: [webStartup]
@@ -101,71 +77,122 @@ Since DSH defaults to listening only on `127.0.0.1` (localhost), you need to bin
101
77
  host: '0.0.0.0'
102
78
  port: 3080
103
79
 
104
- # 2. Disable the built-in remote plugin from @linxin666/dsh-web-ui-all (Required: prevent route conflicts and UI overlaps)
80
+ # 2. Disable built-in remote plugin in @linxin666/dsh-web-ui-all (Required: prevent feature conflict)
105
81
  - id: web-ui-remote-web-ui
106
82
  disabled: true
107
83
  ```
108
84
 
109
- > **💡 Note**: The plugin is automatically registered by the DSH Bundle loader. You **do not** need to add `id: remote-mobile` to `cordis.patch.yml`.
85
+ > **💡 Note**: Plugin self-registration is automatically handled by the DSH Bundle system. You do **not** need to add `id: remote-mobile` manually.
110
86
 
111
87
  ---
112
88
 
113
- ### Step 3: Start DSH
89
+ ### 3. Launch & Pair
114
90
 
115
91
  ```bash
116
- dsh web
92
+ dsh web --no-open
117
93
  ```
118
94
 
119
- Open `http://127.0.0.1:3080` in your PC browser and navigate to **Settings ⚙️ -> Remote & Mobile** to configure pairing.
95
+ Open the DSH Web Console in your browser, navigate to **Settings ⚙️ -> Remote & Mobile Access**, and scan the QR code with your mobile camera or WeChat to start using DSH on mobile!
120
96
 
121
97
  ---
122
98
 
123
- ## ⚙️ Configuration
99
+ <span id="preview"></span>
100
+ ## 🖼️ UI Preview
124
101
 
125
- Options can be customized in `~/.dsh/settings.yaml` (or interactively configured via **Settings ⚙️ -> Remote & Mobile**):
102
+ ### Desktop DSH Plugin Control Panel
126
103
 
127
- ```yaml
128
- dsh-remote-mobile:
129
- # Bypass options
130
- allowTailscale: true # boolean, default: false - Allow passwordless Tailscale access
131
- allowLan: false # boolean, default: false - Allow passwordless LAN (Wi-Fi) access
104
+ | Network Access & QR Pairing | Persistent Passwords, Device Sessions & IP Audit |
105
+ | :---: | :---: |
106
+ | ![PC Settings - Network & QR](images/pc-dsh-setting-1.png) | ![PC Settings - Passwords & Devices](images/pc-dsh-setting-2.png) |
132
107
 
133
- # Persistent password hash (auto-generated when saved in the UI)
134
- secretHash: '' # string, default: empty
108
+ ---
135
109
 
136
- # Advanced security policies
137
- maxVisitsPerMinute: 60 # number, default: 60 - Max visits to /auth per minute per IP
138
- maxFailedAttempts: 5 # number, default: 5 - Lockout threshold for wrong passwords
139
- lockDurationMs: 900000 # number, default: 900000 (15 min) - Lockout duration in ms
140
- ```
110
+ ### Mobile Live Demonstration (Same-Origin Auth & Full Workspace Control)
141
111
 
142
- | Option | Type | Default | Description |
143
- |---|---|---|---|
144
- | `allowTailscale` | `boolean` | `false` | Enable passwordless access for Tailscale nodes |
145
- | `allowLan` | `boolean` | `false` | Enable passwordless access for local Wi-Fi devices (High Risk) |
146
- | `secretHash` | `string` | `""` | Salted scrypt hash for persistent password |
147
- | `maxVisitsPerMinute` | `number` | `60` | Max /auth page visits per minute per IP |
148
- | `maxFailedAttempts` | `number` | `5` | Maximum failed attempts before IP lockout |
149
- | `lockDurationMs` | `number` | `900000` | IP lockout duration (ms) |
112
+ | Mobile QR Auth & Login Page | Full DSH Workspace Control on Mobile |
113
+ | :---: | :---: |
114
+ | ![Mobile Auth Page](images/mobile-auth.jpg) | ![Mobile Workspace Control](images/mobile-dsh.jpg) |
150
115
 
151
116
  ---
152
117
 
153
- ## 📂 Local Storage Paths
154
-
155
- - **Device Sessions & Security Audit**: `~/.dsh/remote-mobile/devices.json`
156
- - **RSA Transport Keypair**: `~/.dsh/remote-mobile/rsa-keys.json` (auto-generated on first launch with `0600` permissions)
118
+ <span id="features"></span>
119
+ ## 🌟 Features
120
+
121
+ ### 1. Network Access
122
+ - **Tailscale Private Network**: Automatically detects local Tailscale IP (`100.64.0.0/10` CGNAT subnet), creates pairing QR codes, and supports direct passwordless bypass (transport encryption guaranteed by Tailscale WireGuard tunnels).
123
+ - **Local Area Network (LAN / Wi-Fi)**: Automatically detects RFC 1918 private IPs (`192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`), generates LAN pairing QR codes with direct link previews and prominent high-risk security warnings.
124
+ - **Instant QR Pairing**: Scan QR codes directly with any mobile camera or scanner app to authenticate.
125
+
126
+ ### 2. Multi-Tier Authentication
127
+ - **Dynamic 6-Digit Pairing Codes**: Generates 6-digit short codes (5-minute validity, single-use) that exchange for 365-day persistent authentication cookies on mobile.
128
+ - **Persistent Access Passwords**: Set customizable persistent passwords (minimum 6 characters with letters and numbers) for seamless long-term logins across multiple devices.
129
+ - **Direct Bypass Mode**: Enable passwordless direct bypass independently for Tailscale or LAN. Disabling bypass immediately cleans up temporary credentials and resets device state.
130
+ - **Device Session Management**: Real-time inspection of connected device types, OS, browser, source IP, and last active timestamp, with single-device revocation and one-click bulk revocation.
131
+
132
+ ### 3. Security & Cryptography
133
+ - **Transport RSA Asymmetric Encryption**: Login endpoints support client-side RSA encryption. In secure contexts (HTTPS) this prioritizes native Web Crypto `RSA-OAEP-SHA256`; in DSH's default HTTP LAN/Tailscale access (non-secure context) it falls back to a pure JS shim using `crypto.getRandomValues()` for cryptographically secure random padding. Sensitive passwords and pairing codes are encrypted on the client before network transmission.
134
+ - **scrypt Slow Hash Storage**: Server persists passwords with `scrypt` salted slow-hashing (`scrypt:${salt}:${hash}`). Verification uses `crypto.timingSafeEqual` constant-time comparison to prevent timing side-channel attacks.
135
+ - **Brute-Force Defense & Rate Limiting**:
136
+ - Automatically locks out IPs for 15 minutes after reaching consecutive failed attempt thresholds (default: 5), returning HTTP 429;
137
+ - Sliding-window rate limiting (default: 60 visits/min) to prevent high-frequency brute-force scraping;
138
+ - Audit logs and lockout states persist to disk and restore across restarts;
139
+ - Administrators can manually unlock blocked IPs with one click in the panel.
140
+ - **Intelligent Static Asset Whitelisting**: The authentication gate automatically allows verified frontend asset extensions (`.js`, `.css`, `.png`, `.svg`, `.woff2`, etc., 20+ types) while strictly blocking extensionless or dynamic API routes (e.g. `/plugins/xxx/admin`), ensuring smooth third-party plugin rendering without manual whitelist configuration.
141
+ - **Resilient Atomic Persistence & Debounce**: 500ms debounce write-throttling to safeguard disk I/O, coupled with `beforeExit` process flush hooks and atomic temporary file replacement (`renameSync`) for YAML configuration.
142
+ - **Real Socket IP Extraction**: Relies strictly on underlying Socket connection addresses, preventing spoofed `X-Forwarded-For` header attacks.
143
+
144
+ ### 4. Real-Time Sync & Internationalization
145
+ - **SSE Real-Time Push**: Push notifications for new device pairing, reconnections, revocations, and security alerts via Server-Sent Events, with bidirectional close listeners and idempotent cleanup.
146
+ - **Adaptive Bilingual UI**: Automatically switches between English and Simplified Chinese based on DSH settings and browser language preferences.
147
+ - **Non-HTTPS Compatibility Patch**: Injects `crypto.randomUUID` Polyfill into HTML templates to fix missing native Web Crypto APIs in HTTP non-secure mobile browser contexts.
157
148
 
158
149
  ---
159
150
 
160
- ## 🔒 Security Best Practices
151
+ <span id="install"></span>
152
+ ## 📦 All Installation Methods
161
153
 
162
- 1. **LAN Bypass Risk**: **NEVER enable `allowLan`** on public Wi-Fi, cafes, hotels, or untrusted shared networks.
163
- 2. **Tailscale Recommended**: Prioritize Tailscale for encrypted private access without exposing ports to the public internet.
164
- 3. **Password Strength**: When setting a persistent password, choose a strong alphanumeric combination (6+ characters).
154
+ <details>
155
+ <summary><b>Expand to view all 4 installation methods</b></summary>
165
156
 
166
- ---
157
+ ### Method 1: DSH CLI One-Command Install (Recommended)
158
+ ```bash
159
+ dsh plugin --profile web add dsh-remote-mobile
160
+ ```
167
161
 
168
- ## 🗑️ Uninstalling
162
+ ### Method 2: Web GUI Plugin Marketplace Install
163
+ 1. Open DSH Web console in your browser;
164
+ 2. Navigate to **Settings ⚙️ -> Plugins**;
165
+ 3. Switch to the **Plugin Management** tab at the top;
166
+ 4. Enter the npm package name **`dsh-remote-mobile`** and click **Install**;
167
+ 5. Restart DSH after installation completes.
168
+
169
+ ### Method 3: Install via Package Manager in Profile Directory
170
+ ```bash
171
+ # 1. Navigate to DSH Web Profile directory
172
+ cd ~/.dsh/profiles/web
173
+
174
+ # 2. Install using pnpm
175
+ pnpm add dsh-remote-mobile
176
+ ```
177
+
178
+ ### Method 4: Local Source Development (Instant Symlink Sync)
179
+ ```bash
180
+ # 1. Clone source code
181
+ git clone https://github.com/IceApriler/dsh-remote-mobile.git
182
+ cd dsh-remote-mobile
183
+
184
+ # 2. Install dependencies & build
185
+ npm install
186
+ npm run build
187
+
188
+ # 3. Create symlink to DSH runtime (changes take effect instantly on npm run build)
189
+ rm -rf ~/.dsh/profiles/web/node_modules/dsh-remote-mobile
190
+ ln -s $(pwd) ~/.dsh/profiles/web/node_modules/dsh-remote-mobile
191
+ ```
192
+
193
+ </details>
194
+
195
+ ### Uninstalling
169
196
 
170
197
  ```bash
171
198
  dsh plugin --profile web remove dsh-remote-mobile
@@ -173,6 +200,60 @@ dsh plugin --profile web remove dsh-remote-mobile
173
200
 
174
201
  ---
175
202
 
203
+ ## ⚙️ Advanced Configuration
204
+
205
+ Fully integrated with the DSH official Settings system. Configurations can be adjusted in the Web UI or edited in `~/.dsh/settings.yaml` under the `dsh-remote-mobile` namespace:
206
+
207
+ ```yaml
208
+ dsh-remote-mobile:
209
+ allowTailscale: false # boolean, default false: allow passwordless access via Tailscale
210
+ allowLan: false # boolean, default false: allow passwordless access via LAN (High Risk)
211
+ secretHash: "" # string, default empty: scrypt salted hash of the persistent password
212
+ maxVisitsPerMinute: 60 # number, default 60: max login page visits per minute per IP
213
+ maxFailedAttempts: 5 # number, default 5: max consecutive failed attempts before IP lockout
214
+ lockDurationMs: 900000 # number, default 900000 (15 mins): IP lockout duration in ms
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 📂 Local File Storage Locations
220
+
221
+ | Path | Description | Security Level |
222
+ |---|---|---|
223
+ | `~/.dsh/settings.yaml` | Global security policies & bypass toggles | User R/W |
224
+ | `~/.dsh/remote-mobile/devices.json` | Authorized devices, sessions & IP audit statistics | Local persistence |
225
+ | `~/.dsh/remote-mobile/rsa-keys.json` | Server RSA keypair (public & private) | Local storage, `0o600` (Restricted to current user) |
226
+
227
+ ---
228
+
229
+ <span id="faq"></span>
230
+ ## ❓ FAQ
231
+
232
+ <details>
233
+ <summary><b>Q1: Getting "Connection Refused" or cannot open page when scanning on mobile?</b></summary>
234
+
235
+ **Answer**: Please verify the following 3 items:
236
+ 1. Ensure `host: '0.0.0.0'` is properly configured in `cordis.patch.yml` and DSH has been restarted;
237
+ 2. For LAN access, ensure your mobile phone and computer are connected to the exact same Wi-Fi router;
238
+ 3. If OS firewall is active on the computer, ensure inbound traffic on port `3080` is allowed.
239
+ </details>
240
+
241
+ <details>
242
+ <summary><b>Q2: What is the difference between Tailscale Bypass and LAN Bypass?</b></summary>
243
+
244
+ **Answer**:
245
+ * **Tailscale Bypass**: Highly secure. Tailscale operates over an encrypted WireGuard overlay where only devices logged into your account can connect.
246
+ * **LAN Bypass**: Inherent risks. Anyone connected to your Wi-Fi (including guests or unauthorized devices) can control your workspace. **Never enable this on untrusted networks**.
247
+ </details>
248
+
249
+ <details>
250
+ <summary><b>Q3: Does mobile support creating and switching workspaces normally?</b></summary>
251
+
252
+ **Answer**: **Supported.** The plugin uses request isolation so that mobile devices get the same workspace management and execution capabilities as the desktop Web interface.
253
+ </details>
254
+
255
+ ---
256
+
176
257
  ## 📄 License
177
258
 
178
- [MIT License](./LICENSE)
259
+ This project is licensed under the [MIT License](LICENSE).
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
3
  import { dirname } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  export const RSA_KEY_FILE = `${homedir()}/.dsh/remote-mobile/rsa-keys.json`;
6
- let keyPair;
6
+ let keyPair = null;
7
7
  function loadOrCreateKeyPair() {
8
8
  try {
9
9
  if (existsSync(RSA_KEY_FILE)) {
@@ -15,30 +15,41 @@ function loadOrCreateKeyPair() {
15
15
  }
16
16
  }
17
17
  catch { }
18
- const pair = generateKeyPairSync('rsa', {
19
- modulusLength: 2048,
20
- publicKeyEncoding: {
21
- type: 'spki',
22
- format: 'pem',
23
- },
24
- privateKeyEncoding: {
25
- type: 'pkcs8',
26
- format: 'pem',
27
- },
28
- });
29
18
  try {
30
- mkdirSync(dirname(RSA_KEY_FILE), { recursive: true });
31
- writeFileSync(RSA_KEY_FILE, JSON.stringify(pair, null, 2), { encoding: 'utf8', mode: 0o600 });
19
+ const pair = generateKeyPairSync('rsa', {
20
+ modulusLength: 2048,
21
+ publicKeyEncoding: {
22
+ type: 'spki',
23
+ format: 'pem',
24
+ },
25
+ privateKeyEncoding: {
26
+ type: 'pkcs8',
27
+ format: 'pem',
28
+ },
29
+ });
30
+ try {
31
+ mkdirSync(dirname(RSA_KEY_FILE), { recursive: true });
32
+ writeFileSync(RSA_KEY_FILE, JSON.stringify(pair, null, 2), { encoding: 'utf8', mode: 0o600 });
33
+ }
34
+ catch { }
35
+ return pair;
32
36
  }
33
- catch { }
34
- return pair;
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ function getKeyPair() {
42
+ if (keyPair)
43
+ return keyPair;
44
+ keyPair = loadOrCreateKeyPair();
45
+ return keyPair;
35
46
  }
36
- keyPair = loadOrCreateKeyPair();
37
47
  /**
38
48
  * 获取服务端的持久化 RSA 公钥 (PEM 格式)
39
49
  */
40
50
  export function getPublicKeyPem() {
41
- return keyPair.publicKey;
51
+ const pair = getKeyPair();
52
+ return pair ? pair.publicKey : '';
42
53
  }
43
54
  /**
44
55
  * 使用 RSA 私钥解密客户端发来的 Base64 密文 (多填充算法自适应兼容)
@@ -46,6 +57,9 @@ export function getPublicKeyPem() {
46
57
  export function decryptWithPrivateKey(encryptedBase64) {
47
58
  if (!encryptedBase64)
48
59
  return '';
60
+ const pair = getKeyPair();
61
+ if (!pair || !pair.privateKey)
62
+ return '';
49
63
  try {
50
64
  const buffer = Buffer.from(encryptedBase64, 'base64');
51
65
  // 2048 位 RSA 密文标准长度必须为 256 字节
@@ -54,7 +68,7 @@ export function decryptWithPrivateKey(encryptedBase64) {
54
68
  // 1. 优先尝试 RSA-OAEP SHA-256 (Web Crypto 规范标准)
55
69
  try {
56
70
  const decrypted = privateDecrypt({
57
- key: keyPair.privateKey,
71
+ key: pair.privateKey,
58
72
  padding: constants.RSA_PKCS1_OAEP_PADDING,
59
73
  oaepHash: 'sha256',
60
74
  }, buffer);
@@ -64,7 +78,7 @@ export function decryptWithPrivateKey(encryptedBase64) {
64
78
  // 2. 尝试 RSA-OAEP SHA-1 兼容模式
65
79
  try {
66
80
  const decrypted = privateDecrypt({
67
- key: keyPair.privateKey,
81
+ key: pair.privateKey,
68
82
  padding: constants.RSA_PKCS1_OAEP_PADDING,
69
83
  oaepHash: 'sha1',
70
84
  }, buffer);
@@ -74,7 +88,7 @@ export function decryptWithPrivateKey(encryptedBase64) {
74
88
  // 3. 尝试 PKCS#1 v1.5 填充兼容模式
75
89
  try {
76
90
  const decrypted = privateDecrypt({
77
- key: keyPair.privateKey,
91
+ key: pair.privateKey,
78
92
  padding: constants.RSA_PKCS1_PADDING,
79
93
  }, buffer);
80
94
  return decrypted.toString('utf8');
package/lib/auth/gate.js CHANGED
@@ -6,6 +6,7 @@ export function isLoopbackRequest(req) {
6
6
  const ip = getClientIp(req);
7
7
  return ip === '127.0.0.1' || ip === '::1' || ip === 'localhost';
8
8
  }
9
+ const STATIC_ASSET_EXT_REGEX = /\.(js|mjs|cjs|css|png|jpg|jpeg|gif|svg|webp|ico|woff|woff2|ttf|eot|otf|map|json|txt|wasm)$/i;
9
10
  /**
10
11
  * 判断是否为无需鉴权的公开静态/登录路径
11
12
  */
@@ -17,7 +18,7 @@ export function isPublicPath(url = '') {
17
18
  return true;
18
19
  if (pathname.startsWith('/api/remote-mobile/verify'))
19
20
  return true;
20
- if (pathname.startsWith('/plugins/'))
21
+ if (pathname.startsWith('/plugins/') && STATIC_ASSET_EXT_REGEX.test(pathname))
21
22
  return true;
22
23
  if (pathname.startsWith('/assets/'))
23
24
  return true;
@@ -12,7 +12,7 @@ export declare function readFromSettingsYaml(filePath?: string): Partial<Session
12
12
  */
13
13
  export declare function readGlobalLocale(filePath?: string): 'zh' | 'en';
14
14
  /**
15
- * 将最新的插件配置安全写回 ~/.dsh/settings.yaml 中的 dsh-remote-mobile 命名空间
15
+ * 将最新的插件配置安全原子写回 ~/.dsh/settings.yaml 中的 dsh-remote-mobile 命名空间
16
16
  */
17
17
  export declare function writeBackToSettingsYaml(options: SessionStoreOptions, filePath?: string): boolean;
18
18
  export interface SessionRecord {
@@ -65,6 +65,7 @@ export declare class SessionStore extends EventEmitter {
65
65
  private ipStats;
66
66
  private options;
67
67
  private settingsMutator?;
68
+ private debounceTimer;
68
69
  readonly persistPath: string;
69
70
  readonly settingsFilePath: string | null;
70
71
  constructor(options?: SessionStoreOptions);
@@ -126,5 +127,16 @@ export declare class SessionStore extends EventEmitter {
126
127
  */
127
128
  clearAllIpSecurityStats(): void;
128
129
  loadPersistedData(): void;
130
+ /**
131
+ * 同步立即持久化落盘(由关键路径调用:配对成功、密码认证、撤销设备、手动改密/配置等)
132
+ */
129
133
  savePersistedData(): void;
134
+ /**
135
+ * 500ms 防抖持久化落盘(由高频访问路径调用:每次打开 /auth、validateToken 重连时间戳更新等)
136
+ */
137
+ savePersistedDataDebounced(): void;
138
+ /**
139
+ * 立即执行待写的持久化数据落盘并清理定时器
140
+ */
141
+ flushPersistedData(): void;
130
142
  }