neon 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
|
@@ -1,64 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Connection backslash commands.
|
|
3
|
-
*
|
|
4
|
-
* TypeScript port of the corresponding `exec_command_*` functions in
|
|
5
|
-
* upstream PostgreSQL's `src/bin/psql/command.c`:
|
|
6
|
-
*
|
|
7
|
-
* - `\c` / `\connect` → exec_command_connect / do_connect
|
|
8
|
-
* - `\conninfo` → exec_command_conninfo
|
|
9
|
-
* - `\encoding` → exec_command_encoding
|
|
10
|
-
* - `\password` → exec_command_password
|
|
11
|
-
*
|
|
12
|
-
* What this module owns:
|
|
13
|
-
*
|
|
14
|
-
* - Parsing the three `\c` argument shapes — positional (`db user host
|
|
15
|
-
* port`, with `-` meaning "keep current"), URI (`postgresql://…`), and
|
|
16
|
-
* conninfo key=value pairs (`dbname=foo host=bar`).
|
|
17
|
-
* - Building a fresh {@link ConnectOptions} by merging the override with
|
|
18
|
-
* the previous connection's opts and dispatching `PgConnection.connect`
|
|
19
|
-
* via an injectable seam so tests can stub the wire layer.
|
|
20
|
-
* - `\conninfo` — renders the PostgreSQL 18 "Connection Information"
|
|
21
|
-
* two-column table (Parameter / Value), mirroring upstream
|
|
22
|
-
* `exec_command_conninfo` in `src/bin/psql/command.c`. PG 18 rewrote
|
|
23
|
-
* `\conninfo` from the old one-line "You are connected to …" message
|
|
24
|
-
* into this table. Every value comes from the live connection's
|
|
25
|
-
* accessors (`getConnectionInfo()`, `getTlsInfo()`,
|
|
26
|
-
* `parameterStatus()`) — we issue NO SQL (the old form's
|
|
27
|
-
* `inet_server_addr()` returned a bogus internal IP behind a proxy like
|
|
28
|
-
* Neon). The ResultSet is handed to the active `\pset format` printer
|
|
29
|
-
* so `\conninfo` honours `-A`, `-H`, etc. just like a query result.
|
|
30
|
-
* - `\encoding` — like cmd_format.ts but additionally issues
|
|
31
|
-
* `SET client_encoding TO …` on the live connection so the backend
|
|
32
|
-
* ParameterStatus stays in sync with `settings.popt.topt.encoding`.
|
|
33
|
-
* - `\password` — prompts for a password (twice), encodes it as a
|
|
34
|
-
* SCRAM-SHA-256 verifier (RFC 5803 / PG's `ALTER USER … PASSWORD`
|
|
35
|
-
* format), and issues the ALTER USER on the live connection. The
|
|
36
|
-
* encoder lives in {@link scramSha256Verifier}; tests call it directly
|
|
37
|
-
* with a fixed salt so we don't have to mock crypto.randomBytes.
|
|
38
|
-
* Refuses to prompt when `settings.notty` is true (matches the spirit
|
|
39
|
-
* of upstream — we don't yet wire `/dev/tty`, so notty would otherwise
|
|
40
|
-
* fight the mainloop's stdin reader).
|
|
41
|
-
*
|
|
42
|
-
* What this module does NOT own:
|
|
43
|
-
*
|
|
44
|
-
* - Cataloguing the current database/user. These are populated by the
|
|
45
|
-
* startup WP into psql vars DBNAME/USER (and kept in sync by `\c`), so
|
|
46
|
-
* `\conninfo` reads them out of `settings.vars`, falling back to the
|
|
47
|
-
* connect-opts surfaced on the live connection. Host/port/hostaddr and
|
|
48
|
-
* the SSL facts come straight from the connection accessors.
|
|
49
|
-
*
|
|
50
|
-
* Password retention: `PgConnection` exposes the password captured at
|
|
51
|
-
* connect time via a read-only `password` getter (mirroring libpq's
|
|
52
|
-
* retention on the `PGconn`). `\c` reads it via a structural cast so we
|
|
53
|
-
* don't have to widen the frozen {@link Connection} interface, and feeds
|
|
54
|
-
* it to {@link mergeConnectOpts} so a reconnect to a different database
|
|
55
|
-
* works without re-prompting. A new password supplied in the conninfo /
|
|
56
|
-
* URI override always wins.
|
|
57
|
-
*/
|
|
58
|
-
import { Buffer } from "node:buffer";
|
|
59
|
-
import { createHash, createHmac, randomBytes as nodeRandomBytes, pbkdf2Sync, } from "node:crypto";
|
|
1
|
+
import { writeErr, writeOut } from "./shared.js";
|
|
60
2
|
import { syncConnectionVars } from "../core/syncVars.js";
|
|
61
|
-
import { readLine
|
|
3
|
+
import { readLine } from "../io/input.js";
|
|
62
4
|
import { alignedPrinter } from "../print/aligned.js";
|
|
63
5
|
import { asciidocPrinter } from "../print/asciidoc.js";
|
|
64
6
|
import { csvPrinter } from "../print/csv.js";
|
|
@@ -68,753 +10,604 @@ import { latexLongtablePrinter, latexPrinter } from "../print/latex.js";
|
|
|
68
10
|
import { troffMsPrinter } from "../print/troff.js";
|
|
69
11
|
import { unalignedPrinter } from "../print/unaligned.js";
|
|
70
12
|
import { PgConnection } from "../wire/connection.js";
|
|
71
|
-
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
13
|
+
import { createHash, createHmac, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
14
|
+
import { Buffer } from "node:buffer";
|
|
15
|
+
//#region src/psql/command/cmd_connect.ts
|
|
16
|
+
/**
|
|
17
|
+
* Connection backslash commands.
|
|
18
|
+
*
|
|
19
|
+
* TypeScript port of the corresponding `exec_command_*` functions in
|
|
20
|
+
* upstream PostgreSQL's `src/bin/psql/command.c`:
|
|
21
|
+
*
|
|
22
|
+
* - `\c` / `\connect` → exec_command_connect / do_connect
|
|
23
|
+
* - `\conninfo` → exec_command_conninfo
|
|
24
|
+
* - `\encoding` → exec_command_encoding
|
|
25
|
+
* - `\password` → exec_command_password
|
|
26
|
+
*
|
|
27
|
+
* What this module owns:
|
|
28
|
+
*
|
|
29
|
+
* - Parsing the three `\c` argument shapes — positional (`db user host
|
|
30
|
+
* port`, with `-` meaning "keep current"), URI (`postgresql://…`), and
|
|
31
|
+
* conninfo key=value pairs (`dbname=foo host=bar`).
|
|
32
|
+
* - Building a fresh {@link ConnectOptions} by merging the override with
|
|
33
|
+
* the previous connection's opts and dispatching `PgConnection.connect`
|
|
34
|
+
* via an injectable seam so tests can stub the wire layer.
|
|
35
|
+
* - `\conninfo` — renders the PostgreSQL 18 "Connection Information"
|
|
36
|
+
* two-column table (Parameter / Value), mirroring upstream
|
|
37
|
+
* `exec_command_conninfo` in `src/bin/psql/command.c`. PG 18 rewrote
|
|
38
|
+
* `\conninfo` from the old one-line "You are connected to …" message
|
|
39
|
+
* into this table. Every value comes from the live connection's
|
|
40
|
+
* accessors (`getConnectionInfo()`, `getTlsInfo()`,
|
|
41
|
+
* `parameterStatus()`) — we issue NO SQL (the old form's
|
|
42
|
+
* `inet_server_addr()` returned a bogus internal IP behind a proxy like
|
|
43
|
+
* Neon). The ResultSet is handed to the active `\pset format` printer
|
|
44
|
+
* so `\conninfo` honours `-A`, `-H`, etc. just like a query result.
|
|
45
|
+
* - `\encoding` — like cmd_format.ts but additionally issues
|
|
46
|
+
* `SET client_encoding TO …` on the live connection so the backend
|
|
47
|
+
* ParameterStatus stays in sync with `settings.popt.topt.encoding`.
|
|
48
|
+
* - `\password` — prompts for a password (twice), encodes it as a
|
|
49
|
+
* SCRAM-SHA-256 verifier (RFC 5803 / PG's `ALTER USER … PASSWORD`
|
|
50
|
+
* format), and issues the ALTER USER on the live connection. The
|
|
51
|
+
* encoder lives in {@link scramSha256Verifier}; tests call it directly
|
|
52
|
+
* with a fixed salt so we don't have to mock crypto.randomBytes.
|
|
53
|
+
* Refuses to prompt when `settings.notty` is true (matches the spirit
|
|
54
|
+
* of upstream — we don't yet wire `/dev/tty`, so notty would otherwise
|
|
55
|
+
* fight the mainloop's stdin reader).
|
|
56
|
+
*
|
|
57
|
+
* What this module does NOT own:
|
|
58
|
+
*
|
|
59
|
+
* - Cataloguing the current database/user. These are populated by the
|
|
60
|
+
* startup WP into psql vars DBNAME/USER (and kept in sync by `\c`), so
|
|
61
|
+
* `\conninfo` reads them out of `settings.vars`, falling back to the
|
|
62
|
+
* connect-opts surfaced on the live connection. Host/port/hostaddr and
|
|
63
|
+
* the SSL facts come straight from the connection accessors.
|
|
64
|
+
*
|
|
65
|
+
* Password retention: `PgConnection` exposes the password captured at
|
|
66
|
+
* connect time via a read-only `password` getter (mirroring libpq's
|
|
67
|
+
* retention on the `PGconn`). `\c` reads it via a structural cast so we
|
|
68
|
+
* don't have to widen the frozen {@link Connection} interface, and feeds
|
|
69
|
+
* it to {@link mergeConnectOpts} so a reconnect to a different database
|
|
70
|
+
* works without re-prompting. A new password supplied in the conninfo /
|
|
71
|
+
* URI override always wins.
|
|
72
|
+
*/
|
|
73
|
+
let currentDeps = {
|
|
74
|
+
connect: (opts) => PgConnection.connect(opts),
|
|
75
|
+
readLine: defaultReadLine,
|
|
76
|
+
randomBytes
|
|
76
77
|
};
|
|
77
|
-
let currentDeps = defaultDeps;
|
|
78
78
|
/** Override the module's runtime deps. Returns a restore function. */
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
const setCmdConnectDeps = (overrides) => {
|
|
80
|
+
const prev = currentDeps;
|
|
81
|
+
currentDeps = {
|
|
82
|
+
...prev,
|
|
83
|
+
...overrides
|
|
84
|
+
};
|
|
85
|
+
return () => {
|
|
86
|
+
currentDeps = prev;
|
|
87
|
+
};
|
|
85
88
|
};
|
|
86
89
|
const readConnectionPassword = (conn) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
90
|
+
if (!conn) return null;
|
|
91
|
+
const raw = conn.password;
|
|
92
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
91
93
|
};
|
|
92
94
|
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
* Read the live connection's EFFECTIVE {@link ConnectOptions} (the full set
|
|
96
|
+
* it was dialled with — sslmode, sslrootcert/cert/key/crl, sslnegotiation,
|
|
97
|
+
* channelBinding, requireAuth, hostaddr, …). The {@link Connection} interface
|
|
98
|
+
* deliberately hides these, but {@link PgConnection} keeps them on a (TS-)
|
|
99
|
+
* private `opts` field; we read it structurally, the same pattern as
|
|
100
|
+
* {@link readConnectionPassword}. Used to SEED a `\c` reconnect so it doesn't
|
|
101
|
+
* silently drop TLS/cert options or downgrade sslmode.
|
|
102
|
+
* Returns `null` for a mock/absent connection.
|
|
103
|
+
*/
|
|
102
104
|
const readConnectionOptions = (conn) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return raw !== undefined && raw !== null && typeof raw === "object"
|
|
107
|
-
? raw
|
|
108
|
-
: null;
|
|
105
|
+
if (!conn) return null;
|
|
106
|
+
const raw = conn.opts;
|
|
107
|
+
return raw !== void 0 && raw !== null && typeof raw === "object" ? raw : null;
|
|
109
108
|
};
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// \c / \connect
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
109
|
const KEEP = "-";
|
|
114
110
|
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const tokens = trimmed.split(/\s+/);
|
|
146
|
-
const out = {};
|
|
147
|
-
if (tokens.length >= 1 && tokens[0] !== KEEP)
|
|
148
|
-
out.database = tokens[0];
|
|
149
|
-
if (tokens.length >= 2 && tokens[1] !== KEEP)
|
|
150
|
-
out.user = tokens[1];
|
|
151
|
-
if (tokens.length >= 3 && tokens[2] !== KEEP)
|
|
152
|
-
out.host = tokens[2];
|
|
153
|
-
if (tokens.length >= 4 && tokens[3] !== KEEP) {
|
|
154
|
-
const port = parseInt(tokens[3], 10);
|
|
155
|
-
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
|
|
156
|
-
return { error: `invalid port number "${tokens[3]}"` };
|
|
157
|
-
}
|
|
158
|
-
out.port = port;
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
111
|
+
* Parse the argument tail of `\c` into a partial override of
|
|
112
|
+
* {@link ConnectOptions}. Three forms:
|
|
113
|
+
*
|
|
114
|
+
* 1. **URI**: starts with `postgresql://` or `postgres://`.
|
|
115
|
+
* 2. **conninfo**: contains `=` in the first token — treated as a space
|
|
116
|
+
* separated `key=value` sequence (libpq's syntax; we don't implement
|
|
117
|
+
* quoted values because psql itself only forwards what the user typed
|
|
118
|
+
* to libpq's `PQconninfoParse`).
|
|
119
|
+
* 3. **positional**: `[db [user [host [port]]]]`. Each can be `-` to keep
|
|
120
|
+
* the current value.
|
|
121
|
+
*
|
|
122
|
+
* Returns an object with only the keys the user supplied. The caller is
|
|
123
|
+
* responsible for merging with the previous opts.
|
|
124
|
+
*/
|
|
125
|
+
const parseConnectArgs = (rawArgs) => {
|
|
126
|
+
const trimmed = rawArgs.trim();
|
|
127
|
+
if (trimmed.length === 0) return {};
|
|
128
|
+
if (trimmed.startsWith("postgresql://") || trimmed.startsWith("postgres://")) return parseUri(trimmed);
|
|
129
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed) || /\s[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) return parseConninfo(trimmed);
|
|
130
|
+
const tokens = trimmed.split(/\s+/);
|
|
131
|
+
const out = {};
|
|
132
|
+
if (tokens.length >= 1 && tokens[0] !== KEEP) out.database = tokens[0];
|
|
133
|
+
if (tokens.length >= 2 && tokens[1] !== KEEP) out.user = tokens[1];
|
|
134
|
+
if (tokens.length >= 3 && tokens[2] !== KEEP) out.host = tokens[2];
|
|
135
|
+
if (tokens.length >= 4 && tokens[3] !== KEEP) {
|
|
136
|
+
const port = parseInt(tokens[3], 10);
|
|
137
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) return { error: `invalid port number "${tokens[3]}"` };
|
|
138
|
+
out.port = port;
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
161
141
|
};
|
|
162
142
|
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
* Apply a single libpq-style connection keyword to `out`, validating values
|
|
144
|
+
* where psql does. `key` must already be lowercased. Returns `{ error }` on a
|
|
145
|
+
* bad value, or `null` on success. Shared by the `key=value` conninfo parser
|
|
146
|
+
* and the `\c <uri>` query-string parser so both honour the same keywords.
|
|
147
|
+
*/
|
|
168
148
|
const applyConnInfoKey = (out, key, value) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
case "client_encoding":
|
|
227
|
-
out.clientEncoding = value;
|
|
228
|
-
break;
|
|
229
|
-
case "connect_timeout": {
|
|
230
|
-
const n = parseInt(value, 10);
|
|
231
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
232
|
-
return { error: `invalid connect_timeout "${value}"` };
|
|
233
|
-
}
|
|
234
|
-
out.connectTimeoutMs = n * 1000;
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
case "options":
|
|
238
|
-
out.options = value;
|
|
239
|
-
break;
|
|
240
|
-
default:
|
|
241
|
-
// Unknown keys are silently ignored — matches libpq's permissiveness
|
|
242
|
-
// for forward-compat with future PG releases. We could warn here,
|
|
243
|
-
// but psql itself doesn't.
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
return null;
|
|
149
|
+
switch (key) {
|
|
150
|
+
case "host":
|
|
151
|
+
out.host = value;
|
|
152
|
+
break;
|
|
153
|
+
case "hostaddr":
|
|
154
|
+
out.hostaddr = value;
|
|
155
|
+
break;
|
|
156
|
+
case "port": {
|
|
157
|
+
const port = parseInt(value, 10);
|
|
158
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) return { error: `invalid port "${value}"` };
|
|
159
|
+
out.port = port;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "user":
|
|
163
|
+
out.user = value;
|
|
164
|
+
break;
|
|
165
|
+
case "password":
|
|
166
|
+
out.password = value;
|
|
167
|
+
break;
|
|
168
|
+
case "dbname":
|
|
169
|
+
case "database":
|
|
170
|
+
out.database = value;
|
|
171
|
+
break;
|
|
172
|
+
case "application_name":
|
|
173
|
+
out.applicationName = value;
|
|
174
|
+
break;
|
|
175
|
+
case "sslmode":
|
|
176
|
+
if (![
|
|
177
|
+
"disable",
|
|
178
|
+
"allow",
|
|
179
|
+
"prefer",
|
|
180
|
+
"require",
|
|
181
|
+
"verify-ca",
|
|
182
|
+
"verify-full"
|
|
183
|
+
].includes(value)) return { error: `invalid sslmode "${value}"` };
|
|
184
|
+
out.ssl = value;
|
|
185
|
+
break;
|
|
186
|
+
case "channel_binding":
|
|
187
|
+
if (![
|
|
188
|
+
"disable",
|
|
189
|
+
"prefer",
|
|
190
|
+
"require"
|
|
191
|
+
].includes(value)) return { error: `invalid channel_binding "${value}"` };
|
|
192
|
+
out.channelBinding = value;
|
|
193
|
+
break;
|
|
194
|
+
case "client_encoding":
|
|
195
|
+
out.clientEncoding = value;
|
|
196
|
+
break;
|
|
197
|
+
case "connect_timeout": {
|
|
198
|
+
const n = parseInt(value, 10);
|
|
199
|
+
if (!Number.isFinite(n) || n < 0) return { error: `invalid connect_timeout "${value}"` };
|
|
200
|
+
out.connectTimeoutMs = n * 1e3;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "options": out.options = value;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
247
206
|
};
|
|
248
207
|
const parseUri = (raw) => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
out.password = decodeURIComponent(url.password);
|
|
276
|
-
}
|
|
277
|
-
const pathname = url.pathname.replace(/^\//, "");
|
|
278
|
-
if (pathname.length > 0)
|
|
279
|
-
out.database = decodeURIComponent(pathname);
|
|
280
|
-
// Map the query string (?sslmode=require&connect_timeout=10&…) onto the
|
|
281
|
-
// same connection keywords libpq accepts in a URI.
|
|
282
|
-
for (const [rawKey, rawVal] of url.searchParams) {
|
|
283
|
-
const res = applyConnInfoKey(out, rawKey.toLowerCase(), rawVal);
|
|
284
|
-
if (res !== null)
|
|
285
|
-
return res;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch (err) {
|
|
289
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
290
|
-
return { error: `invalid URI: ${msg}` };
|
|
291
|
-
}
|
|
292
|
-
return out;
|
|
208
|
+
let url;
|
|
209
|
+
try {
|
|
210
|
+
url = new URL(raw);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return { error: `invalid URI: ${err instanceof Error ? err.message : String(err)}` };
|
|
213
|
+
}
|
|
214
|
+
const out = {};
|
|
215
|
+
try {
|
|
216
|
+
if (url.hostname.length > 0) out.host = decodeURIComponent(url.hostname);
|
|
217
|
+
if (url.port.length > 0) {
|
|
218
|
+
const port = parseInt(url.port, 10);
|
|
219
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) return { error: `invalid port in URI` };
|
|
220
|
+
out.port = port;
|
|
221
|
+
}
|
|
222
|
+
if (url.username.length > 0) out.user = decodeURIComponent(url.username);
|
|
223
|
+
if (url.password.length > 0) out.password = decodeURIComponent(url.password);
|
|
224
|
+
const pathname = url.pathname.replace(/^\//, "");
|
|
225
|
+
if (pathname.length > 0) out.database = decodeURIComponent(pathname);
|
|
226
|
+
for (const [rawKey, rawVal] of url.searchParams) {
|
|
227
|
+
const res = applyConnInfoKey(out, rawKey.toLowerCase(), rawVal);
|
|
228
|
+
if (res !== null) return res;
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
return { error: `invalid URI: ${err instanceof Error ? err.message : String(err)}` };
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
293
234
|
};
|
|
294
235
|
const parseConninfo = (raw) => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const key = pair.slice(0, eq).toLowerCase();
|
|
338
|
-
const value = pair.slice(eq + 1);
|
|
339
|
-
const res = applyConnInfoKey(out, key, value);
|
|
340
|
-
if (res !== null)
|
|
341
|
-
return res;
|
|
342
|
-
}
|
|
343
|
-
return out;
|
|
236
|
+
const out = {};
|
|
237
|
+
const pairs = [];
|
|
238
|
+
let current = "";
|
|
239
|
+
let inQuote = false;
|
|
240
|
+
for (let i = 0; i < raw.length; i++) {
|
|
241
|
+
const c = raw[i];
|
|
242
|
+
if (inQuote) {
|
|
243
|
+
if (c === "\\" && i + 1 < raw.length) {
|
|
244
|
+
current += raw[i + 1];
|
|
245
|
+
i++;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (c === "'") {
|
|
249
|
+
inQuote = false;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
current += c;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (c === "'") {
|
|
256
|
+
inQuote = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (/\s/.test(c)) {
|
|
260
|
+
if (current.length > 0) {
|
|
261
|
+
pairs.push(current);
|
|
262
|
+
current = "";
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
current += c;
|
|
267
|
+
}
|
|
268
|
+
if (current.length > 0) pairs.push(current);
|
|
269
|
+
for (const pair of pairs) {
|
|
270
|
+
const eq = pair.indexOf("=");
|
|
271
|
+
if (eq < 0) return { error: `missing "=" after "${pair}" in connection info` };
|
|
272
|
+
const key = pair.slice(0, eq).toLowerCase();
|
|
273
|
+
const value = pair.slice(eq + 1);
|
|
274
|
+
const res = applyConnInfoKey(out, key, value);
|
|
275
|
+
if (res !== null) return res;
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
344
278
|
};
|
|
345
279
|
/**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const port = merged.port ?? (portStr !== undefined ? parseInt(portStr, 10) : 5432);
|
|
396
|
-
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
|
|
397
|
-
return { error: `invalid port number` };
|
|
398
|
-
}
|
|
399
|
-
const user = merged.user ??
|
|
400
|
-
vars.get("USER") ??
|
|
401
|
-
process.env.USER ??
|
|
402
|
-
process.env.USERNAME ??
|
|
403
|
-
"";
|
|
404
|
-
if (user.length === 0) {
|
|
405
|
-
return { error: "no user name specified" };
|
|
406
|
-
}
|
|
407
|
-
const database = merged.database ?? vars.get("DBNAME") ?? user;
|
|
408
|
-
const password = merged.password ?? vars.get("PASSWORD");
|
|
409
|
-
const ssl = merged.ssl ?? "prefer";
|
|
410
|
-
return {
|
|
411
|
-
...merged,
|
|
412
|
-
host,
|
|
413
|
-
port,
|
|
414
|
-
user,
|
|
415
|
-
password: password ?? undefined,
|
|
416
|
-
database,
|
|
417
|
-
ssl,
|
|
418
|
-
applicationName: merged.applicationName ?? vars.get("APPLICATION_NAME"),
|
|
419
|
-
clientEncoding: merged.clientEncoding ?? settings.popt.topt.encoding,
|
|
420
|
-
};
|
|
280
|
+
* Build a full {@link ConnectOptions} for the new connection by merging
|
|
281
|
+
* `override` over the "previous" connection state. The previous values are
|
|
282
|
+
* sourced from `settings.vars` (HOST/PORT/USER/DBNAME/PASSWORD) which the
|
|
283
|
+
* startup WP populates; for fields not represented there (sslmode, etc.)
|
|
284
|
+
* we use safe defaults.
|
|
285
|
+
*
|
|
286
|
+
* For passwords specifically the precedence is:
|
|
287
|
+
* 1. `override.password` — anything the user typed in this `\c` invocation
|
|
288
|
+
* (URI password or `password=` conninfo key) wins.
|
|
289
|
+
* 2. `previousPassword` — the password captured on the live
|
|
290
|
+
* {@link PgConnection}, mirroring libpq's behaviour of retaining the
|
|
291
|
+
* credential on the `PGconn` so reconnects work transparently.
|
|
292
|
+
* 3. `PASSWORD` psql var — set by `-W` / `PGPASSWORD` at startup.
|
|
293
|
+
*
|
|
294
|
+
* Returns `null` if the merge can't produce a usable opts (e.g. no database
|
|
295
|
+
* and no current connection).
|
|
296
|
+
*/
|
|
297
|
+
const mergeConnectOpts = (settings, override, prior = null) => {
|
|
298
|
+
const vars = settings.vars;
|
|
299
|
+
const ov = {};
|
|
300
|
+
for (const [k, v] of Object.entries(override)) if (v !== void 0) ov[k] = v;
|
|
301
|
+
const seed = prior ? { ...prior } : {};
|
|
302
|
+
const hostChanged = ov.host !== void 0 && ov.host !== prior?.host;
|
|
303
|
+
if (hostChanged || ov.port !== void 0 && ov.port !== prior?.port || ov.user !== void 0 && ov.user !== prior?.user) delete seed.password;
|
|
304
|
+
if (hostChanged) delete seed.hostaddr;
|
|
305
|
+
const merged = {
|
|
306
|
+
...seed,
|
|
307
|
+
...ov
|
|
308
|
+
};
|
|
309
|
+
const host = merged.host ?? vars.get("HOST") ?? "localhost";
|
|
310
|
+
const portStr = vars.get("PORT");
|
|
311
|
+
const port = merged.port ?? (portStr !== void 0 ? parseInt(portStr, 10) : 5432);
|
|
312
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) return { error: `invalid port number` };
|
|
313
|
+
const user = merged.user ?? vars.get("USER") ?? process.env.USER ?? process.env.USERNAME ?? "";
|
|
314
|
+
if (user.length === 0) return { error: "no user name specified" };
|
|
315
|
+
const database = merged.database ?? vars.get("DBNAME") ?? user;
|
|
316
|
+
const password = merged.password ?? vars.get("PASSWORD");
|
|
317
|
+
const ssl = merged.ssl ?? "prefer";
|
|
318
|
+
return {
|
|
319
|
+
...merged,
|
|
320
|
+
host,
|
|
321
|
+
port,
|
|
322
|
+
user,
|
|
323
|
+
password: password ?? void 0,
|
|
324
|
+
database,
|
|
325
|
+
ssl,
|
|
326
|
+
applicationName: merged.applicationName ?? vars.get("APPLICATION_NAME"),
|
|
327
|
+
clientEncoding: merged.clientEncoding ?? settings.popt.topt.encoding
|
|
328
|
+
};
|
|
421
329
|
};
|
|
422
330
|
/** `\c` / `\connect` — reconnect, possibly to a different database/user/host. */
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
writeErr(`\\${ctx.cmdName}: connection failed: ${msg}\n`);
|
|
462
|
-
return { status: "error" };
|
|
463
|
-
}
|
|
464
|
-
const old = ctx.settings.db;
|
|
465
|
-
ctx.settings.db = next;
|
|
466
|
-
// Mirror upstream do_connect → SyncVariables(): refresh the
|
|
467
|
-
// connection-driven psql vars (DBNAME/USER/HOST/PORT/ENCODING/
|
|
468
|
-
// SERVER_VERSION_*) from the new live connection so subsequent
|
|
469
|
-
// `:DBNAME`/`:USER`/etc. interpolations reflect the reconnect target.
|
|
470
|
-
syncConnectionVars(ctx.settings.vars, next);
|
|
471
|
-
if (old && !old.isClosed()) {
|
|
472
|
-
try {
|
|
473
|
-
await old.close();
|
|
474
|
-
}
|
|
475
|
-
catch {
|
|
476
|
-
// Old connection may already be in a weird state; we've moved on.
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
writeOut(`You are now connected to database "${newOpts.database}" as user "${newOpts.user}".\n`);
|
|
480
|
-
return { status: "ok" };
|
|
481
|
-
},
|
|
331
|
+
const cmdConnect = {
|
|
332
|
+
name: "c",
|
|
333
|
+
aliases: ["connect"],
|
|
334
|
+
argMode: "whole-line",
|
|
335
|
+
helpKey: "c",
|
|
336
|
+
run: async (ctx) => {
|
|
337
|
+
const rawArgs = ctx.restOfLine();
|
|
338
|
+
if (rawArgs.trim().length === 0) return runConninfo(ctx);
|
|
339
|
+
const parsed = parseConnectArgs(rawArgs);
|
|
340
|
+
if ("error" in parsed) {
|
|
341
|
+
writeErr(`\\${ctx.cmdName}: ${parsed.error}\n`);
|
|
342
|
+
return { status: "error" };
|
|
343
|
+
}
|
|
344
|
+
const priorOpts = readConnectionOptions(ctx.settings.db);
|
|
345
|
+
const priorPw = readConnectionPassword(ctx.settings.db);
|
|
346
|
+
const prior = priorOpts ?? (priorPw !== null ? { password: priorPw } : null);
|
|
347
|
+
const newOpts = mergeConnectOpts(ctx.settings, parsed, prior);
|
|
348
|
+
if ("error" in newOpts) {
|
|
349
|
+
writeErr(`\\${ctx.cmdName}: ${newOpts.error}\n`);
|
|
350
|
+
return { status: "error" };
|
|
351
|
+
}
|
|
352
|
+
let next;
|
|
353
|
+
try {
|
|
354
|
+
next = await currentDeps.connect(newOpts);
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
357
|
+
writeErr(`\\${ctx.cmdName}: connection failed: ${msg}\n`);
|
|
358
|
+
return { status: "error" };
|
|
359
|
+
}
|
|
360
|
+
const old = ctx.settings.db;
|
|
361
|
+
ctx.settings.db = next;
|
|
362
|
+
syncConnectionVars(ctx.settings.vars, next);
|
|
363
|
+
if (old && !old.isClosed()) try {
|
|
364
|
+
await old.close();
|
|
365
|
+
} catch {}
|
|
366
|
+
writeOut(`You are now connected to database "${newOpts.database}" as user "${newOpts.user}".\n`);
|
|
367
|
+
return { status: "ok" };
|
|
368
|
+
}
|
|
482
369
|
};
|
|
483
|
-
// ---------------------------------------------------------------------------
|
|
484
|
-
// \conninfo
|
|
485
|
-
// ---------------------------------------------------------------------------
|
|
486
370
|
/**
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
371
|
+
* Pick the printer for the active output format. Mirrors the private
|
|
372
|
+
* `pickPrinter` in `core/common.ts` / `pickActivePrinter` in `cmd_io.ts`
|
|
373
|
+
* — replicated here (those are module-private) to avoid an import cycle,
|
|
374
|
+
* the same established pattern this codebase already uses for the two
|
|
375
|
+
* other copies.
|
|
376
|
+
*/
|
|
493
377
|
const pickActivePrinter = (settings) => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return asciidocPrinter;
|
|
508
|
-
case "latex":
|
|
509
|
-
return latexPrinter;
|
|
510
|
-
case "latex-longtable":
|
|
511
|
-
return latexLongtablePrinter;
|
|
512
|
-
case "troff-ms":
|
|
513
|
-
return troffMsPrinter;
|
|
514
|
-
default:
|
|
515
|
-
return alignedPrinter;
|
|
516
|
-
}
|
|
378
|
+
switch (settings.popt.topt.format) {
|
|
379
|
+
case "aligned":
|
|
380
|
+
case "wrapped": return alignedPrinter;
|
|
381
|
+
case "unaligned": return unalignedPrinter;
|
|
382
|
+
case "csv": return csvPrinter;
|
|
383
|
+
case "json": return jsonPrinter;
|
|
384
|
+
case "html": return htmlPrinter;
|
|
385
|
+
case "asciidoc": return asciidocPrinter;
|
|
386
|
+
case "latex": return latexPrinter;
|
|
387
|
+
case "latex-longtable": return latexLongtablePrinter;
|
|
388
|
+
case "troff-ms": return troffMsPrinter;
|
|
389
|
+
default: return alignedPrinter;
|
|
390
|
+
}
|
|
517
391
|
};
|
|
518
392
|
/** A text-typed field descriptor for the synthetic conninfo ResultSet. */
|
|
519
393
|
const textField = (name) => ({
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
format: 0,
|
|
394
|
+
name,
|
|
395
|
+
tableID: 0,
|
|
396
|
+
columnID: 0,
|
|
397
|
+
dataTypeID: 25,
|
|
398
|
+
dataTypeSize: -1,
|
|
399
|
+
dataTypeModifier: -1,
|
|
400
|
+
format: 0
|
|
528
401
|
});
|
|
529
402
|
/**
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
403
|
+
* Build the (Parameter, Value) rows for the PG18 connection-information
|
|
404
|
+
* table. Mirrors the row order and gating of upstream
|
|
405
|
+
* `exec_command_conninfo` (`src/bin/psql/command.c`).
|
|
406
|
+
*/
|
|
534
407
|
const buildConninfoRows = (ctx, db) => {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
rows.push(["Server Port", portStr]);
|
|
572
|
-
rows.push(["Options", info?.options ?? ""]);
|
|
573
|
-
rows.push(["Protocol Version", "3.0"]);
|
|
574
|
-
rows.push(["Password Used", info?.passwordUsed ? "true" : "false"]);
|
|
575
|
-
rows.push(["GSSAPI Authenticated", info?.gssapiUsed ? "true" : "false"]);
|
|
576
|
-
rows.push(["Backend PID", String(info?.backendPid ?? 0)]);
|
|
577
|
-
rows.push(["SSL Connection", tls ? "true" : "false"]);
|
|
578
|
-
if (tls) {
|
|
579
|
-
rows.push(["SSL Library", tls.library]);
|
|
580
|
-
rows.push(["SSL Protocol", tls.protocol]);
|
|
581
|
-
rows.push([
|
|
582
|
-
"SSL Key Bits",
|
|
583
|
-
tls.keyBits !== null ? String(tls.keyBits) : "",
|
|
584
|
-
]);
|
|
585
|
-
rows.push(["SSL Cipher", tls.cipher]);
|
|
586
|
-
rows.push([
|
|
587
|
-
"SSL Compression",
|
|
588
|
-
tls.compression !== "off" ? "true" : "false",
|
|
589
|
-
]);
|
|
590
|
-
rows.push([
|
|
591
|
-
"ALPN",
|
|
592
|
-
tls.alpn && tls.alpn.length > 0 ? tls.alpn : "none",
|
|
593
|
-
]);
|
|
594
|
-
}
|
|
595
|
-
rows.push([
|
|
596
|
-
"Superuser",
|
|
597
|
-
ctx.settings.db?.parameterStatus("is_superuser") ?? "unknown",
|
|
598
|
-
]);
|
|
599
|
-
rows.push([
|
|
600
|
-
"Hot Standby",
|
|
601
|
-
ctx.settings.db?.parameterStatus("in_hot_standby") ?? "unknown",
|
|
602
|
-
]);
|
|
603
|
-
return rows;
|
|
408
|
+
const info = db.getConnectionInfo?.() ?? null;
|
|
409
|
+
const tls = db.getTlsInfo?.() ?? null;
|
|
410
|
+
const database = ctx.settings.vars.get("DBNAME") ?? db.database ?? "";
|
|
411
|
+
const user = ctx.settings.vars.get("USER") ?? db.user ?? "";
|
|
412
|
+
const host = info?.host ?? ctx.settings.vars.get("HOST") ?? "";
|
|
413
|
+
const hostaddr = info?.hostaddr ?? null;
|
|
414
|
+
const port = info?.port ?? Number(ctx.settings.vars.get("PORT") ?? NaN);
|
|
415
|
+
const portStr = Number.isFinite(port) ? String(port) : "";
|
|
416
|
+
const rows = [];
|
|
417
|
+
rows.push(["Database", database]);
|
|
418
|
+
rows.push(["Client User", user]);
|
|
419
|
+
if (host.startsWith("/")) if (hostaddr !== null && hostaddr.length > 0) rows.push(["Host Address", hostaddr]);
|
|
420
|
+
else rows.push(["Socket Directory", host]);
|
|
421
|
+
else {
|
|
422
|
+
rows.push(["Host", host]);
|
|
423
|
+
if (hostaddr !== null && hostaddr.length > 0 && hostaddr !== host) rows.push(["Host Address", hostaddr]);
|
|
424
|
+
}
|
|
425
|
+
rows.push(["Server Port", portStr]);
|
|
426
|
+
rows.push(["Options", info?.options ?? ""]);
|
|
427
|
+
rows.push(["Protocol Version", "3.0"]);
|
|
428
|
+
rows.push(["Password Used", info?.passwordUsed ? "true" : "false"]);
|
|
429
|
+
rows.push(["GSSAPI Authenticated", info?.gssapiUsed ? "true" : "false"]);
|
|
430
|
+
rows.push(["Backend PID", String(info?.backendPid ?? 0)]);
|
|
431
|
+
rows.push(["SSL Connection", tls ? "true" : "false"]);
|
|
432
|
+
if (tls) {
|
|
433
|
+
rows.push(["SSL Library", tls.library]);
|
|
434
|
+
rows.push(["SSL Protocol", tls.protocol]);
|
|
435
|
+
rows.push(["SSL Key Bits", tls.keyBits !== null ? String(tls.keyBits) : ""]);
|
|
436
|
+
rows.push(["SSL Cipher", tls.cipher]);
|
|
437
|
+
rows.push(["SSL Compression", tls.compression !== "off" ? "true" : "false"]);
|
|
438
|
+
rows.push(["ALPN", tls.alpn && tls.alpn.length > 0 ? tls.alpn : "none"]);
|
|
439
|
+
}
|
|
440
|
+
rows.push(["Superuser", ctx.settings.db?.parameterStatus("is_superuser") ?? "unknown"]);
|
|
441
|
+
rows.push(["Hot Standby", ctx.settings.db?.parameterStatus("in_hot_standby") ?? "unknown"]);
|
|
442
|
+
return rows;
|
|
604
443
|
};
|
|
605
444
|
const runConninfo = async (ctx) => {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const out = process.stdout;
|
|
633
|
-
await pickActivePrinter(ctx.settings).printQuery(rs, popt, out);
|
|
634
|
-
return { status: "ok" };
|
|
445
|
+
const db = ctx.settings.db;
|
|
446
|
+
if (!db) {
|
|
447
|
+
writeOut("You are currently not connected to a database.\n");
|
|
448
|
+
return { status: "ok" };
|
|
449
|
+
}
|
|
450
|
+
const rows = buildConninfoRows(ctx, db);
|
|
451
|
+
const rs = {
|
|
452
|
+
command: "SELECT",
|
|
453
|
+
rowCount: rows.length,
|
|
454
|
+
oid: null,
|
|
455
|
+
fields: [textField("Parameter"), textField("Value")],
|
|
456
|
+
rows: rows.map(([p, v]) => [p, v]),
|
|
457
|
+
notices: []
|
|
458
|
+
};
|
|
459
|
+
const popt = {
|
|
460
|
+
...ctx.settings.popt,
|
|
461
|
+
title: "Connection Information",
|
|
462
|
+
topt: {
|
|
463
|
+
...ctx.settings.popt.topt,
|
|
464
|
+
title: "Connection Information",
|
|
465
|
+
defaultFooter: true
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const out = process.stdout;
|
|
469
|
+
await pickActivePrinter(ctx.settings).printQuery(rs, popt, out);
|
|
470
|
+
return { status: "ok" };
|
|
635
471
|
};
|
|
636
472
|
/** `\conninfo` — print info about the current connection. */
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
473
|
+
const cmdConninfo = {
|
|
474
|
+
name: "conninfo",
|
|
475
|
+
helpKey: "conninfo",
|
|
476
|
+
run: (ctx) => runConninfo(ctx)
|
|
641
477
|
};
|
|
642
|
-
// ---------------------------------------------------------------------------
|
|
643
|
-
// \encoding — overrides cmd_format.ts's version so the live connection
|
|
644
|
-
// also sees the new client_encoding (via SET client_encoding TO …).
|
|
645
|
-
// ---------------------------------------------------------------------------
|
|
646
478
|
/** `\encoding [NAME]` — show or set the client encoding, propagating to the connection. */
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
topt.encoding = arg;
|
|
671
|
-
ctx.settings.vars.set("ENCODING", arg);
|
|
672
|
-
return { status: "ok" };
|
|
673
|
-
},
|
|
479
|
+
const cmdEncoding = {
|
|
480
|
+
name: "encoding",
|
|
481
|
+
helpKey: "encoding",
|
|
482
|
+
run: async (ctx) => {
|
|
483
|
+
const arg = ctx.nextArg("normal");
|
|
484
|
+
const topt = ctx.settings.popt.topt;
|
|
485
|
+
if (arg === null) {
|
|
486
|
+
writeOut(`${topt.encoding}\n`);
|
|
487
|
+
return { status: "ok" };
|
|
488
|
+
}
|
|
489
|
+
const db = ctx.settings.db;
|
|
490
|
+
if (db && !db.isClosed()) try {
|
|
491
|
+
await db.execSimple(`SET client_encoding TO ${db.escapeLiteral(arg)}`);
|
|
492
|
+
} catch (err) {
|
|
493
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
494
|
+
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
495
|
+
return { status: "error" };
|
|
496
|
+
}
|
|
497
|
+
topt.encoding = arg;
|
|
498
|
+
ctx.settings.vars.set("ENCODING", arg);
|
|
499
|
+
return { status: "ok" };
|
|
500
|
+
}
|
|
674
501
|
};
|
|
675
|
-
// ---------------------------------------------------------------------------
|
|
676
|
-
// \password
|
|
677
|
-
// ---------------------------------------------------------------------------
|
|
678
502
|
/**
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const serverKey = createHmac("sha256", saltedPassword)
|
|
707
|
-
.update("Server Key")
|
|
708
|
-
.digest();
|
|
709
|
-
return ("SCRAM-SHA-256$" +
|
|
710
|
-
String(iterations) +
|
|
711
|
-
":" +
|
|
712
|
-
salt.toString("base64") +
|
|
713
|
-
"$" +
|
|
714
|
-
storedKey.toString("base64") +
|
|
715
|
-
":" +
|
|
716
|
-
serverKey.toString("base64"));
|
|
503
|
+
* Encode a password into PostgreSQL's SCRAM-SHA-256 verifier format, the
|
|
504
|
+
* value `ALTER ROLE … PASSWORD '…'` expects when the server is configured
|
|
505
|
+
* with `password_encryption = scram-sha-256` (the default since PG 14):
|
|
506
|
+
*
|
|
507
|
+
* SCRAM-SHA-256$<iterations>:<base64 salt>$<base64 storedKey>:<base64 serverKey>
|
|
508
|
+
*
|
|
509
|
+
* Derivation follows RFC 5802 §3:
|
|
510
|
+
*
|
|
511
|
+
* SaltedPassword = PBKDF2-HMAC-SHA256(password, salt, iterations, 32)
|
|
512
|
+
* ClientKey = HMAC-SHA256(SaltedPassword, "Client Key")
|
|
513
|
+
* StoredKey = SHA256(ClientKey)
|
|
514
|
+
* ServerKey = HMAC-SHA256(SaltedPassword, "Server Key")
|
|
515
|
+
*
|
|
516
|
+
* Sending the encoded form (rather than the plaintext password) means the
|
|
517
|
+
* server never sees the password, even briefly, and the SCRAM verifier is
|
|
518
|
+
* stored as-is in `pg_authid.rolpassword`.
|
|
519
|
+
*
|
|
520
|
+
* Iterations default to 4096 to match PG's `scram_iterations` default and
|
|
521
|
+
* libpq's `PQchangePassword`. The caller can override (lower for tests,
|
|
522
|
+
* higher for stronger hardening).
|
|
523
|
+
*/
|
|
524
|
+
const scramSha256Verifier = (password, salt, iterations = 4096) => {
|
|
525
|
+
const saltedPassword = pbkdf2Sync(Buffer.from(password, "utf8"), salt, iterations, 32, "sha256");
|
|
526
|
+
const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
|
|
527
|
+
const storedKey = createHash("sha256").update(clientKey).digest();
|
|
528
|
+
const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
|
|
529
|
+
return "SCRAM-SHA-256$" + String(iterations) + ":" + salt.toString("base64") + "$" + storedKey.toString("base64") + ":" + serverKey.toString("base64");
|
|
717
530
|
};
|
|
718
531
|
/**
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
532
|
+
* Default password / prompt reader. Delegates to the shared input layer
|
|
533
|
+
* ({@link readInputLine}), which suppresses echo on a TTY and falls back to a
|
|
534
|
+
* plain line read otherwise. Kept as a named function so the test seam in
|
|
535
|
+
* {@link CmdConnectDeps} can swap it out.
|
|
536
|
+
*/
|
|
724
537
|
function defaultReadLine(prompt, opts) {
|
|
725
|
-
|
|
538
|
+
return readLine(prompt, { echo: opts.echo });
|
|
726
539
|
}
|
|
727
540
|
/** `\password [USERNAME]` — change a role's password, locally hashed as SCRAM. */
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
// Match upstream's PQchangePassword: `ALTER USER <id> PASSWORD <lit>`.
|
|
787
|
-
// (ALTER USER and ALTER ROLE are synonyms in PG, but we follow the
|
|
788
|
-
// libpq spelling for byte-for-byte parity with vanilla psql.)
|
|
789
|
-
const salt = currentDeps.randomBytes(16);
|
|
790
|
-
const verifier = scramSha256Verifier(pw1, salt);
|
|
791
|
-
const sql = "ALTER USER " +
|
|
792
|
-
db.escapeIdentifier(user) +
|
|
793
|
-
" PASSWORD " +
|
|
794
|
-
db.escapeLiteral(verifier);
|
|
795
|
-
try {
|
|
796
|
-
await db.execSimple(sql);
|
|
797
|
-
}
|
|
798
|
-
catch (err) {
|
|
799
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
800
|
-
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
801
|
-
return { status: "error" };
|
|
802
|
-
}
|
|
803
|
-
return { status: "ok" };
|
|
804
|
-
},
|
|
541
|
+
const cmdPassword = {
|
|
542
|
+
name: "password",
|
|
543
|
+
helpKey: "password",
|
|
544
|
+
run: async (ctx) => {
|
|
545
|
+
const db = ctx.settings.db;
|
|
546
|
+
if (!db || db.isClosed()) {
|
|
547
|
+
writeErr(`\\${ctx.cmdName}: not connected\n`);
|
|
548
|
+
return { status: "error" };
|
|
549
|
+
}
|
|
550
|
+
if (ctx.settings.notty) {
|
|
551
|
+
writeErr(`\\${ctx.cmdName}: not in interactive mode\n`);
|
|
552
|
+
return { status: "error" };
|
|
553
|
+
}
|
|
554
|
+
let user = ctx.nextArg("sql-id");
|
|
555
|
+
if (user === null) {
|
|
556
|
+
try {
|
|
557
|
+
const row = (await db.query("SELECT CURRENT_USER")).rows[0] ?? [];
|
|
558
|
+
user = typeof row[0] === "string" ? row[0] : null;
|
|
559
|
+
} catch (err) {
|
|
560
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
561
|
+
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
562
|
+
return { status: "error" };
|
|
563
|
+
}
|
|
564
|
+
if (!user) {
|
|
565
|
+
writeErr(`\\${ctx.cmdName}: could not determine current user\n`);
|
|
566
|
+
return { status: "error" };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
let pw1;
|
|
570
|
+
let pw2;
|
|
571
|
+
try {
|
|
572
|
+
pw1 = await currentDeps.readLine(`Enter new password for user "${user}": `, { echo: false });
|
|
573
|
+
pw2 = await currentDeps.readLine("Enter it again: ", { echo: false });
|
|
574
|
+
} catch (err) {
|
|
575
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
576
|
+
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
577
|
+
return { status: "error" };
|
|
578
|
+
}
|
|
579
|
+
if (pw1 !== pw2) {
|
|
580
|
+
writeErr(`\\${ctx.cmdName}: Passwords didn't match.\n`);
|
|
581
|
+
return { status: "error" };
|
|
582
|
+
}
|
|
583
|
+
if (pw1.length === 0) {
|
|
584
|
+
writeErr(`\\${ctx.cmdName}: empty password\n`);
|
|
585
|
+
return { status: "error" };
|
|
586
|
+
}
|
|
587
|
+
const salt = currentDeps.randomBytes(16);
|
|
588
|
+
const verifier = scramSha256Verifier(pw1, salt);
|
|
589
|
+
const sql = "ALTER USER " + db.escapeIdentifier(user) + " PASSWORD " + db.escapeLiteral(verifier);
|
|
590
|
+
try {
|
|
591
|
+
await db.execSimple(sql);
|
|
592
|
+
} catch (err) {
|
|
593
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
594
|
+
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
595
|
+
return { status: "error" };
|
|
596
|
+
}
|
|
597
|
+
return { status: "ok" };
|
|
598
|
+
}
|
|
805
599
|
};
|
|
806
|
-
// ---------------------------------------------------------------------------
|
|
807
|
-
// Registry hook
|
|
808
|
-
// ---------------------------------------------------------------------------
|
|
809
600
|
/**
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
601
|
+
* Register the four connection commands on the given registry. Called from
|
|
602
|
+
* `dispatch.ts::defaultRegistry()`. Re-registering an existing primary name
|
|
603
|
+
* overrides — that's how we replace the no-op `\encoding` from cmd_format
|
|
604
|
+
* with this WP's version that propagates to the connection.
|
|
605
|
+
*/
|
|
606
|
+
const registerConnectCommands = (registry) => {
|
|
607
|
+
registry.register(cmdConnect);
|
|
608
|
+
registry.register(cmdConninfo);
|
|
609
|
+
registry.register(cmdEncoding);
|
|
610
|
+
registry.register(cmdPassword);
|
|
820
611
|
};
|
|
612
|
+
//#endregion
|
|
613
|
+
export { cmdConnect, cmdConninfo, cmdEncoding, cmdPassword, mergeConnectOpts, parseConnectArgs, registerConnectCommands, scramSha256Verifier, setCmdConnectDeps };
|