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
package/dist/psql/wire/sasl.js
CHANGED
|
@@ -1,294 +1,209 @@
|
|
|
1
|
+
import { createHash, createHmac, pbkdf2Sync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
//#region src/psql/wire/sasl.ts
|
|
1
3
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
start: () => start(internal),
|
|
65
|
-
continue: (serverFirst) => continueSession(internal, serverFirst),
|
|
66
|
-
finish: (serverFinal) => {
|
|
67
|
-
finishSession(internal, serverFinal);
|
|
68
|
-
},
|
|
69
|
-
};
|
|
4
|
+
* SASL / SCRAM-SHA-256[-PLUS] client for the psql wire layer (WP-03).
|
|
5
|
+
*
|
|
6
|
+
* Ported from node-postgres' `packages/pg/lib/crypto/sasl.js` (MIT,
|
|
7
|
+
* Copyright (c) 2010-2020 Brian Carlson). Adaptations from upstream:
|
|
8
|
+
*
|
|
9
|
+
* - Pure Node `node:crypto` (sync PBKDF2 / HMAC) instead of SubtleCrypto;
|
|
10
|
+
* this module is sync because the wire layer drives it from a state
|
|
11
|
+
* machine that already buffers bytes.
|
|
12
|
+
* - The `pg` `Connection` / `Stream` plumbing is removed. Channel binding
|
|
13
|
+
* data (the certificate hash) is passed in as `Buffer` by the caller,
|
|
14
|
+
* not derived from a TLS socket. The caller (WP-02) is responsible for
|
|
15
|
+
* extracting `tls-server-end-point` per RFC 5929 (i.e. SHA-256 over
|
|
16
|
+
* the cert's `tbsCertificate`, or the cert's own signature hash when
|
|
17
|
+
* strong enough — that policy lives upstream of this module).
|
|
18
|
+
* - Returns Buffer-typed messages because the wire framer consumes
|
|
19
|
+
* Buffers; upstream pg works in strings because its message reader
|
|
20
|
+
* does the same.
|
|
21
|
+
* - SASLprep: ported upstream's permissive minimal SASLprep (the three
|
|
22
|
+
* transformations that actually change byte content). The full RFC
|
|
23
|
+
* 4013 prohibition + bidi tables are *not* implemented — matches
|
|
24
|
+
* libpq's and pg's behaviour, see comment on `saslprep` below.
|
|
25
|
+
*
|
|
26
|
+
* Server signature verification uses `crypto.timingSafeEqual` to avoid
|
|
27
|
+
* leaking timing information about the comparison result.
|
|
28
|
+
*/
|
|
29
|
+
var SaslMechanismError = class extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "SaslMechanismError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var SaslVerificationError = class extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "SaslVerificationError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var SaslProtocolError = class extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "SaslProtocolError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function createScramClient(opts) {
|
|
48
|
+
const { mechanism, gs2Header, cbindInput } = chooseMechanism(opts.mechanisms, opts.channelBinding);
|
|
49
|
+
const internal = {
|
|
50
|
+
state: "init",
|
|
51
|
+
mechanism,
|
|
52
|
+
clientNonce: (opts.randomBytes ?? randomBytes)(18).toString("base64"),
|
|
53
|
+
gs2Header,
|
|
54
|
+
cbindInput,
|
|
55
|
+
password: opts.password,
|
|
56
|
+
serverSignature: null
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
mechanism,
|
|
60
|
+
start: () => start(internal),
|
|
61
|
+
continue: (serverFirst) => continueSession(internal, serverFirst),
|
|
62
|
+
finish: (serverFinal) => {
|
|
63
|
+
finishSession(internal, serverFinal);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
70
66
|
}
|
|
71
67
|
function chooseMechanism(advertised, channelBinding) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
mechanism: "SCRAM-SHA-256",
|
|
92
|
-
gs2Header: gs2,
|
|
93
|
-
cbindInput: Buffer.from(gs2, "utf8"),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
throw new SaslMechanismError(`SASL: server offered [${advertised.join(", ")}] but only SCRAM-SHA-256 and SCRAM-SHA-256-PLUS are supported`);
|
|
68
|
+
const hasPlus = advertised.includes("SCRAM-SHA-256-PLUS");
|
|
69
|
+
const hasPlain = advertised.includes("SCRAM-SHA-256");
|
|
70
|
+
if (channelBinding && hasPlus) {
|
|
71
|
+
const gs2 = "p=tls-server-end-point,,";
|
|
72
|
+
return {
|
|
73
|
+
mechanism: "SCRAM-SHA-256-PLUS",
|
|
74
|
+
gs2Header: gs2,
|
|
75
|
+
cbindInput: Buffer.concat([Buffer.from(gs2, "utf8"), channelBinding.data])
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (hasPlain) {
|
|
79
|
+
const gs2 = channelBinding ? "y,," : "n,,";
|
|
80
|
+
return {
|
|
81
|
+
mechanism: "SCRAM-SHA-256",
|
|
82
|
+
gs2Header: gs2,
|
|
83
|
+
cbindInput: Buffer.from(gs2, "utf8")
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
throw new SaslMechanismError(`SASL: server offered [${advertised.join(", ")}] but only SCRAM-SHA-256 and SCRAM-SHA-256-PLUS are supported`);
|
|
97
87
|
}
|
|
98
88
|
function start(s) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
s.state = "sent-first";
|
|
108
|
-
return { mechanism: s.mechanism, clientFirstMessage };
|
|
89
|
+
if (s.state !== "init") throw new SaslProtocolError(`SASL: start() called in state ${s.state}`);
|
|
90
|
+
const clientFirstBare = `n=,r=${s.clientNonce}`;
|
|
91
|
+
const clientFirstMessage = Buffer.from(s.gs2Header + clientFirstBare, "utf8");
|
|
92
|
+
s.state = "sent-first";
|
|
93
|
+
return {
|
|
94
|
+
mechanism: s.mechanism,
|
|
95
|
+
clientFirstMessage
|
|
96
|
+
};
|
|
109
97
|
}
|
|
110
98
|
function continueSession(s, serverFirst) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const authMessage = `${clientFirstBare},${serverFirstString},${clientFinalWithoutProof}`;
|
|
128
|
-
const saltBytes = Buffer.from(parsed.salt, "base64");
|
|
129
|
-
// SASLprep the password before PBKDF2 (RFC 5802 §2.2). Our impl is minimal;
|
|
130
|
-
// see saslprep() comment for the deviation from full RFC 4013.
|
|
131
|
-
const normalizedPassword = saslprep(s.password);
|
|
132
|
-
const saltedPassword = pbkdf2Sync(Buffer.from(normalizedPassword, "utf8"), saltBytes, parsed.iteration, 32, "sha256");
|
|
133
|
-
const clientKey = hmac(saltedPassword, "Client Key");
|
|
134
|
-
const storedKey = sha256(clientKey);
|
|
135
|
-
const clientSignature = hmac(storedKey, authMessage);
|
|
136
|
-
const clientProof = xor(clientKey, clientSignature);
|
|
137
|
-
const serverKey = hmac(saltedPassword, "Server Key");
|
|
138
|
-
s.serverSignature = hmac(serverKey, authMessage);
|
|
139
|
-
s.state = "sent-final";
|
|
140
|
-
return Buffer.from(`${clientFinalWithoutProof},p=${clientProof.toString("base64")}`, "utf8");
|
|
99
|
+
if (s.state !== "sent-first") throw new SaslProtocolError(`SASL: continue() called in state ${s.state}`);
|
|
100
|
+
const parsed = parseServerFirst(serverFirst.toString("utf8"));
|
|
101
|
+
if (!parsed.nonce.startsWith(s.clientNonce)) throw new SaslProtocolError("SASL: server nonce does not start with client nonce");
|
|
102
|
+
if (parsed.nonce.length === s.clientNonce.length) throw new SaslProtocolError("SASL: server nonce is too short");
|
|
103
|
+
const clientFirstBare = `n=,r=${s.clientNonce}`;
|
|
104
|
+
const serverFirstString = `r=${parsed.nonce},s=${parsed.salt},i=${String(parsed.iteration)}`;
|
|
105
|
+
const clientFinalWithoutProof = `c=${s.cbindInput.toString("base64")},r=${parsed.nonce}`;
|
|
106
|
+
const authMessage = `${clientFirstBare},${serverFirstString},${clientFinalWithoutProof}`;
|
|
107
|
+
const saltBytes = Buffer.from(parsed.salt, "base64");
|
|
108
|
+
const normalizedPassword = saslprep(s.password);
|
|
109
|
+
const saltedPassword = pbkdf2Sync(Buffer.from(normalizedPassword, "utf8"), saltBytes, parsed.iteration, 32, "sha256");
|
|
110
|
+
const clientKey = hmac(saltedPassword, "Client Key");
|
|
111
|
+
const clientProof = xor(clientKey, hmac(sha256(clientKey), authMessage));
|
|
112
|
+
s.serverSignature = hmac(hmac(saltedPassword, "Server Key"), authMessage);
|
|
113
|
+
s.state = "sent-final";
|
|
114
|
+
return Buffer.from(`${clientFinalWithoutProof},p=${clientProof.toString("base64")}`, "utf8");
|
|
141
115
|
}
|
|
142
116
|
function finishSession(s, serverFinal) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
const parsed = parseServerFinal(serverFinal.toString("utf8"));
|
|
151
|
-
const received = Buffer.from(parsed.serverSignature, "base64");
|
|
152
|
-
// Constant-time comparison: defeats timing side channels that could let an
|
|
153
|
-
// attacker (with control over `v=`) learn the prefix of our derived signature.
|
|
154
|
-
if (received.length !== s.serverSignature.length ||
|
|
155
|
-
!timingSafeEqual(received, s.serverSignature)) {
|
|
156
|
-
throw new SaslVerificationError("SASL: server signature does not match — possible MITM or wrong password");
|
|
157
|
-
}
|
|
158
|
-
s.state = "done";
|
|
117
|
+
if (s.state !== "sent-final") throw new SaslProtocolError(`SASL: finish() called in state ${s.state}`);
|
|
118
|
+
if (s.serverSignature === null) throw new SaslProtocolError("SASL: no server signature recorded");
|
|
119
|
+
const parsed = parseServerFinal(serverFinal.toString("utf8"));
|
|
120
|
+
const received = Buffer.from(parsed.serverSignature, "base64");
|
|
121
|
+
if (received.length !== s.serverSignature.length || !timingSafeEqual(received, s.serverSignature)) throw new SaslVerificationError("SASL: server signature does not match — possible MITM or wrong password");
|
|
122
|
+
s.state = "done";
|
|
159
123
|
}
|
|
160
124
|
function parseServerFirst(text) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const iterStr = attrs.get("i");
|
|
177
|
-
if (!iterStr) {
|
|
178
|
-
throw new SaslProtocolError("SASL: server-first missing `i=` (iterations)");
|
|
179
|
-
}
|
|
180
|
-
if (!/^[1-9][0-9]*$/.test(iterStr)) {
|
|
181
|
-
throw new SaslProtocolError(`SASL: server-first iteration count is not a positive integer: ${iterStr}`);
|
|
182
|
-
}
|
|
183
|
-
return { nonce, salt, iteration: parseInt(iterStr, 10) };
|
|
125
|
+
const attrs = parseAttributePairs(text);
|
|
126
|
+
const nonce = attrs.get("r");
|
|
127
|
+
if (!nonce) throw new SaslProtocolError("SASL: server-first missing `r=` (nonce)");
|
|
128
|
+
if (!isPrintableChars(nonce)) throw new SaslProtocolError("SASL: server-first nonce contains non-printable characters");
|
|
129
|
+
const salt = attrs.get("s");
|
|
130
|
+
if (!salt) throw new SaslProtocolError("SASL: server-first missing `s=` (salt)");
|
|
131
|
+
if (!isBase64(salt)) throw new SaslProtocolError("SASL: server-first salt is not base64");
|
|
132
|
+
const iterStr = attrs.get("i");
|
|
133
|
+
if (!iterStr) throw new SaslProtocolError("SASL: server-first missing `i=` (iterations)");
|
|
134
|
+
if (!/^[1-9][0-9]*$/.test(iterStr)) throw new SaslProtocolError(`SASL: server-first iteration count is not a positive integer: ${iterStr}`);
|
|
135
|
+
return {
|
|
136
|
+
nonce,
|
|
137
|
+
salt,
|
|
138
|
+
iteration: parseInt(iterStr, 10)
|
|
139
|
+
};
|
|
184
140
|
}
|
|
185
141
|
function parseServerFinal(text) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
throw new SaslProtocolError("SASL: server-final missing `v=` (server signature)");
|
|
194
|
-
}
|
|
195
|
-
if (!isBase64(v)) {
|
|
196
|
-
throw new SaslProtocolError("SASL: server-final `v=` is not base64");
|
|
197
|
-
}
|
|
198
|
-
return { serverSignature: v };
|
|
142
|
+
const attrs = parseAttributePairs(text);
|
|
143
|
+
const errorAttr = attrs.get("e");
|
|
144
|
+
if (errorAttr) throw new SaslVerificationError(`SASL: server-final returned error: ${errorAttr}`);
|
|
145
|
+
const v = attrs.get("v");
|
|
146
|
+
if (!v) throw new SaslProtocolError("SASL: server-final missing `v=` (server signature)");
|
|
147
|
+
if (!isBase64(v)) throw new SaslProtocolError("SASL: server-final `v=` is not base64");
|
|
148
|
+
return { serverSignature: v };
|
|
199
149
|
}
|
|
200
150
|
function parseAttributePairs(text) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// so a non-conformant/hostile server can't smuggle a shadow value
|
|
210
|
-
// (e.g. `v=<good>,v=<evil>`) past the parse.
|
|
211
|
-
if (out.has(key)) {
|
|
212
|
-
throw new SaslProtocolError(`SASL: duplicate attribute ${JSON.stringify(key)} in message`);
|
|
213
|
-
}
|
|
214
|
-
out.set(key, pair.substring(2));
|
|
215
|
-
}
|
|
216
|
-
return out;
|
|
151
|
+
const out = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const pair of text.split(",")) {
|
|
153
|
+
if (pair.length < 2 || pair[1] !== "=") throw new SaslProtocolError(`SASL: malformed attribute pair: ${JSON.stringify(pair)}`);
|
|
154
|
+
const key = pair[0];
|
|
155
|
+
if (out.has(key)) throw new SaslProtocolError(`SASL: duplicate attribute ${JSON.stringify(key)} in message`);
|
|
156
|
+
out.set(key, pair.substring(2));
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
217
159
|
}
|
|
218
|
-
// printable = %x21-2B / %x2D-7E ;; printable ASCII except ","
|
|
219
160
|
function isPrintableChars(text) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
return true;
|
|
161
|
+
for (let i = 0; i < text.length; i++) {
|
|
162
|
+
const c = text.charCodeAt(i);
|
|
163
|
+
if (!(c >= 33 && c <= 43 || c >= 45 && c <= 126)) return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
227
166
|
}
|
|
228
167
|
function isBase64(text) {
|
|
229
|
-
|
|
168
|
+
return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(text);
|
|
230
169
|
}
|
|
231
|
-
// ---------------------------------------------------------------------------
|
|
232
|
-
// Crypto helpers
|
|
233
|
-
// ---------------------------------------------------------------------------
|
|
234
170
|
function hmac(key, msg) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
171
|
+
const h = createHmac("sha256", key);
|
|
172
|
+
h.update(msg);
|
|
173
|
+
return h.digest();
|
|
238
174
|
}
|
|
239
175
|
function sha256(data) {
|
|
240
|
-
|
|
176
|
+
return createHash("sha256").update(data).digest();
|
|
241
177
|
}
|
|
242
178
|
function xor(a, b) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
for (let i = 0; i < a.length; i++) {
|
|
248
|
-
out[i] = a[i] ^ b[i];
|
|
249
|
-
}
|
|
250
|
-
return out;
|
|
179
|
+
if (a.length !== b.length) throw new SaslProtocolError(`SASL: XOR length mismatch (${String(a.length)} vs ${String(b.length)})`);
|
|
180
|
+
const out = Buffer.alloc(a.length);
|
|
181
|
+
for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];
|
|
182
|
+
return out;
|
|
251
183
|
}
|
|
252
|
-
// ---------------------------------------------------------------------------
|
|
253
|
-
// SASLprep
|
|
254
|
-
// ---------------------------------------------------------------------------
|
|
255
184
|
/**
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// string so the source file stays ASCII-only and so we sidestep ESLint's
|
|
277
|
-
// `no-irregular-whitespace` and `no-misleading-character-class` rules —
|
|
278
|
-
// those only inspect raw regex literals, not dynamically-built patterns.
|
|
279
|
-
const NON_ASCII_SPACE_RE = new RegExp("[" + "\u00A0\u1680" + "\u2000-\u200B" + "\u202F\u205F\u3000" + "]", "g");
|
|
280
|
-
// Combining marks and ZWJ are intentionally in this class; RFC 3454 Table B.1
|
|
281
|
-
// strips them precisely because they combine with neighbouring code points.
|
|
282
|
-
const MAPPED_TO_NOTHING_RE = new RegExp("[" +
|
|
283
|
-
"\u00AD\u034F\u1806" +
|
|
284
|
-
"\u180B-\u180D" +
|
|
285
|
-
"\u200C\u200D\u2060" +
|
|
286
|
-
"\uFE00-\uFE0F" +
|
|
287
|
-
"\uFEFF" +
|
|
288
|
-
"]", "g");
|
|
185
|
+
* Minimal SASLprep (RFC 4013) — exactly the three transformations that
|
|
186
|
+
* change byte content, ported from node-postgres' implementation:
|
|
187
|
+
*
|
|
188
|
+
* 1. RFC 3454 Table C.1.2 (non-ASCII space) -> U+0020 SPACE.
|
|
189
|
+
* 2. RFC 3454 Table B.1 (commonly mapped to nothing) -> empty.
|
|
190
|
+
* 3. NFKC normalization.
|
|
191
|
+
*
|
|
192
|
+
* We deliberately *skip* the RFC 4013 §2.3 prohibition table and the §6
|
|
193
|
+
* bidi checks. libpq is forgiving on those paths and PostgreSQL's own
|
|
194
|
+
* SASLprep matches that leniency for legacy roles — implementing strict
|
|
195
|
+
* RFC 4013 here would lock out passwords that already auth against the
|
|
196
|
+
* server. This deviation matches upstream pg.
|
|
197
|
+
*
|
|
198
|
+
* The character classes below intentionally contain combining marks and
|
|
199
|
+
* zero-width joiners (RFC 3454 Table B.1) and named spaces (Table C.1.2);
|
|
200
|
+
* they are spelled with `\u` escapes so the source is portable across
|
|
201
|
+
* encodings.
|
|
202
|
+
*/
|
|
203
|
+
const NON_ASCII_SPACE_RE = /* @__PURE__ */ new RegExp("[\xA0 - ]", "g");
|
|
204
|
+
const MAPPED_TO_NOTHING_RE = /* @__PURE__ */ new RegExp("[͏᠆᠋-᠍︀-️]", "g");
|
|
289
205
|
function saslprep(password) {
|
|
290
|
-
|
|
291
|
-
.replace(NON_ASCII_SPACE_RE, " ")
|
|
292
|
-
.replace(MAPPED_TO_NOTHING_RE, "")
|
|
293
|
-
.normalize("NFKC");
|
|
206
|
+
return password.replace(NON_ASCII_SPACE_RE, " ").replace(MAPPED_TO_NOTHING_RE, "").normalize("NFKC");
|
|
294
207
|
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { SaslMechanismError, SaslProtocolError, SaslVerificationError, createScramClient };
|