neon 3.2.2 → 3.3.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 +70 -30
- package/dist/_chunks/{auth_selection-DGgq6ifc.js → auth_selection-CV_3n1du.js} +1 -1
- package/dist/_chunks/{cmd_pipeline-CUbBO9U_.js → cmd_pipeline-CEk93dGN.js} +8 -6
- package/dist/_chunks/credential_io-D2vw1UOw.js +409 -0
- package/dist/_chunks/{paths-DMq0Lt7a.js → paths-g3DRqJiD.js} +1 -1
- package/dist/_chunks/{profiles-Ir29rqns.js → profiles-CVXNvU2r.js} +106 -66
- package/dist/_chunks/{psql-DWH-kc69.js → psql-BNljLda2.js} +8 -7
- package/dist/_chunks/{rolldown-runtime-D7D4PA-g.js → rolldown-runtime-8H4AJuhK.js} +1 -0
- package/dist/analytics.js +19 -5
- package/dist/auth_context.js +25 -24
- package/dist/commands/api.js +1 -1
- package/dist/commands/api_keys.js +1 -1
- package/dist/commands/auth.js +121 -80
- package/dist/commands/bootstrap.js +2 -2
- package/dist/commands/branches.js +1 -1
- package/dist/commands/bucket.js +1 -1
- package/dist/commands/checkout.js +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/connection_string.js +1 -1
- package/dist/commands/data_api.js +1 -1
- package/dist/commands/databases.js +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/dev.js +1 -1
- package/dist/commands/diff.js +1 -1
- package/dist/commands/env.js +1 -1
- package/dist/commands/functions.js +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/inspect.js +1 -1
- package/dist/commands/ip_allow.js +1 -1
- package/dist/commands/link.js +1 -1
- package/dist/commands/logs.js +1 -1
- package/dist/commands/neon_auth.js +1 -1
- package/dist/commands/open.js +1 -1
- package/dist/commands/operations.js +1 -1
- package/dist/commands/orgs.js +1 -1
- package/dist/commands/profile.js +183 -202
- package/dist/commands/projects.js +1 -1
- package/dist/commands/psql.js +1 -1
- package/dist/commands/roles.js +1 -1
- package/dist/commands/schema_diff.js +15 -13
- package/dist/commands/set_context.js +1 -1
- package/dist/commands/snapshots.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/user.js +1 -1
- package/dist/commands/vpc_endpoints.js +1 -1
- package/dist/config.js +1 -1
- package/dist/credential_io.js +2 -0
- package/dist/index.js +27 -25
- package/dist/init/auth.js +14 -25
- package/dist/keyring.js +42 -0
- package/dist/psql/cli.js +1 -1
- package/dist/psql/command/cmd_connect.js +4 -3
- package/dist/psql/command/cmd_copy.js +65 -62
- package/dist/psql/command/cmd_io.js +1 -1
- package/dist/psql/command/cmd_pipeline.js +1 -1
- package/dist/psql/command/dispatch.js +1 -1
- package/dist/psql/core/common.js +1 -1
- package/dist/psql/core/mainloop.js +1 -1
- package/dist/psql/core/startup.js +1 -1
- package/dist/psql/index.js +1 -1
- package/dist/psql/io/psqlrc.js +13 -11
- package/dist/psql/print/aligned.js +9 -5
- package/dist/psql/wire/tls.js +11 -9
- package/dist/retire_credential.js +86 -0
- package/dist/utils/inspect_db.js +1 -1
- package/dist/utils/middlewares.js +1 -1
- package/package.json +5 -4
- package/dist/_chunks/credentials-MYdHdKah.js +0 -188
- package/dist/_chunks/secure_file-BucZj4yQ.js +0 -39
|
@@ -1,55 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
//#region ../../internals/cli-core/dist/profiles.js
|
|
1
|
+
import { i as defaultDir, r as credentialsPath, s as resolveConfigFile } from "./paths-g3DRqJiD.js";
|
|
2
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
//#region ../../internals/cli-core/dist/secure_file.js
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* A profile is **a pointer to a credentials file**. Nothing more. That constraint is what
|
|
10
|
-
* keeps the feature small: there is no mirror, no per-profile directory tree, no persistent
|
|
11
|
-
* "active profile" state to fall out of sync, and no migration.
|
|
12
|
-
*
|
|
13
|
-
* ```
|
|
14
|
-
* ~/.config/neon/
|
|
15
|
-
* ├── credentials.json # this IS the DEFAULT profile, not a copy of it
|
|
16
|
-
* ├── credentials.work.json # created by `neon auth --profile work`
|
|
17
|
-
* └── profiles.json # created only once a second profile exists
|
|
18
|
-
* ```
|
|
19
|
-
*
|
|
20
|
-
* `profiles.json` maps a name to a path, and the path may point anywhere — which is what
|
|
21
|
-
* makes adopting an existing directory a one-line edit rather than an import command:
|
|
6
|
+
* Write a secret to disk owner-only, by creating a temporary file in the same directory and
|
|
7
|
+
* renaming it over the target.
|
|
22
8
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* "credentials": "../neonctl-databricks/credentials.json",
|
|
30
|
-
* "label": "someone@example.com"
|
|
31
|
-
* }
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
9
|
+
* The rename is what makes this correct rather than merely tidy. `writeFileSync`'s `mode`
|
|
10
|
+
* applies only when it *creates* the file, so writing over an existing credentials file
|
|
11
|
+
* leaves whatever permissions it already had — a file created `0700` by an older release
|
|
12
|
+
* stays `0700` forever, and one created before a umask change stays world-readable. Renaming
|
|
13
|
+
* a fresh inode into place means every write lands at {@link SECRET_FILE_MODE}, so the
|
|
14
|
+
* permissions repair themselves instead of being inherited.
|
|
35
15
|
*
|
|
36
|
-
*
|
|
16
|
+
* It also closes the window where a reader could see the file at default permissions: the
|
|
17
|
+
* temporary file is created `0600` *before* it holds the secret's final name, and `rename`
|
|
18
|
+
* is atomic within a directory, so there is no moment at which the target is readable by
|
|
19
|
+
* anyone else and no moment at which it is half-written.
|
|
37
20
|
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
21
|
+
* The temporary name carries the pid so two processes writing at once cannot collide on it.
|
|
22
|
+
*/
|
|
23
|
+
const writeSecretFile = (path, contents) => {
|
|
24
|
+
const directory = dirname(path);
|
|
25
|
+
const temporary = join(directory, `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(temporary, contents, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
mode: 384
|
|
30
|
+
});
|
|
31
|
+
renameSync(temporary, path);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
try {
|
|
34
|
+
unlinkSync(temporary);
|
|
35
|
+
} catch {}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region ../../internals/cli-core/dist/cli_config.js
|
|
41
|
+
const CRED_STORAGE_FILE = "file";
|
|
42
|
+
const CRED_STORAGE_KEYRING = "keyring";
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region ../../internals/cli-core/dist/profiles.js
|
|
45
|
+
/**
|
|
46
|
+
* Pointer-only profiles avoid mirrored credentials and persistent active-profile
|
|
47
|
+
* state while preserving existing single-account and legacy-directory installs.
|
|
47
48
|
*/
|
|
48
49
|
const PROFILES_FILE = "profiles.json";
|
|
50
|
+
const KEYRING_CREDENTIALS = "keyring";
|
|
51
|
+
const isKeyringPointer = (credentials) => credentials === KEYRING_CREDENTIALS;
|
|
49
52
|
/** The implicit profile. Backed by plain `credentials.json`, with or without a profiles file. */
|
|
50
53
|
const DEFAULT_PROFILE = "DEFAULT";
|
|
51
54
|
/** Profile names become part of a filename, so keep them boring. */
|
|
52
55
|
const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
56
|
+
const locationOf = (profile) => profile.storage === "keyring" ? {
|
|
57
|
+
profile: profile.name,
|
|
58
|
+
storage: CRED_STORAGE_KEYRING
|
|
59
|
+
} : {
|
|
60
|
+
profile: profile.name,
|
|
61
|
+
storage: CRED_STORAGE_FILE,
|
|
62
|
+
path: profile.credentialsPath
|
|
63
|
+
};
|
|
64
|
+
const credentialsDisplay = (profile) => profile.storage === "keyring" ? KEYRING_CREDENTIALS : profile.credentialsPath;
|
|
53
65
|
/** Which profile this invocation should use: `--profile` → `NEON_PROFILE` → `DEFAULT`. */
|
|
54
66
|
const selectProfileName = (flag, env = process.env) => nonEmpty(flag) ?? nonEmpty(env.NEON_PROFILE) ?? "DEFAULT";
|
|
55
67
|
const assertValidProfileName = (name) => {
|
|
@@ -90,7 +102,7 @@ const inspectProfiles = (dir) => {
|
|
|
90
102
|
if (profiles === null || typeof profiles !== "object" || Array.isArray(profiles)) return broken("it has no `profiles` object");
|
|
91
103
|
for (const [name, entry] of Object.entries(profiles)) {
|
|
92
104
|
if (!NAME_PATTERN.test(name)) return broken(`"${name}" is not a valid profile name`);
|
|
93
|
-
if (entry === null || typeof entry !== "object" || typeof entry.credentials !== "string" || entry.credentials.trim() === "") return broken(`profile "${name}" has no \`credentials\`
|
|
105
|
+
if (entry === null || typeof entry !== "object" || typeof entry.credentials !== "string" || entry.credentials.trim() === "") return broken(`profile "${name}" has no \`credentials\` pointer`);
|
|
94
106
|
}
|
|
95
107
|
return {
|
|
96
108
|
kind: "ok",
|
|
@@ -115,7 +127,7 @@ const readProfiles = (dir, onWarn = () => {}) => {
|
|
|
115
127
|
return null;
|
|
116
128
|
};
|
|
117
129
|
/**
|
|
118
|
-
*
|
|
130
|
+
* Storage cannot be trusted when the profiles file cannot be read.
|
|
119
131
|
*
|
|
120
132
|
* Call this **before** anything that writes a credential, opens a browser, or spends an API
|
|
121
133
|
* call. {@link upsertProfile} refuses too, but it runs last: by then `create` has already
|
|
@@ -124,29 +136,39 @@ const readProfiles = (dir, onWarn = () => {}) => {
|
|
|
124
136
|
* exists to prevent. The path resolution itself is the unsound part, since with the metadata
|
|
125
137
|
* unreadable the conventional filename is a guess about which account that file belongs to.
|
|
126
138
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
139
|
+
* DEFAULT follows the same rule because the broken file may be its only
|
|
140
|
+
* keyring pointer.
|
|
129
141
|
*/
|
|
130
142
|
const assertProfilesUsable = (dir, name) => {
|
|
131
|
-
if (name === "DEFAULT") return;
|
|
132
143
|
const read = inspectProfiles(dir);
|
|
133
144
|
if (read.kind === "unusable") throw new Error(`${read.reason}. Fix or delete the file before working with profile "${name}" — it is the only record of where each account's credentials live.`);
|
|
134
145
|
};
|
|
135
146
|
/** Resolve a profile to an absolute credentials path. Throws when a named profile is unknown. */
|
|
136
147
|
const resolveProfile = (dir, name) => {
|
|
137
148
|
const read = inspectProfiles(dir);
|
|
138
|
-
if (read.kind === "unusable"
|
|
149
|
+
if (read.kind === "unusable") throw new Error(`${read.reason}. Fix or delete the file — every profile is defined in it.`);
|
|
139
150
|
const file = read.kind === "ok" ? read.file : null;
|
|
140
151
|
const entry = file?.profiles[name];
|
|
141
|
-
if (entry)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
if (entry) {
|
|
153
|
+
if (isKeyringPointer(entry.credentials)) return {
|
|
154
|
+
name,
|
|
155
|
+
storage: CRED_STORAGE_KEYRING,
|
|
156
|
+
...entry.label ? { label: entry.label } : {},
|
|
157
|
+
...entry.userId ? { userId: entry.userId } : {},
|
|
158
|
+
declared: true
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
name,
|
|
162
|
+
storage: CRED_STORAGE_FILE,
|
|
163
|
+
credentialsPath: resolveEntryPath(dir, entry.credentials),
|
|
164
|
+
...entry.label ? { label: entry.label } : {},
|
|
165
|
+
...entry.userId ? { userId: entry.userId } : {},
|
|
166
|
+
declared: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
148
169
|
if (name === "DEFAULT") return {
|
|
149
170
|
name,
|
|
171
|
+
storage: CRED_STORAGE_FILE,
|
|
150
172
|
credentialsPath: credentialsPath(dir),
|
|
151
173
|
declared: false
|
|
152
174
|
};
|
|
@@ -171,23 +193,37 @@ const upsertProfile = (dir, name, entry) => {
|
|
|
171
193
|
if (read.kind === "unusable") throw new Error(`${read.reason}. Refusing to rewrite it, because doing so would discard the profiles it defines. Fix or delete the file, then re-run.`);
|
|
172
194
|
const file = read.kind === "ok" ? read.file : {
|
|
173
195
|
version: 1,
|
|
174
|
-
profiles: { [DEFAULT_PROFILE]: { credentials:
|
|
196
|
+
profiles: { [DEFAULT_PROFILE]: { credentials: storedPointer(path, credentialsPath(dir)) } }
|
|
175
197
|
};
|
|
176
198
|
file.profiles[name] = {
|
|
177
|
-
credentials:
|
|
199
|
+
credentials: storedPointer(path, entry.credentials),
|
|
178
200
|
...entry.label ? { label: entry.label } : {},
|
|
179
201
|
...entry.userId ? { userId: entry.userId } : {}
|
|
180
202
|
};
|
|
181
203
|
writeProfiles(path, file);
|
|
182
204
|
};
|
|
183
|
-
/**
|
|
184
|
-
* True when only `DEFAULT` is left, so `profiles.json` no longer earns its place. Mirrors
|
|
185
|
-
* lazy creation: a single-account install has no profiles file, before or after.
|
|
186
|
-
*/
|
|
187
205
|
const onlyDefaultRemains = (file) => {
|
|
188
206
|
const names = Object.keys(file.profiles);
|
|
189
207
|
return names.length === 0 || names.length === 1 && names[0] === "DEFAULT";
|
|
190
208
|
};
|
|
209
|
+
const canDropProfilesFile = (file) => {
|
|
210
|
+
if (!onlyDefaultRemains(file)) return false;
|
|
211
|
+
const remaining = file.profiles[DEFAULT_PROFILE];
|
|
212
|
+
return remaining === void 0 || !isKeyringPointer(remaining.credentials);
|
|
213
|
+
};
|
|
214
|
+
const locationForName = (dir, name) => locationOf(resolveProfile(dir, name));
|
|
215
|
+
const newProfileLocation = (dir, name, storage) => storage === "keyring" ? {
|
|
216
|
+
profile: name,
|
|
217
|
+
storage: CRED_STORAGE_KEYRING
|
|
218
|
+
} : {
|
|
219
|
+
profile: name,
|
|
220
|
+
storage: CRED_STORAGE_FILE,
|
|
221
|
+
path: newProfileCredentialsPath(dir, name)
|
|
222
|
+
};
|
|
223
|
+
const profilesUsingPath = (dir, path, except) => {
|
|
224
|
+
const resolved = resolve(path);
|
|
225
|
+
return listProfiles(dir).filter((profile) => profile.name !== except && profile.storage === "file" && resolve(profile.credentialsPath) === resolved).map((profile) => profile.name);
|
|
226
|
+
};
|
|
191
227
|
const listProfiles = (dir) => {
|
|
192
228
|
const read = inspectProfiles(dir);
|
|
193
229
|
if (read.kind === "unusable") throw new Error(`${read.reason}. Fix or delete the file — every named profile is defined in it.`);
|
|
@@ -203,10 +239,14 @@ const writeProfiles = (path, file) => {
|
|
|
203
239
|
const resolveEntryPath = (dir, entry) => isAbsolute(entry) ? entry : resolve(profilesDir(dir), entry);
|
|
204
240
|
/** `profiles.json` may sit in the legacy directory, so entries resolve against its own dir. */
|
|
205
241
|
const profilesDir = (dir) => resolve(profilesFilePath(dir), "..");
|
|
206
|
-
/**
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
242
|
+
/** A relative file named `keyring` would otherwise collide with the storage sentinel. */
|
|
243
|
+
const storedPointer = (profilesPath, credentials) => {
|
|
244
|
+
if (isKeyringPointer(credentials)) return KEYRING_CREDENTIALS;
|
|
245
|
+
const base = resolve(profilesPath, "..");
|
|
246
|
+
const abs = isAbsolute(credentials) ? credentials : resolve(base, credentials);
|
|
247
|
+
const rel = relative(base, abs);
|
|
248
|
+
const stored = rel && !isAbsolute(rel) ? rel : abs;
|
|
249
|
+
return stored === "keyring" ? `./${KEYRING_CREDENTIALS}` : stored;
|
|
210
250
|
};
|
|
211
251
|
function nonEmpty(value) {
|
|
212
252
|
if (typeof value !== "string") return void 0;
|
|
@@ -214,4 +254,4 @@ function nonEmpty(value) {
|
|
|
214
254
|
return trimmed === "" ? void 0 : trimmed;
|
|
215
255
|
}
|
|
216
256
|
//#endregion
|
|
217
|
-
export {
|
|
257
|
+
export { upsertProfile as _, canDropProfilesFile as a, writeSecretFile as b, listProfiles as c, newProfileLocation as d, profilesFilePath as f, selectProfileName as g, resolveProfile as h, assertValidProfileName as i, locationForName as l, readProfiles as m, KEYRING_CREDENTIALS as n, credentialsDisplay as o, profilesUsingPath as p, assertProfilesUsable as r, isKeyringPointer as s, DEFAULT_PROFILE as t, locationOf as u, CRED_STORAGE_FILE as v, CRED_STORAGE_KEYRING as y };
|
|
@@ -284,13 +284,14 @@ const parseStartupArgs = (argv) => {
|
|
|
284
284
|
const applyShort = (letter, attachedValue, flagDisplay) => {
|
|
285
285
|
const takesArg = SHORT_WITH_ARG.has(letter);
|
|
286
286
|
let value;
|
|
287
|
-
if (takesArg)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
if (takesArg) {
|
|
288
|
+
if (attachedValue !== void 0) value = attachedValue;
|
|
289
|
+
else {
|
|
290
|
+
const v = needValue(flagDisplay);
|
|
291
|
+
if (typeof v !== "string") return v;
|
|
292
|
+
value = v;
|
|
293
|
+
}
|
|
294
|
+
} else if (attachedValue !== void 0) return {
|
|
294
295
|
kind: "invalid-option",
|
|
295
296
|
message: `option does not take an argument -- ${letter}`
|
|
296
297
|
};
|
package/dist/analytics.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-D2vw1UOw.js";
|
|
2
2
|
import { getGithubEnvVars, isCi } from "./env.js";
|
|
3
3
|
import { log } from "./log.js";
|
|
4
4
|
import pkg_default from "./pkg.js";
|
|
5
5
|
import { getApiClient, isNeonApiError } from "./api.js";
|
|
6
|
-
import {
|
|
6
|
+
import { r as credentialsPath } from "./_chunks/paths-g3DRqJiD.js";
|
|
7
7
|
import "./config.js";
|
|
8
8
|
import { getAuthContext } from "./auth_context.js";
|
|
9
9
|
import { isCurrentBranchProbe } from "./context.js";
|
|
@@ -67,6 +67,7 @@ const storedCredentialAttribution = (storedUserId) => storedUserId ? {
|
|
|
67
67
|
const telemetryCredential = (authContext, apiKey, defaultCredentialsPath) => {
|
|
68
68
|
if (authContext === null) return { credentialsPath: defaultCredentialsPath };
|
|
69
69
|
if (authContext.source === "api-key") return { apiKey };
|
|
70
|
+
if (authContext.storage === "keyring") return { apiKey };
|
|
70
71
|
return {
|
|
71
72
|
apiKey,
|
|
72
73
|
credentialsPath: authContext.credentialsPath ?? defaultCredentialsPath
|
|
@@ -102,12 +103,25 @@ const analyticsMiddleware = async (args) => {
|
|
|
102
103
|
if (isCurrentBranchProbe(args)) return;
|
|
103
104
|
const { apiKey: keyToQuery, credentialsPath: fileToRead } = telemetryCredential(getAuthContext(), args.apiKey, credentialsPath(args.configDir));
|
|
104
105
|
if (fileToRead !== void 0) try {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const listing = storeFor(args.configDir).inspect({
|
|
107
|
+
profile: getAuthContext()?.profile ?? "DEFAULT",
|
|
108
|
+
storage: "file",
|
|
109
|
+
path: fileToRead
|
|
110
|
+
});
|
|
111
|
+
if (typeof listing.credentials?.user_id === "string") userId = listing.credentials.user_id;
|
|
112
|
+
else log.debug("No usable credentials at %s", fileToRead);
|
|
108
113
|
} catch (err) {
|
|
109
114
|
log.debug("Could not read %s: %s", fileToRead, err);
|
|
110
115
|
}
|
|
116
|
+
else if (getAuthContext()?.storage === "keyring") try {
|
|
117
|
+
const listing = storeFor(args.configDir).inspect({
|
|
118
|
+
profile: getAuthContext()?.profile ?? "DEFAULT",
|
|
119
|
+
storage: "keyring"
|
|
120
|
+
});
|
|
121
|
+
if (typeof listing.credentials?.user_id === "string") userId = listing.credentials.user_id;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log.debug("Could not read the OS keyring item: %s", err);
|
|
124
|
+
}
|
|
111
125
|
try {
|
|
112
126
|
if (keyToQuery) {
|
|
113
127
|
const apiClient = getApiClient({
|
package/dist/auth_context.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as isOwnedCredentialPath } from "./_chunks/paths-g3DRqJiD.js";
|
|
2
2
|
import "./config.js";
|
|
3
3
|
//#region src/auth_context.ts
|
|
4
|
+
const locationFromContext = (context) => {
|
|
5
|
+
if (context.profile === void 0) return null;
|
|
6
|
+
if (context.storage === "keyring") return {
|
|
7
|
+
profile: context.profile,
|
|
8
|
+
storage: "keyring"
|
|
9
|
+
};
|
|
10
|
+
if (context.credentialsPath === void 0) return null;
|
|
11
|
+
return {
|
|
12
|
+
profile: context.profile,
|
|
13
|
+
storage: "file",
|
|
14
|
+
path: context.credentialsPath
|
|
15
|
+
};
|
|
16
|
+
};
|
|
4
17
|
let current = null;
|
|
5
18
|
const setAuthContext = (context) => {
|
|
6
19
|
current = context;
|
|
@@ -10,27 +23,12 @@ const getAuthContext = () => current;
|
|
|
10
23
|
const clearAuthContext = () => {
|
|
11
24
|
current = null;
|
|
12
25
|
};
|
|
13
|
-
/**
|
|
14
|
-
* The credentials file a 401 should delete, or `null` to leave everything on disk.
|
|
15
|
-
*
|
|
16
|
-
* Only an expired OAuth token set is worth clearing: deleting it makes the next command log
|
|
17
|
-
* in again, which is the recovery. Neither key-shaped source is.
|
|
18
|
-
*
|
|
19
|
-
* A key passed on the command line was never ours to store, so a 401 on it says nothing about
|
|
20
|
-
* any stored credential — clearing one would sign the user out of an account the failed
|
|
21
|
-
* request never used.
|
|
22
|
-
*
|
|
23
|
-
* A key read from a profile must survive for a sharper reason: unlike an OAuth token there is
|
|
24
|
-
* nothing to refresh and no automatic way back, so deleting it would destroy the only copy of
|
|
25
|
-
* a credential the user has to paste or mint again. It is also the expected state during
|
|
26
|
-
* rotation, where the whole point is that the old key is dead and the file must still be there
|
|
27
|
-
* to be replaced.
|
|
28
|
-
*/
|
|
29
26
|
const credentialsToClearOn401 = (context) => {
|
|
30
27
|
if (context?.source !== "stored-credentials") return null;
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
|
|
28
|
+
const at = locationFromContext(context);
|
|
29
|
+
if (at === null) return null;
|
|
30
|
+
if (at.storage === "keyring") return null;
|
|
31
|
+
return isOwnedCredentialPath(context.configDir, at.path) ? at : null;
|
|
34
32
|
};
|
|
35
33
|
/**
|
|
36
34
|
* What to tell the user when the API rejects their credential, naming the profile and file
|
|
@@ -38,10 +36,13 @@ const credentialsToClearOn401 = (context) => {
|
|
|
38
36
|
*/
|
|
39
37
|
const authFailureMessage = (context) => {
|
|
40
38
|
const profile = context?.profile ?? "the selected profile";
|
|
41
|
-
const where = context?.credentialsPath !== void 0 ? ` (${context.credentialsPath})` : "";
|
|
42
|
-
if (context?.source === "profile-api-key") return `Authentication failed: the Neon API rejected profile "${profile}"'s API key${where}. Replace it with \`neon profile create ${profile} --mint
|
|
43
|
-
if (context?.source === "stored-credentials")
|
|
39
|
+
const where = context?.storage === "keyring" ? " (OS keyring)" : context?.credentialsPath !== void 0 ? ` (${context.credentialsPath})` : "";
|
|
40
|
+
if (context?.source === "profile-api-key") return `Authentication failed: the Neon API rejected profile "${profile}"'s API key${where}. Replace it with \`neon profile create ${profile} --mint\`, or store another with \`neon profile create ${profile} --api-key -\`.`;
|
|
41
|
+
if (context?.source === "stored-credentials") {
|
|
42
|
+
if (context.storage === "keyring") return `Authentication failed: the Neon API rejected profile "${profile}"'s stored session (OS keyring). Sign in again with \`neon auth --profile ${profile}\`.`;
|
|
43
|
+
return `Authentication failed: the Neon API rejected profile "${profile}"'s stored session${where}. That file was not created by neon, so it was left alone — sign in again with \`neon auth --profile ${profile}\`.`;
|
|
44
|
+
}
|
|
44
45
|
return "Authentication failed: the Neon API rejected the API key. Check --api-key or NEON_API_KEY.";
|
|
45
46
|
};
|
|
46
47
|
//#endregion
|
|
47
|
-
export { authFailureMessage, clearAuthContext, credentialsToClearOn401, getAuthContext, setAuthContext };
|
|
48
|
+
export { authFailureMessage, clearAuthContext, credentialsToClearOn401, getAuthContext, locationFromContext, setAuthContext };
|
package/dist/commands/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "../_chunks/rolldown-runtime-
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { getEndpoints, loadSpec } from "../utils/openapi.js";
|
|
3
3
|
import { writer } from "../writer.js";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "../_chunks/rolldown-runtime-
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { isNeonApiError } from "../api.js";
|
|
4
4
|
import { writer } from "../writer.js";
|