@linxin666/dsh-ssh 0.2.0 → 0.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.i18n.yaml +2 -2
- package/README.md +4 -4
- package/README.zh.md +2 -2
- package/lib/client.js +67 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +26 -22
- package/lib/types/client/sidebar-entry-core.d.ts +60 -0
- package/lib/types/client/sidebar-entry.d.ts +7 -11
- package/package.json +1 -1
- package/src/client/mount.tsx +1 -0
- package/src/client/panel/SshPanel.tsx +1 -1
- package/src/client/panel/TerminalTab.tsx +1 -1
- package/src/client/sidebar-entry-core.ts +208 -0
- package/src/client/sidebar-entry.ts +23 -138
- package/src/engine/connection-pool.ts +27 -21
- package/src/routes.ts +8 -3
|
@@ -140,28 +140,34 @@ export async function connectChain(engine: PoolEngine, entry: SshHostEntry): Pro
|
|
|
140
140
|
const hops: Client[] = []
|
|
141
141
|
let sock: ConnectConfig['sock']
|
|
142
142
|
const chain = entry.proxyJump
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
143
|
+
try {
|
|
144
|
+
for (let index = 0; index < chain.length; index += 1) {
|
|
145
|
+
const hopAlias = chain[index]
|
|
146
|
+
const hop = engine.store.find(hopAlias)
|
|
147
|
+
if (hop === undefined) {
|
|
148
|
+
throw new Error('proxyJump alias \'' + hopAlias + '\' not found — create it first')
|
|
149
|
+
}
|
|
150
|
+
const hopClient = await connectClient(buildConnectConfig(hop, sock, engine.opts))
|
|
151
|
+
hops.push(hopClient)
|
|
152
|
+
const next = index + 1 < chain.length ? engine.store.find(chain[index + 1]) : undefined
|
|
153
|
+
const nextHost = next !== undefined ? next.host : entry.host
|
|
154
|
+
const nextPort = next !== undefined ? next.port : entry.port
|
|
155
|
+
sock = await new Promise<ConnectConfig['sock']>((resolve, reject) => {
|
|
156
|
+
hopClient.forwardOut('127.0.0.1', 0, nextHost, nextPort, (error, stream) => {
|
|
157
|
+
if (error !== undefined) {
|
|
158
|
+
reject(error)
|
|
159
|
+
} else {
|
|
160
|
+
resolve(stream)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
163
|
})
|
|
164
|
-
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
// A missing alias, a failed hop connect, or a failed forwardOut must all
|
|
167
|
+
// close the hops already connected, so a failed ProxyJump never leaks a
|
|
168
|
+
// middle-hop connection.
|
|
169
|
+
for (const client of hops) client.end()
|
|
170
|
+
throw error
|
|
165
171
|
}
|
|
166
172
|
let target: Client | undefined
|
|
167
173
|
try {
|
package/src/routes.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* deployments must not serve them.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createReadStream, createWriteStream, mkdirSync } from 'node:fs'
|
|
10
|
+
import { closeSync, createReadStream, createWriteStream, mkdirSync, openSync } from 'node:fs'
|
|
11
11
|
import { unlink } from 'node:fs/promises'
|
|
12
12
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
13
13
|
import { basename, join } from 'node:path'
|
|
@@ -93,7 +93,9 @@ export function makeRoutes(deps: SshRoutesDeps): { routes: WebRoute[]; upgrade:
|
|
|
93
93
|
const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
|
|
94
94
|
// The upload route stages request bodies here; it must exist before the
|
|
95
95
|
// first request (a missing dir would hang the first upload forever).
|
|
96
|
-
|
|
96
|
+
// 0700 keeps in-flight transfers unreadable to other local users (the
|
|
97
|
+
// staged files below are 0600; downloads are pre-created 0600 as well).
|
|
98
|
+
mkdirSync(staging, { recursive: true, mode: 0o700 })
|
|
97
99
|
|
|
98
100
|
/** Guard helper: fence + method check. */
|
|
99
101
|
const guard = (req: IncomingMessage, res: ServerResponse, method: string): boolean => {
|
|
@@ -353,7 +355,7 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
|
|
|
353
355
|
}
|
|
354
356
|
// Stage the uploaded bytes, then SFTP them out with progress frames.
|
|
355
357
|
const tmp = join(staging, `upload-${randomBytes(6).toString('hex')}`)
|
|
356
|
-
const sink = createWriteStream(tmp)
|
|
358
|
+
const sink = createWriteStream(tmp, { mode: 0o600 })
|
|
357
359
|
let settled = false
|
|
358
360
|
// Every terminal path (sink error, client abort, response loss) must
|
|
359
361
|
// emit a result frame, end the response, and remove the tmp file.
|
|
@@ -437,6 +439,9 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
|
|
|
437
439
|
}
|
|
438
440
|
const tmp = join(staging, `download-${randomBytes(6).toString('hex')}`)
|
|
439
441
|
try {
|
|
442
|
+
// Pre-create with 0600: ssh2's fastGet opens the destination with
|
|
443
|
+
// umask-default permissions and a pre-created file keeps the mode.
|
|
444
|
+
closeSync(openSync(tmp, 'w', 0o600))
|
|
440
445
|
const outcome = await engine.download(alias, remotePath, tmp)
|
|
441
446
|
res.writeHead(200, {
|
|
442
447
|
'content-type': 'application/octet-stream',
|