dsh-remote 0.8.46 → 0.8.48
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/LICENSE +21 -21
- package/README.en.md +361 -361
- package/README.md +368 -368
- package/cordis.patch.yml +9 -9
- package/lib/binding.js +172 -172
- package/lib/client.js +3637 -3637
- package/lib/credential.js +177 -177
- package/lib/deploy-tasks.js +142 -142
- package/lib/errors.js +87 -87
- package/lib/file-reference.js +457 -457
- package/lib/forwards.js +258 -258
- package/lib/hostkey.js +108 -108
- package/lib/http-transport.js +196 -196
- package/lib/ignore.js +202 -156
- package/lib/index.js +3779 -3747
- package/lib/openssh.js +838 -838
- package/lib/paths.js +225 -225
- package/lib/pool.js +757 -757
- package/lib/registry.js +95 -95
- package/lib/remote-fs.js +148 -143
- package/lib/routes-fs.js +233 -233
- package/lib/search.js +235 -230
- package/lib/sftp-surface.js +76 -76
- package/lib/sshconfig.js +454 -454
- package/lib/sync.js +275 -275
- package/lib/tasks.js +95 -95
- package/lib/telemetry.js +213 -213
- package/lib/update.js +472 -472
- package/lib/web-attach.js +850 -850
- package/lib/web-deploy-skill.js +205 -205
- package/lib/web-deploy.js +694 -694
- package/node_modules/asn1/Jenkinsfile +65 -0
- package/node_modules/asn1/LICENSE +19 -0
- package/node_modules/asn1/README.md +50 -0
- package/node_modules/asn1/lib/ber/errors.js +13 -0
- package/node_modules/asn1/lib/ber/index.js +27 -0
- package/node_modules/asn1/lib/ber/reader.js +262 -0
- package/node_modules/asn1/lib/ber/types.js +36 -0
- package/node_modules/asn1/lib/ber/writer.js +317 -0
- package/node_modules/asn1/lib/index.js +20 -0
- package/node_modules/asn1/package.json +31 -0
- package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
- package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
- package/node_modules/bcrypt-pbkdf/README.md +45 -0
- package/node_modules/bcrypt-pbkdf/index.js +556 -0
- package/node_modules/bcrypt-pbkdf/package.json +15 -0
- package/node_modules/buildcheck/.eslintrc.js +5 -0
- package/node_modules/buildcheck/.github/workflows/ci.yml +88 -0
- package/node_modules/buildcheck/.github/workflows/lint.yml +27 -0
- package/node_modules/buildcheck/.npmignore +3 -0
- package/node_modules/buildcheck/LICENSE +19 -0
- package/node_modules/buildcheck/README.md +106 -0
- package/node_modules/buildcheck/deps/Find-VisualStudio.cs +250 -0
- package/node_modules/buildcheck/lib/findvs.js +361 -0
- package/node_modules/buildcheck/lib/index.js +738 -0
- package/node_modules/buildcheck/package.json +36 -0
- package/node_modules/buildcheck/test/test.js +8 -0
- package/node_modules/cpu-features/.eslintrc.js +5 -0
- package/node_modules/cpu-features/.github/workflows/ci.yml +74 -0
- package/node_modules/cpu-features/.github/workflows/lint.yml +27 -0
- package/node_modules/cpu-features/LICENSE +19 -0
- package/node_modules/cpu-features/README.md +59 -0
- package/node_modules/cpu-features/binding.gyp +16 -0
- package/node_modules/cpu-features/buildcheck.js +32 -0
- package/node_modules/cpu-features/deps/cpu_features/.clang-format +4 -0
- package/node_modules/cpu-features/deps/cpu_features/.dockerignore +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/Dockerfile +5 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/aarch64_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_freebsd_cmake.yml +22 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_bazel.yml +26 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_macos_cmake.yml +43 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_windows_cmake.yml +25 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/arm_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/clang_format.yml +24 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/mips_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/power_linux_cmake.yml +29 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/riscv_linux_cmake.yml +28 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/s390x_linux_cmake.yml +27 -0
- package/node_modules/cpu-features/deps/cpu_features/.grenrc.yml +21 -0
- package/node_modules/cpu-features/deps/cpu_features/BUILD.bazel +329 -0
- package/node_modules/cpu-features/deps/cpu_features/CMakeLists.txt +261 -0
- package/node_modules/cpu-features/deps/cpu_features/CONTRIBUTING.md +23 -0
- package/node_modules/cpu-features/deps/cpu_features/LICENSE +230 -0
- package/node_modules/cpu-features/deps/cpu_features/README.md +272 -0
- package/node_modules/cpu-features/deps/cpu_features/WORKSPACE +19 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/ci/README.md +5 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/platforms.bzl +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesNdkCompatConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/README.md +30 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/Makefile +252 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/README.md +40 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.dot +64 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.svg +312 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/generate_image.sh +7 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/amd64/Dockerfile +48 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/toolchain/Dockerfile +34 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/CMakeLists.txt +22 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/main.cpp +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/vagrant/freebsd/Vagrantfile +107 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/googletest.CMakeLists.txt.in +15 -0
- package/node_modules/cpu-features/deps/cpu_features/cpu_features.gyp +105 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_cache_info.h +54 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_macros.h +384 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_aarch64.h +259 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_arm.h +121 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_mips.h +74 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_ppc.h +149 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_riscv.h +72 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_s390x.h +108 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_x86.h +288 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/bit_utils.h +40 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/cpuid_x86.h +37 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/filesystem.h +39 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/hwcaps.h +240 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/stack_line_reader.h +49 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/string_view.h +110 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/windows_utils.h +70 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/CMakeLists.txt +60 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/README.md +4 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.c +205 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.h +320 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/ndk-compat-test.c +12 -0
- package/node_modules/cpu-features/deps/cpu_features/patches/0001-Add-Apple-Silicon-Support.patch +456 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/generate_badges.d +165 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/make_release.sh +75 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/run_integration.sh +490 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/test_integration.sh +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/copy.inl +19 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection.inl +86 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection_and_hwcaps.inl +26 -0
- package/node_modules/cpu-features/deps/cpu_features/src/equals.inl +22 -0
- package/node_modules/cpu-features/deps/cpu_features/src/filesystem.c +62 -0
- package/node_modules/cpu-features/deps/cpu_features/src/hwcaps.c +169 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64__base_implementation.inl +88 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_linux_or_android.c +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_macos_or_iphone.c +82 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_windows.c +138 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_arm_linux_or_android.c +212 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_mips_linux_or_android.c +93 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_ppc_linux.c +163 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_riscv_linux.c +111 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_s390x_linux.c +120 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86__base_implementation.inl +2055 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_freebsd.c +68 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_linux_or_android.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_macos.c +57 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_windows.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/stack_line_reader.c +132 -0
- package/node_modules/cpu-features/deps/cpu_features/src/string_view.c +192 -0
- package/node_modules/cpu-features/deps/cpu_features/src/utils/list_cpu_features.c +461 -0
- package/node_modules/cpu-features/deps/cpu_features/test/CMakeLists.txt +114 -0
- package/node_modules/cpu-features/deps/cpu_features/test/bit_utils_test.cc +53 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_aarch64_test.cc +396 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_arm_test.cc +384 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_mips_test.cc +176 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_ppc_test.cc +131 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_riscv_test.cc +180 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_s390x_test.cc +82 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_x86_test.cc +1729 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.cc +103 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.h +61 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.cc +52 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.h +31 -0
- package/node_modules/cpu-features/deps/cpu_features/test/stack_line_reader_test.cc +132 -0
- package/node_modules/cpu-features/deps/cpu_features/test/string_view_test.cc +202 -0
- package/node_modules/cpu-features/lib/index.js +5 -0
- package/node_modules/cpu-features/package.json +40 -0
- package/node_modules/cpu-features/src/binding.cc +151 -0
- package/node_modules/cpu-features/test/test.js +12 -0
- package/node_modules/nan/.github/workflows/ci.yml +46 -0
- package/node_modules/nan/.pre-commit-config.yaml +8 -0
- package/node_modules/nan/CHANGELOG.md +605 -0
- package/node_modules/nan/CMakeLists.txt +138 -0
- package/node_modules/nan/LICENSE.md +9 -0
- package/node_modules/nan/README.md +457 -0
- package/node_modules/nan/doc/asyncworker.md +146 -0
- package/node_modules/nan/doc/buffers.md +54 -0
- package/node_modules/nan/doc/callback.md +76 -0
- package/node_modules/nan/doc/converters.md +41 -0
- package/node_modules/nan/doc/errors.md +226 -0
- package/node_modules/nan/doc/json.md +62 -0
- package/node_modules/nan/doc/maybe_types.md +583 -0
- package/node_modules/nan/doc/methods.md +689 -0
- package/node_modules/nan/doc/new.md +147 -0
- package/node_modules/nan/doc/node_misc.md +123 -0
- package/node_modules/nan/doc/object_wrappers.md +263 -0
- package/node_modules/nan/doc/persistent.md +296 -0
- package/node_modules/nan/doc/scopes.md +73 -0
- package/node_modules/nan/doc/script.md +58 -0
- package/node_modules/nan/doc/string_bytes.md +81 -0
- package/node_modules/nan/doc/v8_internals.md +211 -0
- package/node_modules/nan/doc/v8_misc.md +85 -0
- package/node_modules/nan/include_dirs.js +1 -0
- package/node_modules/nan/nan.h +3202 -0
- package/node_modules/nan/nan_callbacks.h +141 -0
- package/node_modules/nan/nan_callbacks_12_inl.h +730 -0
- package/node_modules/nan/nan_callbacks_pre_12_inl.h +528 -0
- package/node_modules/nan/nan_converters.h +72 -0
- package/node_modules/nan/nan_converters_43_inl.h +68 -0
- package/node_modules/nan/nan_converters_pre_43_inl.h +42 -0
- package/node_modules/nan/nan_define_own_property_helper.h +29 -0
- package/node_modules/nan/nan_implementation_12_inl.h +446 -0
- package/node_modules/nan/nan_implementation_pre_12_inl.h +263 -0
- package/node_modules/nan/nan_json.h +166 -0
- package/node_modules/nan/nan_maybe_43_inl.h +360 -0
- package/node_modules/nan/nan_maybe_pre_43_inl.h +268 -0
- package/node_modules/nan/nan_new.h +340 -0
- package/node_modules/nan/nan_object_wrap.h +156 -0
- package/node_modules/nan/nan_persistent_12_inl.h +132 -0
- package/node_modules/nan/nan_persistent_pre_12_inl.h +242 -0
- package/node_modules/nan/nan_private.h +73 -0
- package/node_modules/nan/nan_scriptorigin.h +97 -0
- package/node_modules/nan/nan_string_bytes.h +305 -0
- package/node_modules/nan/nan_typedarray_contents.h +96 -0
- package/node_modules/nan/nan_weak.h +453 -0
- package/node_modules/nan/package.json +38 -0
- package/node_modules/nan/tools/1to2.js +412 -0
- package/node_modules/nan/tools/README.md +14 -0
- package/node_modules/nan/tools/package.json +19 -0
- package/node_modules/safer-buffer/LICENSE +21 -0
- package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/node_modules/safer-buffer/Readme.md +156 -0
- package/node_modules/safer-buffer/dangerous.js +58 -0
- package/node_modules/safer-buffer/package.json +34 -0
- package/node_modules/safer-buffer/safer.js +77 -0
- package/node_modules/safer-buffer/tests.js +406 -0
- package/node_modules/ssh2/.eslintignore +4 -0
- package/node_modules/ssh2/.eslintrc.js +5 -0
- package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
- package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
- package/node_modules/ssh2/LICENSE +19 -0
- package/node_modules/ssh2/README.md +1529 -0
- package/node_modules/ssh2/SFTP.md +413 -0
- package/node_modules/ssh2/examples/server-chat.js +238 -0
- package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
- package/node_modules/ssh2/install.js +27 -0
- package/node_modules/ssh2/lib/Channel.js +295 -0
- package/node_modules/ssh2/lib/agent.js +1123 -0
- package/node_modules/ssh2/lib/client.js +2176 -0
- package/node_modules/ssh2/lib/http-agents.js +84 -0
- package/node_modules/ssh2/lib/index.js +44 -0
- package/node_modules/ssh2/lib/keygen.js +582 -0
- package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
- package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
- package/node_modules/ssh2/lib/protocol/constants.js +356 -0
- package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
- package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
- package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
- package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
- package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
- package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
- package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
- package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
- package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
- package/node_modules/ssh2/lib/protocol/utils.js +356 -0
- package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
- package/node_modules/ssh2/lib/server.js +1380 -0
- package/node_modules/ssh2/lib/utils.js +336 -0
- package/node_modules/ssh2/package.json +49 -0
- package/node_modules/ssh2/test/common.js +316 -0
- package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
- package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
- package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
- package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
- package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
- package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
- package/node_modules/ssh2/test/test-exec.js +578 -0
- package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
- package/node_modules/ssh2/test/test-keygen.js +60 -0
- package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
- package/node_modules/ssh2/test/test-openssh.js +261 -0
- package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
- package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
- package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
- package/node_modules/ssh2/test/test-sftp.js +842 -0
- package/node_modules/ssh2/test/test-shell.js +109 -0
- package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
- package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
- package/node_modules/ssh2/test/test-userauth.js +611 -0
- package/node_modules/ssh2/test/test-worker-imports.js +25 -0
- package/node_modules/ssh2/test/test.js +20 -0
- package/node_modules/ssh2/util/build_pagent.bat +2 -0
- package/node_modules/ssh2/util/pagent.c +88 -0
- package/node_modules/ssh2/util/pagent.exe +0 -0
- package/node_modules/tweetnacl/.npmignore +4 -0
- package/node_modules/tweetnacl/AUTHORS.md +28 -0
- package/node_modules/tweetnacl/CHANGELOG.md +221 -0
- package/node_modules/tweetnacl/LICENSE +24 -0
- package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
- package/node_modules/tweetnacl/README.md +459 -0
- package/node_modules/tweetnacl/nacl-fast.js +2388 -0
- package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
- package/node_modules/tweetnacl/nacl.d.ts +98 -0
- package/node_modules/tweetnacl/nacl.js +1175 -0
- package/node_modules/tweetnacl/nacl.min.js +1 -0
- package/node_modules/tweetnacl/package.json +58 -0
- package/package.json +96 -89
package/lib/credential.js
CHANGED
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
// dsh-remote — optional OS-keychain password storage.
|
|
2
|
-
// Machines keep their password in the OS credential store instead of the
|
|
3
|
-
// plaintext machines.json when the user enables "加密保存密码" per machine.
|
|
4
|
-
// Every backend is best-effort: any failure resolves to {ok:false} and the
|
|
5
|
-
// caller (persistPassword) falls back to plaintext + a user-visible warning
|
|
6
|
-
// (the feature must never block connecting, and must never silently drop the
|
|
7
|
-
// credential — issue #30).
|
|
8
|
-
//
|
|
9
|
-
// Backends:
|
|
10
|
-
// darwin → `security` (login keychain, generic password)
|
|
11
|
-
// win32 → DPAPI via PowerShell (CurrentUser scope), files under
|
|
12
|
-
// $DSH_HOME/remote-workspaces/.secrets/
|
|
13
|
-
// linux → `secret-tool` (libsecret / gnome-keyring), optional
|
|
14
|
-
// else → unsupported (plain)
|
|
15
|
-
import { execFile } from 'node:child_process'
|
|
16
|
-
import { mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs'
|
|
17
|
-
import { join as joinPath } from 'node:path'
|
|
18
|
-
|
|
19
|
-
const SERVICE = 'dsh-remote'
|
|
20
|
-
const TIMEOUT = 8000
|
|
21
|
-
|
|
22
|
-
// Windows PowerShell 5.1 does not preload System.Security, so
|
|
23
|
-
// [System.Security.Cryptography.ProtectedData] is "not found" until the assembly
|
|
24
|
-
// is added (issue #30: without this the whole DPAPI call failed and the caller
|
|
25
|
-
// silently lost the password). `-ErrorAction SilentlyContinue` keeps the line
|
|
26
|
-
// harmless on PowerShell 7, where the type is already available.
|
|
27
|
-
const PS_SECURITY_ASSEMBLY = 'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;'
|
|
28
|
-
|
|
29
|
-
function run(bin, args, input) {
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
execFile(bin, args, { timeout: TIMEOUT, maxBuffer: 1 << 20, ...(input != null ? { input } : {}) }, (err, stdout, stderr) => {
|
|
32
|
-
if (err) return reject(err)
|
|
33
|
-
resolve(String(stdout || '').trim())
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function platformBackend() {
|
|
39
|
-
if (process.platform === 'darwin') return 'keychain'
|
|
40
|
-
if (process.platform === 'win32') return 'windows'
|
|
41
|
-
return 'secret' // linux (secret-tool may be absent → falls back)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function account(machineId) {
|
|
45
|
-
// Only machine ids are stored; sanitize just in case.
|
|
46
|
-
return String(machineId || '').replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Redact a failed command's diagnostics: execFile puts the FULL command line
|
|
50
|
-
* (which carries the password as an argv) into `err.message`, and this string is
|
|
51
|
-
* shown in the settings UI — so the secret must never survive it. */
|
|
52
|
-
function safeReason(err, password) {
|
|
53
|
-
const raw = [err && err.stderr, err && err.message, err && err.code]
|
|
54
|
-
.filter(Boolean).map(String).join(' ')
|
|
55
|
-
const secret = String(password == null ? '' : password)
|
|
56
|
-
const scrubbed = secret ? raw.split(secret).join('***') : raw
|
|
57
|
-
return scrubbed.replace(/\s+/g, ' ').trim().slice(0, 300)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** PowerShell one-liner that DPAPI-protects a password (CurrentUser scope).
|
|
61
|
-
* Exported because the issue #30 fix (the mandatory Add-Type preamble) is only
|
|
62
|
-
* observable here — a Windows host cannot be exercised from the test suite. */
|
|
63
|
-
export function dpapiProtectScript(password) {
|
|
64
|
-
return PS_SECURITY_ASSEMBLY +
|
|
65
|
-
`$p='${String(password).replace(/'/g, "''")}';` +
|
|
66
|
-
`$s=[System.Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes($p),$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);` +
|
|
67
|
-
`[Convert]::ToBase64String($s)`
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** PowerShell one-liner that reverses dpapiProtectScript(). */
|
|
71
|
-
export function dpapiUnprotectScript(b64) {
|
|
72
|
-
return PS_SECURITY_ASSEMBLY +
|
|
73
|
-
`$s=[Convert]::FromBase64String('${String(b64).replace(/'/g, "''")}');` +
|
|
74
|
-
`[Text.Encoding]::UTF8.GetString([System.Security.Cryptography.ProtectedData]::Unprotect($s,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser))`
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Save a password to the OS store. Resolves {ok, backend, error} — ok:false on
|
|
78
|
-
* any failure (caller falls back to plaintext). `error` is a password-free
|
|
79
|
-
* diagnostic for the UI. */
|
|
80
|
-
export async function saveSecret(machineId, password, secretsDir) {
|
|
81
|
-
const acc = account(machineId)
|
|
82
|
-
try {
|
|
83
|
-
if (process.platform === 'darwin') {
|
|
84
|
-
await run('security', ['add-generic-password', '-U', '-s', SERVICE, '-a', acc, '-w', String(password)])
|
|
85
|
-
return { ok: true, backend: 'keychain', error: '' }
|
|
86
|
-
}
|
|
87
|
-
if (process.platform === 'win32') {
|
|
88
|
-
// DPAPI-encrypt and store under the harness home.
|
|
89
|
-
const script = dpapiProtectScript(password)
|
|
90
|
-
const b64 = await run('powershell', ['-NoProfile', '-STA', '-Command', script])
|
|
91
|
-
if (!b64) return { ok: false, backend: 'windows', error: 'DPAPI returned no ciphertext' }
|
|
92
|
-
mkdirSync(secretsDir || '.', { recursive: true })
|
|
93
|
-
writeFileSync(joinPath(secretsDir || '.', acc + '.bin'), b64, 'utf8')
|
|
94
|
-
return { ok: true, backend: 'windows', error: '' }
|
|
95
|
-
}
|
|
96
|
-
if (process.platform === 'linux') {
|
|
97
|
-
await run('secret-tool', ['store', '--label=' + SERVICE, 'service', SERVICE, 'account', acc], String(password) + '\n')
|
|
98
|
-
return { ok: true, backend: 'secret', error: '' }
|
|
99
|
-
}
|
|
100
|
-
} catch (err) {
|
|
101
|
-
return { ok: false, backend: platformBackend(), error: safeReason(err, password) || 'secret store write failed' }
|
|
102
|
-
}
|
|
103
|
-
return { ok: false, backend: platformBackend(), error: `unsupported platform: ${process.platform}` }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Decide where a machine password is persisted (issue #30).
|
|
107
|
-
*
|
|
108
|
-
* The contract the settings UI relies on: a password is NEVER dropped. When the
|
|
109
|
-
* requested OS store fails (missing DPAPI assembly, locked keychain, absent
|
|
110
|
-
* secret-tool), this falls back to plaintext in machines.json and reports
|
|
111
|
-
* `warning: 'secret-store-failed'` so the caller can tell the user instead of
|
|
112
|
-
* silently saving a credential-less machine.
|
|
113
|
-
*
|
|
114
|
-
* `save` is injectable so the fallback is unit-testable on any platform.
|
|
115
|
-
*
|
|
116
|
-
* @returns {Promise<{stored:'secret'|'plain'|'none', credentialBackend:string, password:string, warning:string, error:string}>}
|
|
117
|
-
*/
|
|
118
|
-
export async function persistPassword({ backend, machineId, password, secretsDir, save = saveSecret }) {
|
|
119
|
-
const pw = password == null ? '' : String(password)
|
|
120
|
-
const want = backend && backend !== 'plain' ? backend : 'plain'
|
|
121
|
-
if (!pw) return { stored: 'none', credentialBackend: want, password: '', warning: '', error: '' }
|
|
122
|
-
if (want === 'plain') return { stored: 'plain', credentialBackend: 'plain', password: pw, warning: '', error: '' }
|
|
123
|
-
let res
|
|
124
|
-
try {
|
|
125
|
-
res = await save(machineId, pw, secretsDir)
|
|
126
|
-
} catch (err) {
|
|
127
|
-
res = { ok: false, error: safeReason(err, pw) }
|
|
128
|
-
}
|
|
129
|
-
if (res && res.ok) {
|
|
130
|
-
return { stored: 'secret', credentialBackend: res.backend || want, password: '', warning: '', error: '' }
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
stored: 'plain',
|
|
134
|
-
credentialBackend: 'plain',
|
|
135
|
-
password: pw,
|
|
136
|
-
warning: 'secret-store-failed',
|
|
137
|
-
error: String((res && res.error) || '') || 'secret store write failed',
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Fetch a stored password. Resolves string|null. */
|
|
142
|
-
export async function getSecret(machineId, secretsDir) {
|
|
143
|
-
const acc = account(machineId)
|
|
144
|
-
try {
|
|
145
|
-
if (process.platform === 'darwin') {
|
|
146
|
-
return await run('security', ['find-generic-password', '-s', SERVICE, '-a', acc, '-w'])
|
|
147
|
-
}
|
|
148
|
-
if (process.platform === 'win32') {
|
|
149
|
-
const b64 = readFileSync(joinPath(secretsDir || '.', acc + '.bin'), 'utf8').trim()
|
|
150
|
-
if (!b64) return null
|
|
151
|
-
const script = dpapiUnprotectScript(b64)
|
|
152
|
-
return await run('powershell', ['-NoProfile', '-STA', '-Command', script])
|
|
153
|
-
}
|
|
154
|
-
if (process.platform === 'linux') {
|
|
155
|
-
return await run('secret-tool', ['lookup', 'service', SERVICE, 'account', acc])
|
|
156
|
-
}
|
|
157
|
-
} catch { /* fall through */ }
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Delete a stored password (idempotent). */
|
|
162
|
-
export async function deleteSecret(machineId, secretsDir) {
|
|
163
|
-
const acc = account(machineId)
|
|
164
|
-
try {
|
|
165
|
-
if (process.platform === 'darwin') {
|
|
166
|
-
await run('security', ['delete-generic-password', '-s', SERVICE, '-a', acc]).catch(() => {})
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
if (process.platform === 'win32') {
|
|
170
|
-
try { unlinkSync(joinPath(secretsDir || '.', acc + '.bin')) } catch { /* absent */ }
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
if (process.platform === 'linux') {
|
|
174
|
-
await run('secret-tool', ['clear', 'service', SERVICE, 'account', acc]).catch(() => {})
|
|
175
|
-
}
|
|
176
|
-
} catch { /* best effort */ }
|
|
177
|
-
}
|
|
1
|
+
// dsh-remote — optional OS-keychain password storage.
|
|
2
|
+
// Machines keep their password in the OS credential store instead of the
|
|
3
|
+
// plaintext machines.json when the user enables "加密保存密码" per machine.
|
|
4
|
+
// Every backend is best-effort: any failure resolves to {ok:false} and the
|
|
5
|
+
// caller (persistPassword) falls back to plaintext + a user-visible warning
|
|
6
|
+
// (the feature must never block connecting, and must never silently drop the
|
|
7
|
+
// credential — issue #30).
|
|
8
|
+
//
|
|
9
|
+
// Backends:
|
|
10
|
+
// darwin → `security` (login keychain, generic password)
|
|
11
|
+
// win32 → DPAPI via PowerShell (CurrentUser scope), files under
|
|
12
|
+
// $DSH_HOME/remote-workspaces/.secrets/
|
|
13
|
+
// linux → `secret-tool` (libsecret / gnome-keyring), optional
|
|
14
|
+
// else → unsupported (plain)
|
|
15
|
+
import { execFile } from 'node:child_process'
|
|
16
|
+
import { mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs'
|
|
17
|
+
import { join as joinPath } from 'node:path'
|
|
18
|
+
|
|
19
|
+
const SERVICE = 'dsh-remote'
|
|
20
|
+
const TIMEOUT = 8000
|
|
21
|
+
|
|
22
|
+
// Windows PowerShell 5.1 does not preload System.Security, so
|
|
23
|
+
// [System.Security.Cryptography.ProtectedData] is "not found" until the assembly
|
|
24
|
+
// is added (issue #30: without this the whole DPAPI call failed and the caller
|
|
25
|
+
// silently lost the password). `-ErrorAction SilentlyContinue` keeps the line
|
|
26
|
+
// harmless on PowerShell 7, where the type is already available.
|
|
27
|
+
const PS_SECURITY_ASSEMBLY = 'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;'
|
|
28
|
+
|
|
29
|
+
function run(bin, args, input) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
execFile(bin, args, { timeout: TIMEOUT, maxBuffer: 1 << 20, ...(input != null ? { input } : {}) }, (err, stdout, stderr) => {
|
|
32
|
+
if (err) return reject(err)
|
|
33
|
+
resolve(String(stdout || '').trim())
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function platformBackend() {
|
|
39
|
+
if (process.platform === 'darwin') return 'keychain'
|
|
40
|
+
if (process.platform === 'win32') return 'windows'
|
|
41
|
+
return 'secret' // linux (secret-tool may be absent → falls back)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function account(machineId) {
|
|
45
|
+
// Only machine ids are stored; sanitize just in case.
|
|
46
|
+
return String(machineId || '').replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Redact a failed command's diagnostics: execFile puts the FULL command line
|
|
50
|
+
* (which carries the password as an argv) into `err.message`, and this string is
|
|
51
|
+
* shown in the settings UI — so the secret must never survive it. */
|
|
52
|
+
function safeReason(err, password) {
|
|
53
|
+
const raw = [err && err.stderr, err && err.message, err && err.code]
|
|
54
|
+
.filter(Boolean).map(String).join(' ')
|
|
55
|
+
const secret = String(password == null ? '' : password)
|
|
56
|
+
const scrubbed = secret ? raw.split(secret).join('***') : raw
|
|
57
|
+
return scrubbed.replace(/\s+/g, ' ').trim().slice(0, 300)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** PowerShell one-liner that DPAPI-protects a password (CurrentUser scope).
|
|
61
|
+
* Exported because the issue #30 fix (the mandatory Add-Type preamble) is only
|
|
62
|
+
* observable here — a Windows host cannot be exercised from the test suite. */
|
|
63
|
+
export function dpapiProtectScript(password) {
|
|
64
|
+
return PS_SECURITY_ASSEMBLY +
|
|
65
|
+
`$p='${String(password).replace(/'/g, "''")}';` +
|
|
66
|
+
`$s=[System.Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes($p),$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);` +
|
|
67
|
+
`[Convert]::ToBase64String($s)`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** PowerShell one-liner that reverses dpapiProtectScript(). */
|
|
71
|
+
export function dpapiUnprotectScript(b64) {
|
|
72
|
+
return PS_SECURITY_ASSEMBLY +
|
|
73
|
+
`$s=[Convert]::FromBase64String('${String(b64).replace(/'/g, "''")}');` +
|
|
74
|
+
`[Text.Encoding]::UTF8.GetString([System.Security.Cryptography.ProtectedData]::Unprotect($s,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser))`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Save a password to the OS store. Resolves {ok, backend, error} — ok:false on
|
|
78
|
+
* any failure (caller falls back to plaintext). `error` is a password-free
|
|
79
|
+
* diagnostic for the UI. */
|
|
80
|
+
export async function saveSecret(machineId, password, secretsDir) {
|
|
81
|
+
const acc = account(machineId)
|
|
82
|
+
try {
|
|
83
|
+
if (process.platform === 'darwin') {
|
|
84
|
+
await run('security', ['add-generic-password', '-U', '-s', SERVICE, '-a', acc, '-w', String(password)])
|
|
85
|
+
return { ok: true, backend: 'keychain', error: '' }
|
|
86
|
+
}
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
// DPAPI-encrypt and store under the harness home.
|
|
89
|
+
const script = dpapiProtectScript(password)
|
|
90
|
+
const b64 = await run('powershell', ['-NoProfile', '-STA', '-Command', script])
|
|
91
|
+
if (!b64) return { ok: false, backend: 'windows', error: 'DPAPI returned no ciphertext' }
|
|
92
|
+
mkdirSync(secretsDir || '.', { recursive: true })
|
|
93
|
+
writeFileSync(joinPath(secretsDir || '.', acc + '.bin'), b64, 'utf8')
|
|
94
|
+
return { ok: true, backend: 'windows', error: '' }
|
|
95
|
+
}
|
|
96
|
+
if (process.platform === 'linux') {
|
|
97
|
+
await run('secret-tool', ['store', '--label=' + SERVICE, 'service', SERVICE, 'account', acc], String(password) + '\n')
|
|
98
|
+
return { ok: true, backend: 'secret', error: '' }
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return { ok: false, backend: platformBackend(), error: safeReason(err, password) || 'secret store write failed' }
|
|
102
|
+
}
|
|
103
|
+
return { ok: false, backend: platformBackend(), error: `unsupported platform: ${process.platform}` }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Decide where a machine password is persisted (issue #30).
|
|
107
|
+
*
|
|
108
|
+
* The contract the settings UI relies on: a password is NEVER dropped. When the
|
|
109
|
+
* requested OS store fails (missing DPAPI assembly, locked keychain, absent
|
|
110
|
+
* secret-tool), this falls back to plaintext in machines.json and reports
|
|
111
|
+
* `warning: 'secret-store-failed'` so the caller can tell the user instead of
|
|
112
|
+
* silently saving a credential-less machine.
|
|
113
|
+
*
|
|
114
|
+
* `save` is injectable so the fallback is unit-testable on any platform.
|
|
115
|
+
*
|
|
116
|
+
* @returns {Promise<{stored:'secret'|'plain'|'none', credentialBackend:string, password:string, warning:string, error:string}>}
|
|
117
|
+
*/
|
|
118
|
+
export async function persistPassword({ backend, machineId, password, secretsDir, save = saveSecret }) {
|
|
119
|
+
const pw = password == null ? '' : String(password)
|
|
120
|
+
const want = backend && backend !== 'plain' ? backend : 'plain'
|
|
121
|
+
if (!pw) return { stored: 'none', credentialBackend: want, password: '', warning: '', error: '' }
|
|
122
|
+
if (want === 'plain') return { stored: 'plain', credentialBackend: 'plain', password: pw, warning: '', error: '' }
|
|
123
|
+
let res
|
|
124
|
+
try {
|
|
125
|
+
res = await save(machineId, pw, secretsDir)
|
|
126
|
+
} catch (err) {
|
|
127
|
+
res = { ok: false, error: safeReason(err, pw) }
|
|
128
|
+
}
|
|
129
|
+
if (res && res.ok) {
|
|
130
|
+
return { stored: 'secret', credentialBackend: res.backend || want, password: '', warning: '', error: '' }
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
stored: 'plain',
|
|
134
|
+
credentialBackend: 'plain',
|
|
135
|
+
password: pw,
|
|
136
|
+
warning: 'secret-store-failed',
|
|
137
|
+
error: String((res && res.error) || '') || 'secret store write failed',
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Fetch a stored password. Resolves string|null. */
|
|
142
|
+
export async function getSecret(machineId, secretsDir) {
|
|
143
|
+
const acc = account(machineId)
|
|
144
|
+
try {
|
|
145
|
+
if (process.platform === 'darwin') {
|
|
146
|
+
return await run('security', ['find-generic-password', '-s', SERVICE, '-a', acc, '-w'])
|
|
147
|
+
}
|
|
148
|
+
if (process.platform === 'win32') {
|
|
149
|
+
const b64 = readFileSync(joinPath(secretsDir || '.', acc + '.bin'), 'utf8').trim()
|
|
150
|
+
if (!b64) return null
|
|
151
|
+
const script = dpapiUnprotectScript(b64)
|
|
152
|
+
return await run('powershell', ['-NoProfile', '-STA', '-Command', script])
|
|
153
|
+
}
|
|
154
|
+
if (process.platform === 'linux') {
|
|
155
|
+
return await run('secret-tool', ['lookup', 'service', SERVICE, 'account', acc])
|
|
156
|
+
}
|
|
157
|
+
} catch { /* fall through */ }
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Delete a stored password (idempotent). */
|
|
162
|
+
export async function deleteSecret(machineId, secretsDir) {
|
|
163
|
+
const acc = account(machineId)
|
|
164
|
+
try {
|
|
165
|
+
if (process.platform === 'darwin') {
|
|
166
|
+
await run('security', ['delete-generic-password', '-s', SERVICE, '-a', acc]).catch(() => {})
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if (process.platform === 'win32') {
|
|
170
|
+
try { unlinkSync(joinPath(secretsDir || '.', acc + '.bin')) } catch { /* absent */ }
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if (process.platform === 'linux') {
|
|
174
|
+
await run('secret-tool', ['clear', 'service', SERVICE, 'account', acc]).catch(() => {})
|
|
175
|
+
}
|
|
176
|
+
} catch { /* best effort */ }
|
|
177
|
+
}
|
package/lib/deploy-tasks.js
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
// dsh-remote — host-side deploy task manager.
|
|
2
|
-
//
|
|
3
|
-
// WHY THIS EXISTS: `install` used to be a single long HTTP request (npm can
|
|
4
|
-
// legitimately take minutes). If the browser tab closed — or the fetch was
|
|
5
|
-
// cancelled — the RESPONSE was lost and, with it, every trace of what the
|
|
6
|
-
// deploy did: the user could not see the progress again, could not tell whether
|
|
7
|
-
// it succeeded, and clicking the button again would start a SECOND concurrent
|
|
8
|
-
// npm install into the same prefix. High availability here means:
|
|
9
|
-
//
|
|
10
|
-
// · the deploy runs on the HOST, not tied to any request lifecycle;
|
|
11
|
-
// · every step's outcome is recorded as it happens;
|
|
12
|
-
// · the UI can re-attach at any time and see the live state;
|
|
13
|
-
// · a second click while one is running RE-ATTACHES instead of restarting;
|
|
14
|
-
// · the result (including the machine's webAttachCommand) is recorded even
|
|
15
|
-
// if no client ever polls for it.
|
|
16
|
-
//
|
|
17
|
-
// One deploy per machine at a time — npm --prefix into the same directory from
|
|
18
|
-
// two processes corrupts the tree. A NEW request for a machine that already has
|
|
19
|
-
// a running or finished deploy returns the EXISTING task (resume semantics);
|
|
20
|
-
// `force: true` starts a fresh one after the previous is finished.
|
|
21
|
-
|
|
22
|
-
/** A deploy task's lifecycle: queued → running → done | failed | cancelled. */
|
|
23
|
-
export class DeployTasks {
|
|
24
|
-
constructor() {
|
|
25
|
-
/** @type {Map<string, object>} machineId → task */
|
|
26
|
-
this.byMachine = new Map()
|
|
27
|
-
/** @type {Map<string, object>} taskId → task (for stable polling) */
|
|
28
|
-
this.byId = new Map()
|
|
29
|
-
this.nextId = 1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The task for a machine, if one exists in ANY state.
|
|
34
|
-
* @returns {object|null}
|
|
35
|
-
*/
|
|
36
|
-
forMachine(machineId) {
|
|
37
|
-
return this.byMachine.get(String(machineId || '')) || null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
get(id) {
|
|
41
|
-
return this.byId.get(String(id || '')) || null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Start a deploy for a machine, unless one is already running.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} machineId
|
|
48
|
-
* @param {Function} fn - async (ctx) => result. ctx = { progress(patch) }.
|
|
49
|
-
* @returns {{task: object, reused: boolean}} the task plus whether it already
|
|
50
|
-
* existed (caller reports "resumed" so the UI does not claim a fresh start).
|
|
51
|
-
*/
|
|
52
|
-
start(machineId, label, fn) {
|
|
53
|
-
const mid = String(machineId || '')
|
|
54
|
-
const existing = this.byMachine.get(mid)
|
|
55
|
-
if (existing && (existing.status === 'queued' || existing.status === 'running')) {
|
|
56
|
-
return { task: existing, reused: true }
|
|
57
|
-
}
|
|
58
|
-
const id = `deploy-${this.nextId++}`
|
|
59
|
-
const task = {
|
|
60
|
-
id,
|
|
61
|
-
machineId: mid,
|
|
62
|
-
kind: 'deploy',
|
|
63
|
-
label: String(label || ''),
|
|
64
|
-
status: 'queued',
|
|
65
|
-
// Steps are appended as they finish; `steps` is what the UI renders.
|
|
66
|
-
steps: [],
|
|
67
|
-
// The current in-flight step, so a re-attached UI can show "正在安装…"
|
|
68
|
-
// rather than a stale list with no hint that more is coming.
|
|
69
|
-
current: '',
|
|
70
|
-
progress: {},
|
|
71
|
-
result: null,
|
|
72
|
-
error: null,
|
|
73
|
-
startedAt: null,
|
|
74
|
-
finishedAt: null,
|
|
75
|
-
cancelled: false,
|
|
76
|
-
}
|
|
77
|
-
this.byMachine.set(mid, task)
|
|
78
|
-
this.byId.set(id, task)
|
|
79
|
-
// Deliberately NOT queued through the shared TaskManager: that one is
|
|
80
|
-
// single-flight across ALL tasks (one sync at a time), while deploys to
|
|
81
|
-
// DIFFERENT machines must run in parallel.
|
|
82
|
-
task.status = 'running'
|
|
83
|
-
task.startedAt = new Date().toISOString()
|
|
84
|
-
const ctx = {
|
|
85
|
-
progress: (p) => { task.progress = { ...task.progress, ...p } },
|
|
86
|
-
get cancelled() { return !!task.cancelled },
|
|
87
|
-
}
|
|
88
|
-
Promise.resolve()
|
|
89
|
-
.then(() => fn(ctx))
|
|
90
|
-
.then((result) => {
|
|
91
|
-
task.result = result || null
|
|
92
|
-
task.status = task.cancelled ? 'cancelled' : 'done'
|
|
93
|
-
})
|
|
94
|
-
.catch((err) => {
|
|
95
|
-
task.error = String((err && err.message) || err)
|
|
96
|
-
// A step-level failure carries the step list (and hints) as structured
|
|
97
|
-
// data so the UI can render exactly what the old single-response flow
|
|
98
|
-
// rendered — re-attaching must lose nothing.
|
|
99
|
-
if (err && err.detail && typeof err.detail === 'object') task.detail = err.detail
|
|
100
|
-
task.status = task.cancelled ? 'cancelled' : 'failed'
|
|
101
|
-
})
|
|
102
|
-
.finally(() => {
|
|
103
|
-
task.current = ''
|
|
104
|
-
task.finishedAt = new Date().toISOString()
|
|
105
|
-
})
|
|
106
|
-
return { task, reused: false }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Cooperative cancel (a running npm step still finishes; later steps stop). */
|
|
110
|
-
cancel(machineId) {
|
|
111
|
-
const t = this.byMachine.get(String(machineId || ''))
|
|
112
|
-
if (!t) return false
|
|
113
|
-
if (t.status === 'queued' || t.status === 'running') {
|
|
114
|
-
t.cancelled = true
|
|
115
|
-
return true
|
|
116
|
-
}
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Serializable view for the UI. */
|
|
121
|
-
describe(t) {
|
|
122
|
-
if (!t) return null
|
|
123
|
-
return {
|
|
124
|
-
id: t.id,
|
|
125
|
-
machineId: t.machineId,
|
|
126
|
-
kind: t.kind,
|
|
127
|
-
status: t.status, // queued | running | done | failed | cancelled
|
|
128
|
-
steps: t.steps, // [{id,title,ok,code,output}]
|
|
129
|
-
current: t.current, // in-flight step title ('' when none)
|
|
130
|
-
progress: t.progress,
|
|
131
|
-
result: t.result,
|
|
132
|
-
error: t.error,
|
|
133
|
-
detail: t.detail || null,
|
|
134
|
-
startedAt: t.startedAt,
|
|
135
|
-
finishedAt: t.finishedAt,
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
list() {
|
|
140
|
-
return [...this.byMachine.values()].map((t) => this.describe(t))
|
|
141
|
-
}
|
|
142
|
-
}
|
|
1
|
+
// dsh-remote — host-side deploy task manager.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS EXISTS: `install` used to be a single long HTTP request (npm can
|
|
4
|
+
// legitimately take minutes). If the browser tab closed — or the fetch was
|
|
5
|
+
// cancelled — the RESPONSE was lost and, with it, every trace of what the
|
|
6
|
+
// deploy did: the user could not see the progress again, could not tell whether
|
|
7
|
+
// it succeeded, and clicking the button again would start a SECOND concurrent
|
|
8
|
+
// npm install into the same prefix. High availability here means:
|
|
9
|
+
//
|
|
10
|
+
// · the deploy runs on the HOST, not tied to any request lifecycle;
|
|
11
|
+
// · every step's outcome is recorded as it happens;
|
|
12
|
+
// · the UI can re-attach at any time and see the live state;
|
|
13
|
+
// · a second click while one is running RE-ATTACHES instead of restarting;
|
|
14
|
+
// · the result (including the machine's webAttachCommand) is recorded even
|
|
15
|
+
// if no client ever polls for it.
|
|
16
|
+
//
|
|
17
|
+
// One deploy per machine at a time — npm --prefix into the same directory from
|
|
18
|
+
// two processes corrupts the tree. A NEW request for a machine that already has
|
|
19
|
+
// a running or finished deploy returns the EXISTING task (resume semantics);
|
|
20
|
+
// `force: true` starts a fresh one after the previous is finished.
|
|
21
|
+
|
|
22
|
+
/** A deploy task's lifecycle: queued → running → done | failed | cancelled. */
|
|
23
|
+
export class DeployTasks {
|
|
24
|
+
constructor() {
|
|
25
|
+
/** @type {Map<string, object>} machineId → task */
|
|
26
|
+
this.byMachine = new Map()
|
|
27
|
+
/** @type {Map<string, object>} taskId → task (for stable polling) */
|
|
28
|
+
this.byId = new Map()
|
|
29
|
+
this.nextId = 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The task for a machine, if one exists in ANY state.
|
|
34
|
+
* @returns {object|null}
|
|
35
|
+
*/
|
|
36
|
+
forMachine(machineId) {
|
|
37
|
+
return this.byMachine.get(String(machineId || '')) || null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get(id) {
|
|
41
|
+
return this.byId.get(String(id || '')) || null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Start a deploy for a machine, unless one is already running.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} machineId
|
|
48
|
+
* @param {Function} fn - async (ctx) => result. ctx = { progress(patch) }.
|
|
49
|
+
* @returns {{task: object, reused: boolean}} the task plus whether it already
|
|
50
|
+
* existed (caller reports "resumed" so the UI does not claim a fresh start).
|
|
51
|
+
*/
|
|
52
|
+
start(machineId, label, fn) {
|
|
53
|
+
const mid = String(machineId || '')
|
|
54
|
+
const existing = this.byMachine.get(mid)
|
|
55
|
+
if (existing && (existing.status === 'queued' || existing.status === 'running')) {
|
|
56
|
+
return { task: existing, reused: true }
|
|
57
|
+
}
|
|
58
|
+
const id = `deploy-${this.nextId++}`
|
|
59
|
+
const task = {
|
|
60
|
+
id,
|
|
61
|
+
machineId: mid,
|
|
62
|
+
kind: 'deploy',
|
|
63
|
+
label: String(label || ''),
|
|
64
|
+
status: 'queued',
|
|
65
|
+
// Steps are appended as they finish; `steps` is what the UI renders.
|
|
66
|
+
steps: [],
|
|
67
|
+
// The current in-flight step, so a re-attached UI can show "正在安装…"
|
|
68
|
+
// rather than a stale list with no hint that more is coming.
|
|
69
|
+
current: '',
|
|
70
|
+
progress: {},
|
|
71
|
+
result: null,
|
|
72
|
+
error: null,
|
|
73
|
+
startedAt: null,
|
|
74
|
+
finishedAt: null,
|
|
75
|
+
cancelled: false,
|
|
76
|
+
}
|
|
77
|
+
this.byMachine.set(mid, task)
|
|
78
|
+
this.byId.set(id, task)
|
|
79
|
+
// Deliberately NOT queued through the shared TaskManager: that one is
|
|
80
|
+
// single-flight across ALL tasks (one sync at a time), while deploys to
|
|
81
|
+
// DIFFERENT machines must run in parallel.
|
|
82
|
+
task.status = 'running'
|
|
83
|
+
task.startedAt = new Date().toISOString()
|
|
84
|
+
const ctx = {
|
|
85
|
+
progress: (p) => { task.progress = { ...task.progress, ...p } },
|
|
86
|
+
get cancelled() { return !!task.cancelled },
|
|
87
|
+
}
|
|
88
|
+
Promise.resolve()
|
|
89
|
+
.then(() => fn(ctx))
|
|
90
|
+
.then((result) => {
|
|
91
|
+
task.result = result || null
|
|
92
|
+
task.status = task.cancelled ? 'cancelled' : 'done'
|
|
93
|
+
})
|
|
94
|
+
.catch((err) => {
|
|
95
|
+
task.error = String((err && err.message) || err)
|
|
96
|
+
// A step-level failure carries the step list (and hints) as structured
|
|
97
|
+
// data so the UI can render exactly what the old single-response flow
|
|
98
|
+
// rendered — re-attaching must lose nothing.
|
|
99
|
+
if (err && err.detail && typeof err.detail === 'object') task.detail = err.detail
|
|
100
|
+
task.status = task.cancelled ? 'cancelled' : 'failed'
|
|
101
|
+
})
|
|
102
|
+
.finally(() => {
|
|
103
|
+
task.current = ''
|
|
104
|
+
task.finishedAt = new Date().toISOString()
|
|
105
|
+
})
|
|
106
|
+
return { task, reused: false }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Cooperative cancel (a running npm step still finishes; later steps stop). */
|
|
110
|
+
cancel(machineId) {
|
|
111
|
+
const t = this.byMachine.get(String(machineId || ''))
|
|
112
|
+
if (!t) return false
|
|
113
|
+
if (t.status === 'queued' || t.status === 'running') {
|
|
114
|
+
t.cancelled = true
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Serializable view for the UI. */
|
|
121
|
+
describe(t) {
|
|
122
|
+
if (!t) return null
|
|
123
|
+
return {
|
|
124
|
+
id: t.id,
|
|
125
|
+
machineId: t.machineId,
|
|
126
|
+
kind: t.kind,
|
|
127
|
+
status: t.status, // queued | running | done | failed | cancelled
|
|
128
|
+
steps: t.steps, // [{id,title,ok,code,output}]
|
|
129
|
+
current: t.current, // in-flight step title ('' when none)
|
|
130
|
+
progress: t.progress,
|
|
131
|
+
result: t.result,
|
|
132
|
+
error: t.error,
|
|
133
|
+
detail: t.detail || null,
|
|
134
|
+
startedAt: t.startedAt,
|
|
135
|
+
finishedAt: t.finishedAt,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
list() {
|
|
140
|
+
return [...this.byMachine.values()].map((t) => this.describe(t))
|
|
141
|
+
}
|
|
142
|
+
}
|