dsh-db-tool 1.9.3 → 1.9.4
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 +22 -0
- package/README.md +27 -4
- package/README_EN.md +29 -4
- package/client/client.js +70 -30
- package/dist/adapters/sqlserver/index.js +126 -12
- package/package.json +1 -1
- package/skills/db-admin/SKILL.md +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog — dsh-db-tool
|
|
2
2
|
|
|
3
|
+
## 1.9.4
|
|
4
|
+
|
|
5
|
+
SQL Server 连接改为「开箱即连」:表单只留主机/端口/账号/密码,加密参数收进「高级选项」,新建连接默认 `encrypt=auto`——不加密优先,服务器强制加密时自动升级并信任其证书。
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- **前端「高级选项」折叠区**:`trustServerCertificate` / `serverName` / `caCert` / `tlsMinVersion` / `tdsVersion` 等参数从主表单移入默认收起的折叠区,主表单只留主机、端口、账号、密码与库名。这些控件本身及其校验、回填逻辑不变,只是换了入口——常规连接不再需要理解加密参数,排障时展开即可。
|
|
10
|
+
- **`encrypt=auto`(新建连接默认值)**:建连先不带加密;若服务器**强制加密**(如 `Force Encryption=Yes` 或 Azure SQL,表现为 `EENCRYPT` / 「服务器要求加密连接」),自动改用 `encrypt=true` + `trustServerCertificate=true` 重试一次。服务器未强制加密的实例(内网自建居多)因此不再被自签证书问题挡住,这是「像 Navicat 一样开箱即连」的核心。
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- **不再因为默认值而必须先读懂 TLS 才能连库**:此前默认 `encrypt=true` + `trustServerCertificate=false`,连自签证书实例必然失败并报证书错误,用户必须先展开并理解 `caCert` / `serverName` 才连得上。现在这条路径由默认策略自己消化,证书参数退化为纯排障工具。这是默认值调整带来的连带效果,并非独立缺陷修复。
|
|
15
|
+
|
|
16
|
+
### 行为变更(升级须知)
|
|
17
|
+
|
|
18
|
+
- **默认 `encrypt` 从 `true` 改为 `auto`**,新建连接的行为随之改变,详见上文。
|
|
19
|
+
- **自动重试的安全边界(有意收窄,不要当作无限兜底)**:
|
|
20
|
+
- **仅重试一次**,不做重试链;失败即把原始错误抛给用户。
|
|
21
|
+
- **仅针对「服务器强制加密」这一种原因**。证书校验失败(`self signed certificate` / `CERT_` 家族)、TLS 版本协商失败(`UNSUPPORTED_PROTOCOL`)、登录失败等其他原因**不触发重试**——盲目重试会覆盖真实错因,把排障引向错误方向。
|
|
22
|
+
- **显式指定 `encrypt` 时不做任何重试**(`true` / `false` / `strict` 皆是)。显式值即最终策略,工具不替用户改主意,行为完全可预期。
|
|
23
|
+
- **`auto` 不是「自动加密」**:在未强制加密的服务器上,`auto` 的行为与 `encrypt=false` 相同,即**明文发送 LOGIN7(含账号密码)**。默认值的定位是「可信内网开箱即连」,跨公网或不可信网段请显式设 `encrypt=true`。README / README_EN 与两份 SKILL.md 均已同步这条风险说明。
|
|
24
|
+
|
|
3
25
|
## 1.9.3
|
|
4
26
|
|
|
5
27
|
SQL Server 支持范围扩到 2005(9.0)→ 2025(17.0),修复前端无法配置加密参数导致的「怎么改都连不上」,并在面板上加插件版本号标签。
|
package/README.md
CHANGED
|
@@ -147,11 +147,28 @@ Navicat 风格的右侧栏多标签编辑器(CodeMirror 6):
|
|
|
147
147
|
|
|
148
148
|
覆盖 SQL Server 2005(9.0)→ 2025(17.0)与 Azure SQL Database。
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
**常规使用只需要填主机、端口、账号、密码**(外加库名)就能连上,加密与证书参数一律不用管。表单默认只显示这四个必填项,其余参数收在「高级选项」折叠区里,需要排障时再展开。
|
|
151
|
+
|
|
152
|
+
### 默认加密策略:不加密优先,被拒后自动升级
|
|
153
|
+
|
|
154
|
+
新建连接默认 `encrypt=auto`(表单加密一栏的默认档):
|
|
155
|
+
|
|
156
|
+
1. 先**不带加密**建连——服务器没强制加密的实例(内网自建居多)直接连上,不再因为自签证书或多一层 TLS 卡住。
|
|
157
|
+
2. 若服务器**强制加密**(报 `EENCRYPT` / 提示「服务器要求加密连接」,如开启 `Force Encryption=Yes` 或 Azure SQL),自动改用 `encrypt=true` + `trustServerCertificate=true` **重试一次**,用户无感。
|
|
158
|
+
|
|
159
|
+
**自动重试的边界(有意收窄,不要理解成「无限自动兜底」)**:
|
|
160
|
+
|
|
161
|
+
- **只重试一次**,不会连环重试,失败就把原始错误报给用户。
|
|
162
|
+
- **只针对「服务器强制加密」这一种原因**。证书校验失败、TLS 版本协商失败、账号密码错误等其他失败**不会**触发重试——盲目重试只会把这些错误的真实原因盖掉,把排障引向错误方向。
|
|
163
|
+
- **显式写 `encrypt=true` / `false` / `strict` 时不做任何重试**。你指定的策略就是最终策略,工具不替你改主意。
|
|
164
|
+
|
|
165
|
+
**`auto` 在未强制加密的服务器上等同于 `encrypt=false`,也就是明文传输**——这是默认策略的代价,风险说明见下文「`encrypt=false` 的真实语义」,别跳过。
|
|
166
|
+
|
|
167
|
+
**连接 URL 参数**(高级用户与排障时用,日常连接**无需**填这些;分字段表单同样支持):
|
|
151
168
|
|
|
152
169
|
| 参数 | 取值 | 默认 |
|
|
153
170
|
|---|---|---|
|
|
154
|
-
| `encrypt` | `true` / `false` / `strict` | `
|
|
171
|
+
| `encrypt` | `auto` / `true` / `false` / `strict` | `auto`(先不加密,服务器强制加密时自动升级并信任其证书,仅重试一次) |
|
|
155
172
|
| `trustServerCertificate` | `true` / `false` | `false` |
|
|
156
173
|
| `tdsVersion` | `7_4` / `7_3_A` / `7_3_B` / `7_2` / `7_1` | `7_4`(服务器自动降级,通常无需设置) |
|
|
157
174
|
| `tlsMinVersion` | `TLSv1` / `TLSv1.1` / `TLSv1.2` | 不设置(Node 默认 TLSv1.2) |
|
|
@@ -160,6 +177,8 @@ Navicat 风格的右侧栏多标签编辑器(CodeMirror 6):
|
|
|
160
177
|
| `instanceName` | 命名实例名(与 port 互斥) | — |
|
|
161
178
|
| `useUTC` | `true` / `false` | `true` |
|
|
162
179
|
|
|
180
|
+
`caCert` / `serverName` / `trustServerCertificate` / `tlsMinVersion` / `tdsVersion` 都在表单的**「高级选项」**折叠区里(默认收起),专供证书与老版本实例排障;正常连接不需要展开它。
|
|
181
|
+
|
|
163
182
|
**TDS 版本不需要手动选**:客户端固定声明 `7.4`,服务器会按自身能力自动降级(2005→7.2、2008→7.3.A、2008 R2→7.3.B)。这是 [MS-TDS](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac) 规定的行为(客户端声明高于服务器时,服务器**必须**使用自己最高的版本)。`tdsVersion` 仅作排障覆盖项保留。
|
|
164
183
|
|
|
165
184
|
**连老实例时先分清两类 TLS 失败,解法正交、不可互相替代**:
|
|
@@ -175,7 +194,11 @@ Navicat 风格的右侧栏多标签编辑器(CodeMirror 6):
|
|
|
175
194
|
- 服务器**强制**加密(`Force Encryption=Yes` 或 Azure SQL)时,`encrypt=false` 会被拒绝(提示「服务器要求加密连接」)——此时仍必须开加密。
|
|
176
195
|
- 微软文档中「登录包始终加密」指的是**服务器强制加密或客户端 `encrypt=true` 时**的语义,不能推广成「`encrypt=false` 也总有 TLS 握手」。
|
|
177
196
|
|
|
178
|
-
|
|
197
|
+
**这段也是默认 `encrypt=auto` 的安全含义**:`auto` 的第一步就是「不加密建连」,落到未强制加密的服务器上时,行为与 `encrypt=false` **完全相同**——明文传账号密码。`auto` 只在服务器拒绝明文时才升级到 TLS,它不是「自动帮你加密」,而是「先按不加密试,被拒才加密」。因此:
|
|
198
|
+
|
|
199
|
+
- 在**可信内网**用默认值没问题;跨公网、跨不可信网段连 SQL Server 时,请显式设 `encrypt=true`(并在需要时用 `caCert` / `serverName` 解决证书问题),不要依赖 `auto`。
|
|
200
|
+
- 需要强制安全档时显式写 `encrypt=true`:显式值不做任何自动重试,行为完全可预期。
|
|
201
|
+
- `encrypt=false` 与 `encrypt=auto` 都应视为**明确的降级选择**,不是「只是没开高级选项」。
|
|
179
202
|
|
|
180
203
|
**`encrypt=strict`(TDS 8.0)**:仅 SQL Server 2022+ 且开启 Force Strict Encryption 的实例支持。该模式下 TLS 在登录包之前就完成握手,因此 **`trustServerCertificate` 完全不生效**(驱动不传 `rejectUnauthorized`,证书强制校验),只能靠 `caCert` 或 `serverName`。
|
|
181
204
|
|
|
@@ -208,7 +231,7 @@ DSH 桌面端 0.2.0-rc.2+ 已捆绑 `dsh` 命令:
|
|
|
208
231
|
2. 安装插件(npm 安装须钉精确版本,`@latest` 会因 release-age 校验回落):
|
|
209
232
|
|
|
210
233
|
```bash
|
|
211
|
-
dsh plugin --profile desktop add dsh-db-tool@1.9.
|
|
234
|
+
dsh plugin --profile desktop add dsh-db-tool@1.9.4 --registry=https://registry.npmjs.org/
|
|
212
235
|
```
|
|
213
236
|
|
|
214
237
|
3. **重启桌面端**生效
|
package/README_EN.md
CHANGED
|
@@ -147,11 +147,28 @@ Both are contract behaviours of the official right sidebar and are easy to mista
|
|
|
147
147
|
|
|
148
148
|
Covers SQL Server 2005 (9.0) through 2025 (17.0) and Azure SQL Database.
|
|
149
149
|
|
|
150
|
-
**
|
|
150
|
+
**For normal use you only fill in host, port, user and password** (plus the database). Encryption and certificate parameters are not your problem. The form shows only those four required fields; everything else lives in a collapsed **Advanced options** section you open only when troubleshooting.
|
|
151
|
+
|
|
152
|
+
### Default encryption policy: prefer plaintext, upgrade automatically when the server insists
|
|
153
|
+
|
|
154
|
+
New connections default to `encrypt=auto` (the default option in the form's encryption field):
|
|
155
|
+
|
|
156
|
+
1. Connect **without encryption** first — instances that do not force encryption (typically self-hosted, on an intranet) connect right away, no longer blocked by a self-signed certificate or an extra TLS layer.
|
|
157
|
+
2. If the server **forces encryption** (`EENCRYPT` / "server requires encryption", e.g. `Force Encryption=Yes` or Azure SQL), retry **once** with `encrypt=true` + `trustServerCertificate=true`. The user does not see this.
|
|
158
|
+
|
|
159
|
+
**Boundaries of the automatic retry (deliberately narrow — not an unlimited safety net)**:
|
|
160
|
+
|
|
161
|
+
- **Exactly one retry.** No retry chains; on failure the original error is surfaced.
|
|
162
|
+
- **Only for the "server forces encryption" cause.** Certificate-validation failures, TLS-version negotiation failures, bad credentials and everything else do **not** trigger a retry — blind retrying would bury the real cause and send troubleshooting in the wrong direction.
|
|
163
|
+
- **No retry at all when `encrypt` is set explicitly** to `true` / `false` / `strict`. The policy you specify is final; the tool does not second-guess it.
|
|
164
|
+
|
|
165
|
+
**On a server that does not force encryption, `auto` behaves exactly like `encrypt=false`, i.e. cleartext transmission** — that is the cost of this default; see "What `encrypt=false` really does" below. Do not skip it.
|
|
166
|
+
|
|
167
|
+
**Connection URL parameters** (for advanced users and troubleshooting — **not needed** for everyday connections; the field-by-field form supports the same set):
|
|
151
168
|
|
|
152
169
|
| Parameter | Values | Default |
|
|
153
170
|
|---|---|---|
|
|
154
|
-
| `encrypt` | `true` / `false` / `strict` | `
|
|
171
|
+
| `encrypt` | `auto` / `true` / `false` / `strict` | `auto` (unencrypted first; upgrade with certificate trust when the server forces encryption — one retry only) |
|
|
155
172
|
| `trustServerCertificate` | `true` / `false` | `false` |
|
|
156
173
|
| `tdsVersion` | `7_4` / `7_3_A` / `7_3_B` / `7_2` / `7_1` | `7_4` (negotiated down automatically; normally leave unset) |
|
|
157
174
|
| `tlsMinVersion` | `TLSv1` / `TLSv1.1` / `TLSv1.2` | unset (Node default TLSv1.2) |
|
|
@@ -160,6 +177,8 @@ Covers SQL Server 2005 (9.0) through 2025 (17.0) and Azure SQL Database.
|
|
|
160
177
|
| `instanceName` | Named instance (mutually exclusive with port) | — |
|
|
161
178
|
| `useUTC` | `true` / `false` | `true` |
|
|
162
179
|
|
|
180
|
+
`caCert` / `serverName` / `trustServerCertificate` / `tlsMinVersion` / `tdsVersion` all live in the **Advanced options** section of the form (collapsed by default); they exist for certificate and legacy-instance troubleshooting and are not needed for normal connections.
|
|
181
|
+
|
|
163
182
|
**You do not need to pick a TDS version**: the client always declares `7.4` and the server negotiates down to what it supports (2005→7.2, 2008→7.3.A, 2008 R2→7.3.B). This is mandated by [MS-TDS](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac) (when the client declares a higher version, the server **must** use the highest version it supports). `tdsVersion` remains only as a troubleshooting override.
|
|
164
183
|
|
|
165
184
|
**When connecting to older instances, tell the two TLS failure families apart — their remedies are orthogonal and not interchangeable**:
|
|
@@ -175,7 +194,13 @@ TLS 1.2 support by version: 2016+ has it natively; 2012 SP4 / 2014 SP2 and later
|
|
|
175
194
|
- When the server **does** force encryption (`Force Encryption=Yes`, Azure SQL), `encrypt=false` is rejected ("server requires encryption"), so encryption is still mandatory there.
|
|
176
195
|
- Microsoft's "the login packet is always encrypted" statement applies **only when the server forces encryption or the client sets `encrypt=true`**; it must not be generalised into "`encrypt=false` still performs a TLS handshake".
|
|
177
196
|
|
|
178
|
-
|
|
197
|
+
Setting `encrypt=true` explicitly is therefore the secure setting; treat `encrypt=false` as an explicit downgrade.
|
|
198
|
+
|
|
199
|
+
**The same paragraph defines what the default `encrypt=auto` means for security**: the first step of `auto` is an unencrypted connection, so against a server that does not force encryption it behaves **exactly** like `encrypt=false` — credentials in cleartext. `auto` only upgrades to TLS when the server refuses plaintext; it is not "automatically encrypt for you", it is "try unencrypted first, encrypt only when rejected". Therefore:
|
|
200
|
+
|
|
201
|
+
- The default is fine on a **trusted intranet**; when the SQL Server is reached over the public internet or an untrusted segment, set `encrypt=true` explicitly (and use `caCert` / `serverName` for certificate issues) instead of relying on `auto`.
|
|
202
|
+
- Set `encrypt=true` explicitly when you need a guaranteed secure setting: explicit values never retry, so behaviour is fully predictable.
|
|
203
|
+
- Treat both `encrypt=false` and `encrypt=auto` as an **explicit downgrade**, not as "just left the advanced options closed".
|
|
179
204
|
|
|
180
205
|
**`encrypt=strict` (TDS 8.0)**: only supported by SQL Server 2022+ instances with Force Strict Encryption enabled. In this mode TLS completes before the login packet, so **`trustServerCertificate` has no effect at all** (the driver omits `rejectUnauthorized` and validation is mandatory); use `caCert` or `serverName` instead.
|
|
181
206
|
|
|
@@ -208,7 +233,7 @@ DSH desktop 0.2.0-rc.2+ bundles the `dsh` command:
|
|
|
208
233
|
2. Install the plugin (npm installs must pin an exact version — `@latest` falls back to an older release due to release-age checks):
|
|
209
234
|
|
|
210
235
|
```bash
|
|
211
|
-
dsh plugin --profile desktop add dsh-db-tool@1.9.
|
|
236
|
+
dsh plugin --profile desktop add dsh-db-tool@1.9.4 --registry=https://registry.npmjs.org/
|
|
212
237
|
```
|
|
213
238
|
|
|
214
239
|
3. **Restart the desktop app** to load it
|
package/client/client.js
CHANGED
|
@@ -119,14 +119,16 @@ window.__ModuleLoader__.load({
|
|
|
119
119
|
fieldsTlsMinVersion: "最低 TLS 版本(连老实例报协议族错误时才需要下探)",
|
|
120
120
|
tlsMinDefault: "默认(TLSv1.2)",
|
|
121
121
|
fieldsEncrypt: "加密",
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
encryptAuto: "自动(推荐)",
|
|
123
|
+
// 原「默认(请求加密)」空值项已由 auto 取代;本键现在是 value=true 的文案(强制加密)
|
|
124
|
+
encryptDefault: "强制加密",
|
|
124
125
|
encryptOff: "不主动加密(encrypt=false)",
|
|
125
126
|
encryptStrict: "严格加密 TDS 8.0(strict)",
|
|
126
127
|
fieldsTrustCert: "信任服务器证书(跳过证书校验)",
|
|
127
128
|
fieldsServerName: "证书校验用名称(用 IP 连接但证书签给域名时填)",
|
|
128
129
|
fieldsCaCert: "受信 CA(PEM 文件路径或证书文本,推荐)",
|
|
129
130
|
tlsHint: "证书类错误请装 CA(推荐)或勾选信任证书;协议版本类错误请下探最低 TLS 版本。两者解法不同,不能互相替代。",
|
|
131
|
+
advancedTls: "高级选项",
|
|
130
132
|
ssl: "启用 SSL",
|
|
131
133
|
test: "测试",
|
|
132
134
|
testing: "测试中…",
|
|
@@ -360,14 +362,15 @@ window.__ModuleLoader__.load({
|
|
|
360
362
|
fieldsTlsMinVersion: "Minimum TLS version (lower it only when an old instance reports a protocol error)",
|
|
361
363
|
tlsMinDefault: "Default (TLSv1.2)",
|
|
362
364
|
fieldsEncrypt: "Encryption",
|
|
363
|
-
|
|
364
|
-
|
|
365
|
+
encryptAuto: "Automatic (recommended)",
|
|
366
|
+
encryptDefault: "Mandatory encryption",
|
|
365
367
|
encryptOff: "Optional (encrypt=false)",
|
|
366
368
|
encryptStrict: "Strict TDS 8.0 (strict)",
|
|
367
369
|
fieldsTrustCert: "Trust server certificate (skip certificate validation)",
|
|
368
370
|
fieldsServerName: "Name to validate (set when connecting by IP but the certificate names a host)",
|
|
369
371
|
fieldsCaCert: "Trusted CA (PEM file path or certificate text, recommended)",
|
|
370
372
|
tlsHint: "Certificate errors: install the CA (recommended) or trust the certificate. Protocol-version errors: lower the minimum TLS version. These are different fixes and cannot substitute for each other.",
|
|
373
|
+
advancedTls: "Advanced options",
|
|
371
374
|
ssl: "Enable SSL",
|
|
372
375
|
test: "Test",
|
|
373
376
|
testing: "Testing…",
|
|
@@ -849,7 +852,19 @@ window.__ModuleLoader__.load({
|
|
|
849
852
|
}
|
|
850
853
|
|
|
851
854
|
/* ---------------- 连接管理 ---------------- */
|
|
852
|
-
|
|
855
|
+
// encrypt 默认 'auto':对齐 Navicat 的「自动」策略(适配器按服务端能力协商),
|
|
856
|
+
// 而不是把「强制加密」当默认,避免老实例连不上时用户被迫先找开关。
|
|
857
|
+
const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", urlUser: "", urlPassword: "", host: "", port: "", user: "", password: "", database: "", tlsMinVersion: "", encrypt: "auto", trustServerCertificate: false, serverName: "", caCert: "", ssl: false };
|
|
858
|
+
|
|
859
|
+
/** 是否有任一高级 TLS 字段被存过(编辑时据此默认展开高级区,避免用户看不到自己存过的配置) */
|
|
860
|
+
function hasAdvancedTls(meta) {
|
|
861
|
+
if (!meta) return false;
|
|
862
|
+
return !!(meta.tlsMinVersion || meta.serverName || meta.caCert || meta.trustServerCertificate
|
|
863
|
+
|| (meta.encrypt !== undefined && meta.encrypt !== null && meta.encrypt !== "" && meta.encrypt !== "auto"));
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// 高级 TLS 折叠区容器 id(aria-controls 指过来;单表单实例,固定值足够)
|
|
867
|
+
const TLS_ADVANCED_ID = "dbt-tls-advanced";
|
|
853
868
|
|
|
854
869
|
// 口令弹窗(ManageView 局部复用,导出/导入共用):键盘契约对齐 DangerDialog
|
|
855
870
|
// (Escape=取消、Tab 在对话框内循环锁定);差异:初始焦点在口令输入框,确认按钮主色。
|
|
@@ -1377,8 +1392,11 @@ window.__ModuleLoader__.load({
|
|
|
1377
1392
|
host: c.host || "", port: c.port != null ? String(c.port) : "",
|
|
1378
1393
|
user: c.user || "", database: c.database || "",
|
|
1379
1394
|
tlsMinVersion: c.tlsMinVersion || "",
|
|
1380
|
-
// encrypt
|
|
1381
|
-
|
|
1395
|
+
// encrypt 四态:'auto'(推荐,交适配器协商)/ true / false / 'strict',回填时保持原样。
|
|
1396
|
+
// 缺省(老记录没有该键)回落到 EMPTY_FORM 的 'auto':下拉已无空值项,
|
|
1397
|
+
// 回填空串会让 select 显示为空白选中项。
|
|
1398
|
+
encrypt: typeof c.encrypt === "boolean" ? String(c.encrypt)
|
|
1399
|
+
: (c.encrypt === "strict" || c.encrypt === "auto" ? c.encrypt : EMPTY_FORM.encrypt),
|
|
1382
1400
|
trustServerCertificate: !!c.trustServerCertificate,
|
|
1383
1401
|
serverName: c.serverName || "",
|
|
1384
1402
|
caCert: c.caCert || "",
|
|
@@ -1409,6 +1427,9 @@ window.__ModuleLoader__.load({
|
|
|
1409
1427
|
const [form, setForm] = React.useState(() => props.initial || freshForm());
|
|
1410
1428
|
const [dirty, setDirty] = React.useState(() => new Set()); // 用户手改过的字段(切 kind 时保留)
|
|
1411
1429
|
const [draftTest, setDraftTest] = React.useState(null); // null | {ok, msg}
|
|
1430
|
+
// 高级 TLS 区折叠态:默认收起(对齐 Navicat 的简洁界面);编辑已有连接且存过任一高级字段时默认展开。
|
|
1431
|
+
// 折叠只影响「是否渲染」,form 里的值不受影响 —— 收起状态照样回填与提交,绝不因隐藏丢配置。
|
|
1432
|
+
const [advancedOpen, setAdvancedOpen] = React.useState(() => hasAdvancedTls(props.initial));
|
|
1412
1433
|
// 无障碍:URL/分字段切换(及表单挂载)后焦点引导进该模式首个输入,避免键盘/SR 用户滞留切换按钮
|
|
1413
1434
|
const firstInputRef = React.useRef(null);
|
|
1414
1435
|
React.useEffect(() => { if (firstInputRef.current) firstInputRef.current.focus(); }, [form.mode]);
|
|
@@ -1464,9 +1485,11 @@ window.__ModuleLoader__.load({
|
|
|
1464
1485
|
// 不改 TLS 下限),发空字符串反而会触发服务端「取值非法」的 fail-closed 校验。
|
|
1465
1486
|
if (form.kind === "sqlserver") {
|
|
1466
1487
|
if (form.tlsMinVersion) body.fields.tlsMinVersion = form.tlsMinVersion;
|
|
1467
|
-
// encrypt
|
|
1488
|
+
// encrypt 四态:'auto' / true / false / 'strict'。这是证书族与协议族错误的正确出口,
|
|
1468
1489
|
// 也是「服务器强制加密」与「TDS 8.0 严格加密」两条路径的开关。
|
|
1469
|
-
|
|
1490
|
+
// 'auto' 必须原样发出字符串,不得退化成布尔(退化成 true 就丢了协商语义)。
|
|
1491
|
+
if (form.encrypt === "auto") body.fields.encrypt = "auto";
|
|
1492
|
+
else if (form.encrypt === "true") body.fields.encrypt = true;
|
|
1470
1493
|
else if (form.encrypt === "false") body.fields.encrypt = false;
|
|
1471
1494
|
else if (form.encrypt === "strict") body.fields.encrypt = "strict";
|
|
1472
1495
|
// 信任自签证书:只影响证书校验,不影响加密协商;strict 下驱动不传
|
|
@@ -1539,33 +1562,50 @@ window.__ModuleLoader__.load({
|
|
|
1539
1562
|
React.createElement("input", { placeholder: t("fieldsUser"), "aria-label": t("fieldsUser") + t("requiredMark"), "aria-required": "true", value: form.user, onChange: (e) => patch({ user: e.target.value }) }),
|
|
1540
1563
|
React.createElement("input", { type: "password", placeholder: pwLabel, "aria-label": pwLabel, value: form.password, onChange: (e) => patch({ password: e.target.value }) })),
|
|
1541
1564
|
// TLS 相关项(仅 sqlserver)。设计原则:默认留空 = 沿用适配器默认
|
|
1542
|
-
// (encrypt=
|
|
1565
|
+
// (encrypt='auto' 交适配器协商 + 校验证书 + Node 默认 TLS 下限),即「默认安全」;
|
|
1543
1566
|
// 只有连老实例/自签证书失败时才需要动下面这些开关。
|
|
1544
1567
|
// 常见误配:报 self signed certificate 是「证书族」,改 TLS 版本无效;
|
|
1545
1568
|
// 报 UNSUPPORTED_PROTOCOL 是「协议族」,开关闭证书校验无效——两族解法正交。
|
|
1569
|
+
// 交互:这些项默认藏在「高级选项」折叠区内(对齐 Navicat 的简洁界面),
|
|
1570
|
+
// 收起时只留入口按钮;折叠态不影响 form 值,收起也照样回填与提交。
|
|
1546
1571
|
form.kind === "sqlserver"
|
|
1547
1572
|
? React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
1548
1573
|
React.createElement("div", { className: "dbt-row" },
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1574
|
+
React.createElement("button", {
|
|
1575
|
+
className: "dbt-btn",
|
|
1576
|
+
type: "button",
|
|
1577
|
+
"aria-label": t("advancedTls"),
|
|
1578
|
+
"aria-expanded": advancedOpen,
|
|
1579
|
+
// 收起时容器不渲染 → 不下发 aria-controls,避免指向不存在的节点
|
|
1580
|
+
"aria-controls": advancedOpen ? TLS_ADVANCED_ID : undefined,
|
|
1581
|
+
onClick: () => setAdvancedOpen(!advancedOpen),
|
|
1582
|
+
...focusRingProps(),
|
|
1583
|
+
}, (advancedOpen ? "▾ " : "▸ ") + t("advancedTls"))),
|
|
1584
|
+
advancedOpen
|
|
1585
|
+
? React.createElement("div", { id: TLS_ADVANCED_ID, style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
1586
|
+
React.createElement("div", { className: "dbt-row" },
|
|
1587
|
+
// 加密模式:auto=交适配器协商(推荐,对齐 Navicat)/
|
|
1588
|
+
// true=强制加密(握手失败即报错)/ false=不主动要求加密(登录包仍加密)/
|
|
1589
|
+
// strict=TDS 8.0 严格加密(仅 SQL Server 2022+ 开启 Force Strict Encryption 的实例)
|
|
1590
|
+
React.createElement("label", { className: "dbt-row", style: { flex: "none" } }, t("fieldsEncrypt"),
|
|
1591
|
+
React.createElement("select", { "aria-label": t("fieldsEncrypt"), value: form.encrypt, onChange: (e) => patch({ encrypt: e.target.value }) },
|
|
1592
|
+
React.createElement("option", { value: "auto" }, t("encryptAuto")),
|
|
1593
|
+
React.createElement("option", { value: "true" }, t("encryptDefault")),
|
|
1594
|
+
React.createElement("option", { value: "false" }, t("encryptOff")),
|
|
1595
|
+
React.createElement("option", { value: "strict" }, t("encryptStrict")))),
|
|
1596
|
+
// 最低 TLS 版本:仅老实例(2005/2008/未打补丁的 2012·2014)报协议族错误时才需要下探
|
|
1597
|
+
React.createElement("label", { className: "dbt-row", style: { flex: "none" } }, t("fieldsTlsMinVersion"),
|
|
1598
|
+
React.createElement("select", { "aria-label": t("fieldsTlsMinVersion"), value: form.tlsMinVersion, onChange: (e) => patch({ tlsMinVersion: e.target.value }) },
|
|
1599
|
+
React.createElement("option", { value: "" }, t("tlsMinDefault")),
|
|
1600
|
+
["TLSv1", "TLSv1.1", "TLSv1.2"].map((v) => React.createElement("option", { key: v, value: v }, v))))),
|
|
1601
|
+
React.createElement("label", { className: "dbt-row", style: { flex: "none" } },
|
|
1602
|
+
React.createElement("input", { type: "checkbox", checked: !!form.trustServerCertificate, "aria-label": t("fieldsTrustCert"), onChange: (e) => patch({ trustServerCertificate: e.target.checked }) }),
|
|
1603
|
+
t("fieldsTrustCert")),
|
|
1604
|
+
React.createElement("div", { className: "dbt-row" },
|
|
1605
|
+
React.createElement("input", { placeholder: t("fieldsServerName"), "aria-label": t("fieldsServerName"), value: form.serverName, onChange: (e) => patch({ serverName: e.target.value }) }),
|
|
1606
|
+
React.createElement("input", { placeholder: t("fieldsCaCert"), "aria-label": t("fieldsCaCert"), value: form.caCert, onChange: (e) => patch({ caCert: e.target.value }) })),
|
|
1607
|
+
React.createElement("span", { style: { fontSize: 12, opacity: 0.75 } }, t("tlsHint")))
|
|
1608
|
+
: null)
|
|
1569
1609
|
: null,
|
|
1570
1610
|
),
|
|
1571
1611
|
// 分组 3:操作
|
|
@@ -107,11 +107,15 @@ function applyTdsVersion(options, raw, source) {
|
|
|
107
107
|
options.tdsVersion = v;
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
-
* 加密模式(URL ?encrypt= 与 fields.encrypt 同口径):true / false / strict
|
|
110
|
+
* 加密模式(URL ?encrypt= 与 fields.encrypt 同口径):true / false / strict / auto 四态。
|
|
111
111
|
*
|
|
112
112
|
* strict 是 tedious 对 TDS 8.0 的支持:TLS 在登录包之前就完成握手(ALPN=tds/8.0),仅 SQL Server
|
|
113
113
|
* 2022+ 开启 Force Strict Encryption 的实例可用;老实例必然拒绝,故非法值必须 fail-closed 报清
|
|
114
|
-
*
|
|
114
|
+
* 四态,不能把拼错的 'stric' 之类静默当成 true(加密强度是安全承诺,连错不报是安全事故)。
|
|
115
|
+
*
|
|
116
|
+
* auto 是 Navicat 式「只填地址账号密码就能连」:先按 encrypt=false 连(等同 Navicat 不开加密),
|
|
117
|
+
* 只有服务器在预登录回 ON/REQ(tedious 抛 EENCRYPT「Server requires encryption」)时才自动以
|
|
118
|
+
* encrypt=true + trustServerCertificate=true 重试一次,见 makePoolBox。显式三态绝不触发重试。
|
|
115
119
|
*/
|
|
116
120
|
function applyEncrypt(options, raw, source) {
|
|
117
121
|
if (raw === undefined || raw === null || String(raw).trim() === '')
|
|
@@ -133,7 +137,11 @@ function applyEncrypt(options, raw, source) {
|
|
|
133
137
|
options.encrypt = 'strict';
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
136
|
-
|
|
140
|
+
if (s === 'auto') {
|
|
141
|
+
options.encrypt = 'auto';
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`sqlserver 连接${source}的 encrypt 取值非法:${JSON.stringify(raw)}(只接受 true/false/strict/auto;strict 表示 TDS 8.0 强制加密,仅 SQL Server 2022 及以上并开启 Force Strict Encryption 的实例支持;auto 表示先按明文连接,服务器强制加密时自动以 encrypt=true 并信任服务器证书重试一次)`);
|
|
137
145
|
}
|
|
138
146
|
/**
|
|
139
147
|
* 证书校验用的服务器名(URL ?serverName= 与 fields.serverName 同口径)。
|
|
@@ -464,6 +472,7 @@ function poolConfig(conn) {
|
|
|
464
472
|
}
|
|
465
473
|
// conn.ssl 是通用字段(mysql/pg 适配器同样处理):语义上等价要求加密信道。
|
|
466
474
|
// 已显式选 strict(TDS 8.0)时不降级为 true——strict 是更强的加密承诺,降级会悄悄削弱安全要求。
|
|
475
|
+
// auto 也要让位:ssl=true 是用户显式要求加密,不能因为 auto 的首阶段是明文就退回明文。
|
|
467
476
|
if (conn.ssl && options.encrypt !== 'strict')
|
|
468
477
|
options.encrypt = true;
|
|
469
478
|
// 未显式给 caCert 时把系统信任库并入默认校验:Node 默认只信任内置 bundle,不读操作系统信任库,
|
|
@@ -806,6 +815,9 @@ function mssqlReason(e) {
|
|
|
806
815
|
case 'ECANCEL':
|
|
807
816
|
base = `请求已取消(${detail})`;
|
|
808
817
|
break;
|
|
818
|
+
// encrypt=auto 的合并错误:文案本身已含「该实例强制加密」与两条原始原因,原样透出即可。
|
|
819
|
+
// 不做任何族建议——重试已带 trustServerCertificate=true,而原始原因②常含 self signed
|
|
820
|
+
// certificate / TLS 字样,让证书族建议跟上来会把人引向「再关一次校验」,那是错误方向。
|
|
809
821
|
case 'EREQUEST': {
|
|
810
822
|
base = `SQL 执行错误${num !== undefined ? `(错误号 ${num}${sev !== undefined ? `、严重级 ${sev}` : ''}${line !== undefined ? `、第 ${line} 行` : ''})` : ''}:${knownHint === undefined ? detail : `${knownHint}。服务器原文:${detail}`}`;
|
|
811
823
|
break;
|
|
@@ -817,8 +829,10 @@ function mssqlReason(e) {
|
|
|
817
829
|
// 已知错误号已经给出了针对性诊断时,抑制 TLS 族建议:这类错误的服务器原文常含 certificate /
|
|
818
830
|
// encryption 字样(如 17819 的「TLS certificate is not configured to accept strict」),会被下面的
|
|
819
831
|
// 正则命中;但真因是服务端配置/协议问题,客户端装 CA、关校验一律无效,叠加只会给出互相矛盾的药方。
|
|
820
|
-
|
|
821
|
-
|
|
832
|
+
// EAUTOENCRYPT(encrypt=auto 两次都失败)同理:原始原因②常带 self signed certificate / TLS
|
|
833
|
+
// 字样,而重试已经带了 trustServerCertificate=true,再追加证书族建议等于让人重复做同一件事。
|
|
834
|
+
if (knownHint !== undefined || code === 'EAUTOENCRYPT') {
|
|
835
|
+
// 已由 EREQUEST_HINTS / EAUTOENCRYPT 给出单一出口,此处不再追加任何族建议
|
|
822
836
|
}
|
|
823
837
|
else if (tlsProtoish) {
|
|
824
838
|
// 顺序即优先级:服务端升级是正解(一次性、对所有客户端生效),客户端下探是权宜(仍受限于
|
|
@@ -920,17 +934,100 @@ function parseMajor(row) {
|
|
|
920
934
|
function rowsOf(res) {
|
|
921
935
|
return (res.recordset ?? res.recordsets?.[0] ?? []);
|
|
922
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* 建连失败是否「服务器强制加密」(encrypt=auto 的重试判据)。
|
|
939
|
+
*
|
|
940
|
+
* tedious 在 PRELOGIN 响应为 ON/REQ 而客户端 encrypt=false 时抛
|
|
941
|
+
* `EENCRYPT: Server requires encryption, set 'encrypt' config option to true.`。
|
|
942
|
+
* 同一个 EENCRYPT 还有反方向的一种成因(客户端要加密但服务器回了 OFF/NOT_SUP,文案是
|
|
943
|
+
* "Server did not negotiate encryption")——那种情形重试加密毫无意义,必须排除,否则会把
|
|
944
|
+
* 「服务器不支持加密」误判成「服务器强制加密」而白跑一次。
|
|
945
|
+
*/
|
|
946
|
+
function isServerRequiresEncryption(e) {
|
|
947
|
+
const err = (e ?? {});
|
|
948
|
+
const text = `${errText(e)} ${errText(err.originalError)} ${errText(err.cause)}`;
|
|
949
|
+
if (/did not negotiate encryption/i.test(text))
|
|
950
|
+
return false;
|
|
951
|
+
// code 存在时必须是 EENCRYPT;缺失时(被上层重新包装过)按服务器原文特征判定
|
|
952
|
+
const code = typeof err.code === 'string' ? err.code : '';
|
|
953
|
+
if (code !== '' && code !== 'EENCRYPT')
|
|
954
|
+
return false;
|
|
955
|
+
return /Server requires encryption/i.test(text);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* encrypt=auto 两次都失败时的合并错误。
|
|
959
|
+
*
|
|
960
|
+
* 必须保留两条原始原因:用户要判断的是「服务器确实强制加密」还是「加密路径本身有别的问题」,
|
|
961
|
+
* 只留最后一条会把明文侧的拒绝原因吞掉。code 标成未被 tedious 使用的 EAUTOENCRYPT,让
|
|
962
|
+
* mssqlReason 走专门分支直接输出本条完整文案,不做族建议——重试已经带了
|
|
963
|
+
* trustServerCertificate=true,再往证书方向引(原始原因里常有 "self signed certificate")
|
|
964
|
+
* 只会让人白排查一轮。
|
|
965
|
+
*/
|
|
966
|
+
function autoEncryptFailure(first, second) {
|
|
967
|
+
const err = new Error(`该实例强制加密:encrypt=auto 先按明文连接被服务器拒绝(服务器要求加密),已自动改用 encrypt=true 并信任服务器证书重试一次,仍然失败。` +
|
|
968
|
+
`原始原因①(明文连接):${errText(first)};原始原因②(加密连接):${errText(second)}`);
|
|
969
|
+
Object.assign(err, { code: 'EAUTOENCRYPT' });
|
|
970
|
+
return err;
|
|
971
|
+
}
|
|
923
972
|
function makePoolBox(Driver, cfg) {
|
|
924
|
-
|
|
973
|
+
// encrypt=auto 只在本层展开,绝不把字面量 'auto' 交给 tedious(它对未知取值没有定义行为)。
|
|
974
|
+
// 池配置是浅拷贝共享的(跨库池 { ...cfg, database } 的 options 仍指向同一对象),故这里必须
|
|
975
|
+
// 先拷一层 options 再改 encrypt,否则会把「第一阶段用明文」写回共享配置、污染后续池。
|
|
976
|
+
const baseOptions = (cfg.options ?? {});
|
|
977
|
+
const autoEncrypt = baseOptions['encrypt'] === 'auto';
|
|
978
|
+
const initialCfg = autoEncrypt
|
|
979
|
+
? { ...cfg, options: { ...baseOptions, encrypt: false } }
|
|
980
|
+
: cfg;
|
|
981
|
+
// 第二阶段配置(仅 auto 存在):encrypt=true + 信任服务器证书,等价 Navicat 勾「加密+信任证书」
|
|
982
|
+
const retryCfg = autoEncrypt
|
|
983
|
+
? { ...cfg, options: { ...baseOptions, encrypt: true, trustServerCertificate: true } }
|
|
984
|
+
: undefined;
|
|
925
985
|
// 无条件兜底:mssql 的 ConnectionPool 在 acquire 失败(空闲连接被服务器断开等)时会
|
|
926
986
|
// emit('error', err),EventEmitter 无监听器时直接抛 → 崩掉宿主进程。这里只留痕不中断请求。
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
987
|
+
const attachErrorTrap = (p) => {
|
|
988
|
+
p.on('error', (e) => {
|
|
989
|
+
console.error('[sqlserver] 连接池错误(不影响后续请求,连接将按需重建):', e instanceof Error ? e.message : e);
|
|
990
|
+
});
|
|
991
|
+
};
|
|
992
|
+
// auto 重试会换池,故 pool 必须以 getter 暴露当前有效池:调用方(query/close)拿到的永远是
|
|
993
|
+
// 正在用的那个,不需要知道发生过重试。
|
|
994
|
+
let pool = new Driver.ConnectionPool(initialCfg);
|
|
995
|
+
attachErrorTrap(pool);
|
|
996
|
+
/**
|
|
997
|
+
* 单次建连;auto 模式下服务器强制加密时换一次池并重试,绝不循环。
|
|
998
|
+
* 顺序即语义:先明文(等同 Navicat 不开加密)→ 仅「服务器明确要求加密」才升级到加密+信任证书。
|
|
999
|
+
*/
|
|
1000
|
+
const connectOnce = async () => {
|
|
1001
|
+
try {
|
|
1002
|
+
await pool.connect();
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
catch (e) {
|
|
1006
|
+
if (retryCfg === undefined || !isServerRequiresEncryption(e))
|
|
1007
|
+
throw e;
|
|
1008
|
+
const first = e;
|
|
1009
|
+
// 明文池已废:不关会留下半死连接(其 socket 监听仍在),失败也不得掩盖真正的成因
|
|
1010
|
+
try {
|
|
1011
|
+
await pool.close();
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
/* 关闭失败不影响重试判据 */
|
|
1015
|
+
}
|
|
1016
|
+
const next = new Driver.ConnectionPool(retryCfg);
|
|
1017
|
+
attachErrorTrap(next);
|
|
1018
|
+
pool = next;
|
|
1019
|
+
try {
|
|
1020
|
+
await pool.connect();
|
|
1021
|
+
}
|
|
1022
|
+
catch (second) {
|
|
1023
|
+
throw autoEncryptFailure(first, second);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
930
1027
|
let connecting;
|
|
931
1028
|
const ready = () => {
|
|
932
1029
|
if (connecting === undefined) {
|
|
933
|
-
connecting =
|
|
1030
|
+
connecting = connectOnce().then(() => undefined, (e) => {
|
|
934
1031
|
connecting = undefined; // 失败不缓存:允许下次重试
|
|
935
1032
|
throw e;
|
|
936
1033
|
});
|
|
@@ -955,7 +1052,13 @@ function makePoolBox(Driver, cfg) {
|
|
|
955
1052
|
}
|
|
956
1053
|
return majorPromise;
|
|
957
1054
|
};
|
|
958
|
-
return {
|
|
1055
|
+
return {
|
|
1056
|
+
get pool() {
|
|
1057
|
+
return pool;
|
|
1058
|
+
},
|
|
1059
|
+
ready,
|
|
1060
|
+
major,
|
|
1061
|
+
};
|
|
959
1062
|
}
|
|
960
1063
|
/* ============================ 适配器 ============================ */
|
|
961
1064
|
const LIST_DATABASES_SQL = `SELECT name FROM sys.databases WHERE database_id > 4 ORDER BY name`;
|
|
@@ -1264,11 +1367,22 @@ export const factory = (conn) => createSqlserverAdapter(conn);
|
|
|
1264
1367
|
/**
|
|
1265
1368
|
* 数据传输端复用的独立原始池(绕过展示化转换,保真搬运)。
|
|
1266
1369
|
* 库名与主连接一致(跨库由调用方在 conn 上覆盖 database)。
|
|
1370
|
+
*
|
|
1371
|
+
* pool 是 getter:encrypt=auto 的重试会换掉底层池,而传输端是在 ready() 之前就抓住 pool 的
|
|
1372
|
+
* (createSqlserverTransferReaderFromConn 先取 raw.pool 再 await raw.ready()),
|
|
1373
|
+
* 固定字段会把重试后的新池挡在外面、继续用那个已关闭的明文池。
|
|
1267
1374
|
*/
|
|
1268
1375
|
export async function openSqlserverRaw(conn, driver) {
|
|
1269
1376
|
const Driver = driver ?? vendor();
|
|
1270
1377
|
const cfg = poolConfig(conn);
|
|
1271
1378
|
const box = makePoolBox(Driver, cfg);
|
|
1272
1379
|
const database = typeof cfg.database === 'string' ? cfg.database : '';
|
|
1273
|
-
return {
|
|
1380
|
+
return {
|
|
1381
|
+
get pool() {
|
|
1382
|
+
return box.pool;
|
|
1383
|
+
},
|
|
1384
|
+
ready: box.ready,
|
|
1385
|
+
database,
|
|
1386
|
+
driver: Driver,
|
|
1387
|
+
};
|
|
1274
1388
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/skills/db-admin/SKILL.md
CHANGED
|
@@ -183,8 +183,9 @@ DatabaseManager({ action: "run_script", conn_id: "mongo1", code: '{"find":"users
|
|
|
183
183
|
- 覆盖 SQL Server 2005(9.0)→ 2025(17.0)与 Azure SQL Database;不支持 SQL Server 2000 及更早。方言与元数据只使用 2005+ 通用写法(元数据走 `sys.*` catalog views)。
|
|
184
184
|
- **TDS 版本自动协商,无需用户选择**:客户端固定声明 `7_4`,服务器按自身能力自动降级(2005→`7_2`、2008→`7_3_A`、2008 R2→`7_3_B`、2012+→`7_4`)。这是 [MS-TDS](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac) 的 MUST 级规定(客户端声明高于服务器时,服务器**必须**用自己最高版本)。`tdsVersion` 仅作排障覆盖项;**不要**因「老版本」就下调它——那会把「连不上」变成「连上了但行为异常」。
|
|
185
185
|
- **认证**:仅 SQL 认证(user/password);**不支持 Windows 集成认证,也不支持 Azure AD 认证**——需要这两类认证时本工具不可用。注意:本工具**未实现** `IsIntegratedSecurityOnly` 探测,遇到「实例仅允许 Windows 认证」时只会表现为登录失败,不要声称能自动识别该状态。
|
|
186
|
-
- **连接形态**:`sqlserver://user:password@host:port/db`,支持 query 参数 `encrypt` / `trustServerCertificate` / `tdsVersion` / `tlsMinVersion` / `serverName` / `caCert` / `instanceName` / `useUTC`(参数名大小写不敏感);`instanceName` 与端口互斥(命名实例改由 SQL Server Browser 解析)。**不接受** ADO.NET 键值连接串(`Server=...;Database
|
|
187
|
-
- **默认与覆盖**:`encrypt=
|
|
186
|
+
- **连接形态**:`sqlserver://user:password@host:port/db`,支持 query 参数 `encrypt` / `trustServerCertificate` / `tdsVersion` / `tlsMinVersion` / `serverName` / `caCert` / `instanceName` / `useUTC`(参数名大小写不敏感);`instanceName` 与端口互斥(命名实例改由 SQL Server Browser 解析)。**不接受** ADO.NET 键值连接串(`Server=...;Database=...`)。**常规连接只需主机/端口/账号/密码**;除 `useUTC` / `instanceName` 外,前述加密与证书参数在界面里都收在「高级选项」折叠区(默认收起),属排障用,不要默认要求用户去填。
|
|
187
|
+
- **默认与覆盖**:`encrypt=auto`(新建连接默认)+ `trustServerCertificate=false` + `tdsVersion=7_4` + `useUTC=true`。URL query 与分字段表单**同口径**,两处都能覆盖上述各项;非法取值直接报错,不静默回退。
|
|
188
|
+
- **`encrypt=auto` 的语义(默认策略,勿按「必须手动开加密」描述)**:先不带加密建连;若服务器**强制加密**(`EENCRYPT` / 「服务器要求加密连接」,如 `Force Encryption=Yes` 或 Azure SQL),自动以 `encrypt=true` + `trustServerCertificate=true` **重试一次**。边界严格:**仅重试一次**;**仅针对「服务器强制加密」这一种原因**(证书类、协议版本类、登录失败等一律不重试);**显式指定 `encrypt`(`true`/`false`/`strict`)时不做任何重试**。**`auto` 不等于「自动加密」**:在未强制加密的服务器上它等价于 `encrypt=false`,即明文传输——见下条风险说明。
|
|
188
189
|
|
|
189
190
|
### TLS 握手失败:两类根因,解法互不通用
|
|
190
191
|
|
|
@@ -195,7 +196,9 @@ DatabaseManager({ action: "run_script", conn_id: "mongo1", code: '{"find":"users
|
|
|
195
196
|
|
|
196
197
|
**不要拿证书类参数治协议版本类**:`trustServerCertificate=true` / `encrypt=false` 只影响证书校验,对 `UNSUPPORTED_PROTOCOL` 没有任何作用,试错只会白跑一轮。两类判据互斥,报错消息里已按族给出中文根因;看到 `TCP 已连通但 TLS ...` 说明网络与端口都没问题,不要再查主机/防火墙。
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
**默认 `auto` 不会替你兜住证书类错误**:`auto` 的自动重试只认「服务器强制加密」这一种原因,证书校验失败仍会原样报错。也就是说自签证书实例在默认值下照样可能连不上(取决于服务器是否强制加密),此时按上表证书族路径处理——把 CA 导入系统受信任根通常就能连(插件已并入系统信任库,无需填 `caCert`)。
|
|
200
|
+
|
|
201
|
+
**关键事实:`encrypt=false` 与默认 `encrypt=auto` 的真实行为(已抓包实测)**。服务器**未**强制加密时,`encrypt=false` 会**完全跳过 TLS 握手并明文发送 LOGIN7(含账号密码)**——实测客户端下一个包就是 TDS `0x10`,没有 TLS 握手。这是明文传输风险,不是「只是不加密数据包」。**默认的 `auto` 第一步正是这条路径,因此在未强制加密的服务器上 `auto` 与 `encrypt=false` 行为完全相同**——`auto` 只在服务器拒绝明文时才升级到 TLS,它不是「自动帮你加密」。服务器**强制**加密时(`Force Encryption=Yes` / Azure SQL),`encrypt=false` 会被拒(提示服务器要求加密)。官方那句「登录包始终加密」仅适用于「服务器强制加密或客户端 `encrypt=true`」的场景,**不可推广成 `encrypt=false` 也总有 TLS 握手**。故:可信内网可用默认值;跨公网或不可信网段连接时**显式设 `encrypt=true`**(显式值不触发任何自动重试,行为可预期),证书问题再用 `caCert` / `serverName` 解决。另注:连 2005 或未打补丁的 2008 时服务端可能只有 TLS 1.0,需下探 `?tlsMinVersion=TLSv1`。
|
|
199
202
|
|
|
200
203
|
### 版本与 TLS 1.2 支持
|
|
201
204
|
|