neonctl 2.27.1 → 2.29.0
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 +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
package/dist/psql/wire/sasl.js
CHANGED
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
* Server signature verification uses `crypto.timingSafeEqual` to avoid
|
|
25
25
|
* leaking timing information about the comparison result.
|
|
26
26
|
*/
|
|
27
|
-
import { createHash, createHmac,
|
|
27
|
+
import { createHash, createHmac, randomBytes as nodeRandomBytes, pbkdf2Sync, timingSafeEqual, } from "node:crypto";
|
|
28
28
|
export class SaslMechanismError extends Error {
|
|
29
29
|
constructor(message) {
|
|
30
30
|
super(message);
|
|
31
|
-
this.name =
|
|
31
|
+
this.name = "SaslMechanismError";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
export class SaslVerificationError extends Error {
|
|
35
35
|
constructor(message) {
|
|
36
36
|
super(message);
|
|
37
|
-
this.name =
|
|
37
|
+
this.name = "SaslVerificationError";
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
export class SaslProtocolError extends Error {
|
|
41
41
|
constructor(message) {
|
|
42
42
|
super(message);
|
|
43
|
-
this.name =
|
|
43
|
+
this.name = "SaslProtocolError";
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
export function createScramClient(opts) {
|
|
@@ -49,9 +49,9 @@ export function createScramClient(opts) {
|
|
|
49
49
|
// 18 raw bytes => 24 base64 chars; same width as upstream pg.
|
|
50
50
|
// Commas are not part of the base64 alphabet, so the nonce is automatically
|
|
51
51
|
// SCRAM-safe (RFC 5802 §5.1 forbids `,` in `r=`).
|
|
52
|
-
const clientNonce = rng(18).toString(
|
|
52
|
+
const clientNonce = rng(18).toString("base64");
|
|
53
53
|
const internal = {
|
|
54
|
-
state:
|
|
54
|
+
state: "init",
|
|
55
55
|
mechanism,
|
|
56
56
|
clientNonce,
|
|
57
57
|
gs2Header,
|
|
@@ -69,15 +69,15 @@ export function createScramClient(opts) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
function chooseMechanism(advertised, channelBinding) {
|
|
72
|
-
const hasPlus = advertised.includes(
|
|
73
|
-
const hasPlain = advertised.includes(
|
|
72
|
+
const hasPlus = advertised.includes("SCRAM-SHA-256-PLUS");
|
|
73
|
+
const hasPlain = advertised.includes("SCRAM-SHA-256");
|
|
74
74
|
if (channelBinding && hasPlus) {
|
|
75
|
-
const gs2 =
|
|
75
|
+
const gs2 = "p=tls-server-end-point,,";
|
|
76
76
|
return {
|
|
77
|
-
mechanism:
|
|
77
|
+
mechanism: "SCRAM-SHA-256-PLUS",
|
|
78
78
|
gs2Header: gs2,
|
|
79
79
|
cbindInput: Buffer.concat([
|
|
80
|
-
Buffer.from(gs2,
|
|
80
|
+
Buffer.from(gs2, "utf8"),
|
|
81
81
|
channelBinding.data,
|
|
82
82
|
]),
|
|
83
83
|
};
|
|
@@ -86,96 +86,96 @@ function chooseMechanism(advertised, channelBinding) {
|
|
|
86
86
|
// Client has channel-binding data but server doesn't advertise PLUS: per
|
|
87
87
|
// RFC 5802 §6 the client must signal this with `y` so a downgrade attack
|
|
88
88
|
// is detected (the server, if PLUS-capable, would have advertised it).
|
|
89
|
-
const gs2 = channelBinding ?
|
|
89
|
+
const gs2 = channelBinding ? "y,," : "n,,";
|
|
90
90
|
return {
|
|
91
|
-
mechanism:
|
|
91
|
+
mechanism: "SCRAM-SHA-256",
|
|
92
92
|
gs2Header: gs2,
|
|
93
|
-
cbindInput: Buffer.from(gs2,
|
|
93
|
+
cbindInput: Buffer.from(gs2, "utf8"),
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
-
throw new SaslMechanismError(`SASL: server offered [${advertised.join(
|
|
96
|
+
throw new SaslMechanismError(`SASL: server offered [${advertised.join(", ")}] but only SCRAM-SHA-256 and SCRAM-SHA-256-PLUS are supported`);
|
|
97
97
|
}
|
|
98
98
|
function start(s) {
|
|
99
|
-
if (s.state !==
|
|
99
|
+
if (s.state !== "init") {
|
|
100
100
|
throw new SaslProtocolError(`SASL: start() called in state ${s.state}`);
|
|
101
101
|
}
|
|
102
102
|
// n=,r=<nonce> — the username field is empty because PostgreSQL ignores it
|
|
103
103
|
// (the actual user was sent in StartupMessage) and an empty `n=` matches
|
|
104
104
|
// libpq's wire output. Note pg sends `n=*` instead; both interoperate.
|
|
105
105
|
const clientFirstBare = `n=,r=${s.clientNonce}`;
|
|
106
|
-
const clientFirstMessage = Buffer.from(s.gs2Header + clientFirstBare,
|
|
107
|
-
s.state =
|
|
106
|
+
const clientFirstMessage = Buffer.from(s.gs2Header + clientFirstBare, "utf8");
|
|
107
|
+
s.state = "sent-first";
|
|
108
108
|
return { mechanism: s.mechanism, clientFirstMessage };
|
|
109
109
|
}
|
|
110
110
|
function continueSession(s, serverFirst) {
|
|
111
|
-
if (s.state !==
|
|
111
|
+
if (s.state !== "sent-first") {
|
|
112
112
|
throw new SaslProtocolError(`SASL: continue() called in state ${s.state}`);
|
|
113
113
|
}
|
|
114
|
-
const serverFirstStr = serverFirst.toString(
|
|
114
|
+
const serverFirstStr = serverFirst.toString("utf8");
|
|
115
115
|
const parsed = parseServerFirst(serverFirstStr);
|
|
116
116
|
if (!parsed.nonce.startsWith(s.clientNonce)) {
|
|
117
|
-
throw new SaslProtocolError(
|
|
117
|
+
throw new SaslProtocolError("SASL: server nonce does not start with client nonce");
|
|
118
118
|
}
|
|
119
119
|
if (parsed.nonce.length === s.clientNonce.length) {
|
|
120
|
-
throw new SaslProtocolError(
|
|
120
|
+
throw new SaslProtocolError("SASL: server nonce is too short");
|
|
121
121
|
}
|
|
122
122
|
const clientFirstBare = `n=,r=${s.clientNonce}`;
|
|
123
123
|
const serverFirstString = `r=${parsed.nonce},s=${parsed.salt},i=${String(parsed.iteration)}`;
|
|
124
124
|
// c=<base64 of GS2 header || optional CB data>
|
|
125
|
-
const channelBindingB64 = s.cbindInput.toString(
|
|
125
|
+
const channelBindingB64 = s.cbindInput.toString("base64");
|
|
126
126
|
const clientFinalWithoutProof = `c=${channelBindingB64},r=${parsed.nonce}`;
|
|
127
127
|
const authMessage = `${clientFirstBare},${serverFirstString},${clientFinalWithoutProof}`;
|
|
128
|
-
const saltBytes = Buffer.from(parsed.salt,
|
|
128
|
+
const saltBytes = Buffer.from(parsed.salt, "base64");
|
|
129
129
|
// SASLprep the password before PBKDF2 (RFC 5802 §2.2). Our impl is minimal;
|
|
130
130
|
// see saslprep() comment for the deviation from full RFC 4013.
|
|
131
131
|
const normalizedPassword = saslprep(s.password);
|
|
132
|
-
const saltedPassword = pbkdf2Sync(Buffer.from(normalizedPassword,
|
|
133
|
-
const clientKey = hmac(saltedPassword,
|
|
132
|
+
const saltedPassword = pbkdf2Sync(Buffer.from(normalizedPassword, "utf8"), saltBytes, parsed.iteration, 32, "sha256");
|
|
133
|
+
const clientKey = hmac(saltedPassword, "Client Key");
|
|
134
134
|
const storedKey = sha256(clientKey);
|
|
135
135
|
const clientSignature = hmac(storedKey, authMessage);
|
|
136
136
|
const clientProof = xor(clientKey, clientSignature);
|
|
137
|
-
const serverKey = hmac(saltedPassword,
|
|
137
|
+
const serverKey = hmac(saltedPassword, "Server Key");
|
|
138
138
|
s.serverSignature = hmac(serverKey, authMessage);
|
|
139
|
-
s.state =
|
|
140
|
-
return Buffer.from(`${clientFinalWithoutProof},p=${clientProof.toString(
|
|
139
|
+
s.state = "sent-final";
|
|
140
|
+
return Buffer.from(`${clientFinalWithoutProof},p=${clientProof.toString("base64")}`, "utf8");
|
|
141
141
|
}
|
|
142
142
|
function finishSession(s, serverFinal) {
|
|
143
|
-
if (s.state !==
|
|
143
|
+
if (s.state !== "sent-final") {
|
|
144
144
|
throw new SaslProtocolError(`SASL: finish() called in state ${s.state}`);
|
|
145
145
|
}
|
|
146
146
|
if (s.serverSignature === null) {
|
|
147
147
|
// Defensive: should be impossible because state machine guards it.
|
|
148
|
-
throw new SaslProtocolError(
|
|
148
|
+
throw new SaslProtocolError("SASL: no server signature recorded");
|
|
149
149
|
}
|
|
150
|
-
const parsed = parseServerFinal(serverFinal.toString(
|
|
151
|
-
const received = Buffer.from(parsed.serverSignature,
|
|
150
|
+
const parsed = parseServerFinal(serverFinal.toString("utf8"));
|
|
151
|
+
const received = Buffer.from(parsed.serverSignature, "base64");
|
|
152
152
|
// Constant-time comparison: defeats timing side channels that could let an
|
|
153
153
|
// attacker (with control over `v=`) learn the prefix of our derived signature.
|
|
154
154
|
if (received.length !== s.serverSignature.length ||
|
|
155
155
|
!timingSafeEqual(received, s.serverSignature)) {
|
|
156
|
-
throw new SaslVerificationError(
|
|
156
|
+
throw new SaslVerificationError("SASL: server signature does not match — possible MITM or wrong password");
|
|
157
157
|
}
|
|
158
|
-
s.state =
|
|
158
|
+
s.state = "done";
|
|
159
159
|
}
|
|
160
160
|
function parseServerFirst(text) {
|
|
161
161
|
const attrs = parseAttributePairs(text);
|
|
162
|
-
const nonce = attrs.get(
|
|
162
|
+
const nonce = attrs.get("r");
|
|
163
163
|
if (!nonce) {
|
|
164
|
-
throw new SaslProtocolError(
|
|
164
|
+
throw new SaslProtocolError("SASL: server-first missing `r=` (nonce)");
|
|
165
165
|
}
|
|
166
166
|
if (!isPrintableChars(nonce)) {
|
|
167
|
-
throw new SaslProtocolError(
|
|
167
|
+
throw new SaslProtocolError("SASL: server-first nonce contains non-printable characters");
|
|
168
168
|
}
|
|
169
|
-
const salt = attrs.get(
|
|
169
|
+
const salt = attrs.get("s");
|
|
170
170
|
if (!salt) {
|
|
171
|
-
throw new SaslProtocolError(
|
|
171
|
+
throw new SaslProtocolError("SASL: server-first missing `s=` (salt)");
|
|
172
172
|
}
|
|
173
173
|
if (!isBase64(salt)) {
|
|
174
|
-
throw new SaslProtocolError(
|
|
174
|
+
throw new SaslProtocolError("SASL: server-first salt is not base64");
|
|
175
175
|
}
|
|
176
|
-
const iterStr = attrs.get(
|
|
176
|
+
const iterStr = attrs.get("i");
|
|
177
177
|
if (!iterStr) {
|
|
178
|
-
throw new SaslProtocolError(
|
|
178
|
+
throw new SaslProtocolError("SASL: server-first missing `i=` (iterations)");
|
|
179
179
|
}
|
|
180
180
|
if (!/^[1-9][0-9]*$/.test(iterStr)) {
|
|
181
181
|
throw new SaslProtocolError(`SASL: server-first iteration count is not a positive integer: ${iterStr}`);
|
|
@@ -184,23 +184,23 @@ function parseServerFirst(text) {
|
|
|
184
184
|
}
|
|
185
185
|
function parseServerFinal(text) {
|
|
186
186
|
const attrs = parseAttributePairs(text);
|
|
187
|
-
const errorAttr = attrs.get(
|
|
187
|
+
const errorAttr = attrs.get("e");
|
|
188
188
|
if (errorAttr) {
|
|
189
189
|
throw new SaslVerificationError(`SASL: server-final returned error: ${errorAttr}`);
|
|
190
190
|
}
|
|
191
|
-
const v = attrs.get(
|
|
191
|
+
const v = attrs.get("v");
|
|
192
192
|
if (!v) {
|
|
193
|
-
throw new SaslProtocolError(
|
|
193
|
+
throw new SaslProtocolError("SASL: server-final missing `v=` (server signature)");
|
|
194
194
|
}
|
|
195
195
|
if (!isBase64(v)) {
|
|
196
|
-
throw new SaslProtocolError(
|
|
196
|
+
throw new SaslProtocolError("SASL: server-final `v=` is not base64");
|
|
197
197
|
}
|
|
198
198
|
return { serverSignature: v };
|
|
199
199
|
}
|
|
200
200
|
function parseAttributePairs(text) {
|
|
201
201
|
const out = new Map();
|
|
202
|
-
for (const pair of text.split(
|
|
203
|
-
if (pair.length < 2 || pair[1] !==
|
|
202
|
+
for (const pair of text.split(",")) {
|
|
203
|
+
if (pair.length < 2 || pair[1] !== "=") {
|
|
204
204
|
throw new SaslProtocolError(`SASL: malformed attribute pair: ${JSON.stringify(pair)}`);
|
|
205
205
|
}
|
|
206
206
|
const key = pair[0];
|
|
@@ -232,12 +232,12 @@ function isBase64(text) {
|
|
|
232
232
|
// Crypto helpers
|
|
233
233
|
// ---------------------------------------------------------------------------
|
|
234
234
|
function hmac(key, msg) {
|
|
235
|
-
const h = createHmac(
|
|
235
|
+
const h = createHmac("sha256", key);
|
|
236
236
|
h.update(msg);
|
|
237
237
|
return h.digest();
|
|
238
238
|
}
|
|
239
239
|
function sha256(data) {
|
|
240
|
-
return createHash(
|
|
240
|
+
return createHash("sha256").update(data).digest();
|
|
241
241
|
}
|
|
242
242
|
function xor(a, b) {
|
|
243
243
|
if (a.length !== b.length) {
|
|
@@ -276,21 +276,21 @@ function xor(a, b) {
|
|
|
276
276
|
// string so the source file stays ASCII-only and so we sidestep ESLint's
|
|
277
277
|
// `no-irregular-whitespace` and `no-misleading-character-class` rules —
|
|
278
278
|
// those only inspect raw regex literals, not dynamically-built patterns.
|
|
279
|
-
const NON_ASCII_SPACE_RE = new RegExp(
|
|
279
|
+
const NON_ASCII_SPACE_RE = new RegExp("[" + "\u00A0\u1680" + "\u2000-\u200B" + "\u202F\u205F\u3000" + "]", "g");
|
|
280
280
|
// Combining marks and ZWJ are intentionally in this class; RFC 3454 Table B.1
|
|
281
281
|
// strips them precisely because they combine with neighbouring code points.
|
|
282
282
|
/* eslint-disable no-misleading-character-class */
|
|
283
|
-
const MAPPED_TO_NOTHING_RE = new RegExp(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
const MAPPED_TO_NOTHING_RE = new RegExp("[" +
|
|
284
|
+
"\u00AD\u034F\u1806" +
|
|
285
|
+
"\u180B-\u180D" +
|
|
286
|
+
"\u200C\u200D\u2060" +
|
|
287
|
+
"\uFE00-\uFE0F" +
|
|
288
|
+
"\uFEFF" +
|
|
289
|
+
"]", "g");
|
|
290
290
|
/* eslint-enable no-misleading-character-class */
|
|
291
291
|
function saslprep(password) {
|
|
292
292
|
return password
|
|
293
|
-
.replace(NON_ASCII_SPACE_RE,
|
|
294
|
-
.replace(MAPPED_TO_NOTHING_RE,
|
|
295
|
-
.normalize(
|
|
293
|
+
.replace(NON_ASCII_SPACE_RE, " ")
|
|
294
|
+
.replace(MAPPED_TO_NOTHING_RE, "")
|
|
295
|
+
.normalize("NFKC");
|
|
296
296
|
}
|