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/CHANGELOG.md +29 -0
- package/README.md +187 -106
- package/README_EN.md +187 -106
- package/lib/auth/crypto.js +35 -21
- package/lib/auth/gate.js +2 -1
- package/lib/auth/token.d.ts +13 -1
- package/lib/auth/token.js +53 -20
- package/lib/client.js +171 -89
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/routes/api.js +8 -2
- package/lib/routes/login-page.js +54 -12
- package/package.json +7 -3
- package/lib/types/auth.d.ts +0 -49
- package/lib/types/auth.js +0 -4
package/README_EN.md
CHANGED
|
@@ -1,99 +1,75 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# dsh-remote-mobile
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
**DeepSeek Harness (DSH) Mobile & Remote Access Security Guard Plugin**
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-remote-mobile)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-remote-mobile)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](https://github.com/IceApriler/dsh-remote-mobile/blob/master/LICENSE)
|
|
6
11
|
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
31
|
+
</div>
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
<span id="about"></span>
|
|
34
|
+
## 📖 About
|
|
44
35
|
|
|
45
|
-
|
|
36
|
+
`dsh-remote-mobile` is a dedicated remote and mobile security governance plugin designed specifically for **DeepSeek Harness (DSH)**.
|
|
46
37
|
|
|
47
|
-
|
|
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
|
-
|
|
40
|
+
---
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
# 1. Enter the DSH Web Profile directory
|
|
65
|
-
cd ~/.dsh/profiles/web
|
|
51
|
+
---
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
<span id="quick-start"></span>
|
|
54
|
+
## 🚀 Quick Start
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
# Ensure "dsh-remote-mobile" is present in dsh.profile.bundles
|
|
72
|
-
```
|
|
56
|
+
### 1. One-Command Installation
|
|
73
57
|
|
|
74
|
-
|
|
58
|
+
Run the DSH official plugin management command in your terminal (Recommended):
|
|
75
59
|
|
|
76
60
|
```bash
|
|
77
|
-
|
|
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
|
-
|
|
66
|
+
<span id="config"></span>
|
|
67
|
+
### 2. Required Configuration (Open External Listening)
|
|
92
68
|
|
|
93
|
-
|
|
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.
|
|
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
|
|
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**:
|
|
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
|
-
###
|
|
89
|
+
### 3. Launch & Pair
|
|
114
90
|
|
|
115
91
|
```bash
|
|
116
|
-
dsh web
|
|
92
|
+
dsh web --no-open
|
|
117
93
|
```
|
|
118
94
|
|
|
119
|
-
Open
|
|
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
|
-
|
|
99
|
+
<span id="preview"></span>
|
|
100
|
+
## 🖼️ UI Preview
|
|
124
101
|
|
|
125
|
-
|
|
102
|
+
### Desktop DSH Plugin Control Panel
|
|
126
103
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
|  |  |
|
|
132
107
|
|
|
133
|
-
|
|
134
|
-
secretHash: '' # string, default: empty
|
|
108
|
+
---
|
|
135
109
|
|
|
136
|
-
|
|
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
|
-
|
|
|
143
|
-
|
|
144
|
-
|
|
|
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
|
+
|  |  |
|
|
150
115
|
|
|
151
116
|
---
|
|
152
117
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
151
|
+
<span id="install"></span>
|
|
152
|
+
## 📦 All Installation Methods
|
|
161
153
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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](
|
|
259
|
+
This project is licensed under the [MIT License](LICENSE).
|
package/lib/auth/crypto.js
CHANGED
|
@@ -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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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;
|
package/lib/auth/token.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
}
|