neon 2.47.0 → 3.1.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 +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +395 -0
- package/dist/_shared/env-core/reuse-secrets.js +179 -0
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -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 +575 -658
- 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 +642 -681
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -341
- 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 +614 -845
- 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 +313 -394
- package/dist/dev/functions.js +54 -64
- 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 +36 -38
- 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 +727 -983
- 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 +1293 -2082
- 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 +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -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 +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- 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 +626 -815
- 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 +1414 -1927
- 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 +2 -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 +133 -147
- 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 +37 -44
- 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 +9 -7
package/dist/commands/auth.js
CHANGED
|
@@ -1,346 +1,301 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { credentialInputs, displacedProfileWarning, selectCredential, } from "../_shared/auth_selection.js";
|
|
4
|
-
import { API_KEY, interpretCredentials, OAUTH, readCredentials, writeCredentials, } from "../_shared/credentials.js";
|
|
5
|
-
import { assertProfilesUsable, assertValidProfileName, DEFAULT_PROFILE, newProfileCredentialsPath, readProfiles, resolveProfile, selectProfileName, upsertProfile, } from "../_shared/profiles.js";
|
|
6
|
-
import { getApiClient } from "../api.js";
|
|
7
|
-
import { auth, refreshToken } from "../auth.js";
|
|
8
|
-
import { setAuthContext } from "../auth_context.js";
|
|
9
|
-
import { credentialsPath as defaultCredentialsPath } from "../config.js";
|
|
10
|
-
import { isConfigInit, isCurrentBranchProbe, isProfileCommand, } from "../context.js";
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { API_KEY, OAUTH, interpretCredentials, readCredentials, writeCredentials } from "../_shared/credentials.js";
|
|
11
3
|
import { isCi } from "../env.js";
|
|
12
4
|
import { log } from "../log.js";
|
|
5
|
+
import { getApiClient } from "../api.js";
|
|
6
|
+
import { credentialsPath } from "../_shared/paths.js";
|
|
7
|
+
import "../config.js";
|
|
8
|
+
import { setAuthContext } from "../auth_context.js";
|
|
9
|
+
import { isConfigInit, isCurrentBranchProbe, isProfileCommand } from "../context.js";
|
|
13
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
|
+
import { auth, refreshToken } from "../auth.js";
|
|
12
|
+
import { DEFAULT_PROFILE, assertProfilesUsable, assertValidProfileName, newProfileCredentialsPath, readProfiles, resolveProfile, selectProfileName, upsertProfile } from "../_shared/profiles.js";
|
|
13
|
+
import { credentialInputs, displacedProfileWarning, selectCredential } from "../_shared/auth_selection.js";
|
|
14
|
+
import { existsSync, rmSync } from "node:fs";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
//#region src/commands/auth.ts
|
|
17
|
+
var auth_exports = /* @__PURE__ */ __exportAll({
|
|
18
|
+
aliases: () => aliases,
|
|
19
|
+
authFlow: () => authFlow,
|
|
20
|
+
builder: () => builder,
|
|
21
|
+
command: () => command,
|
|
22
|
+
credentialsPathForName: () => credentialsPathForName,
|
|
23
|
+
deleteCredentialsAt: () => deleteCredentialsAt,
|
|
24
|
+
describe: () => describe,
|
|
25
|
+
ensureAuth: () => ensureAuth,
|
|
26
|
+
handler: () => handler,
|
|
27
|
+
usableCredential: () => usableCredential
|
|
28
|
+
});
|
|
14
29
|
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return resolveProfile(configDir, name).credentialsPath;
|
|
30
|
+
* The credentials file a named profile resolves to, falling back to plain `credentials.json`
|
|
31
|
+
* when the name is `DEFAULT` and nothing is declared. An unknown profile name is a hard error
|
|
32
|
+
* rather than a silent write to the default file — a typo must not authenticate the wrong
|
|
33
|
+
* account.
|
|
34
|
+
*/
|
|
35
|
+
const credentialsPathForName = (configDir, name) => {
|
|
36
|
+
if (name === "DEFAULT" && !readProfiles(configDir, log.warning)) return credentialsPath(configDir);
|
|
37
|
+
return resolveProfile(configDir, name).credentialsPath;
|
|
24
38
|
};
|
|
25
|
-
const credentialsPathFor = ({ configDir, profile
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export const handler = async (args) => {
|
|
33
|
-
await authFlow(args);
|
|
39
|
+
const credentialsPathFor = ({ configDir, profile }) => credentialsPathForName(configDir, selectProfileName(profile));
|
|
40
|
+
const command = "auth";
|
|
41
|
+
const aliases = ["login"];
|
|
42
|
+
const describe = "Authenticate";
|
|
43
|
+
const builder = (yargs) => yargs.option("context-file", { hidden: true });
|
|
44
|
+
const handler = async (args) => {
|
|
45
|
+
await authFlow(args);
|
|
34
46
|
};
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (isNamed) {
|
|
71
|
-
upsertProfile(configDir, profileName, {
|
|
72
|
-
credentials: credentialsPath,
|
|
73
|
-
...(identity.email ? { label: identity.email } : {}),
|
|
74
|
-
...(identity.id ? { userId: identity.id } : {}),
|
|
75
|
-
});
|
|
76
|
-
log.info('Saved profile "%s" (%s)', profileName, credentialsPath);
|
|
77
|
-
}
|
|
78
|
-
log.info("Auth complete");
|
|
79
|
-
return tokenSet.access_token || "";
|
|
47
|
+
const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "force-auth": forceAuthKebab, allowUnsafeTls, profile }) => {
|
|
48
|
+
if (!(forceAuth ?? forceAuthKebab) && isCi()) throw new Error("Cannot run interactive auth in CI");
|
|
49
|
+
const profileName = selectProfileName(profile);
|
|
50
|
+
const isNamed = profileName !== DEFAULT_PROFILE;
|
|
51
|
+
if (isNamed) assertValidProfileName(profileName);
|
|
52
|
+
assertProfilesUsable(configDir, profileName);
|
|
53
|
+
const tokenSet = await auth({
|
|
54
|
+
oauthHost,
|
|
55
|
+
clientId,
|
|
56
|
+
allowUnsafeTls
|
|
57
|
+
});
|
|
58
|
+
const credentialsPath = isNamed && !readProfiles(configDir, log.warning)?.profiles[profileName] ? newProfileCredentialsPath(configDir, profileName) : credentialsPathFor({
|
|
59
|
+
configDir,
|
|
60
|
+
profile
|
|
61
|
+
});
|
|
62
|
+
let identity = {};
|
|
63
|
+
try {
|
|
64
|
+
identity = await preserveCredentials(credentialsPath, tokenSet, getApiClient({
|
|
65
|
+
apiKey: tokenSet.access_token || "",
|
|
66
|
+
apiHost
|
|
67
|
+
}));
|
|
68
|
+
} catch {
|
|
69
|
+
log.error("Failed to save credentials");
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
if (isNamed) {
|
|
73
|
+
upsertProfile(configDir, profileName, {
|
|
74
|
+
credentials: credentialsPath,
|
|
75
|
+
...identity.email ? { label: identity.email } : {},
|
|
76
|
+
...identity.id ? { userId: identity.id } : {}
|
|
77
|
+
});
|
|
78
|
+
log.info("Saved profile \"%s\" (%s)", profileName, credentialsPath);
|
|
79
|
+
}
|
|
80
|
+
log.info("Auth complete");
|
|
81
|
+
return tokenSet.access_token || "";
|
|
80
82
|
};
|
|
81
83
|
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
* Persist the token set and return the account it belongs to, so a named profile can be
|
|
85
|
+
* labelled with an email. The credentials file records only `user_id` — a UUID with no
|
|
86
|
+
* email — which is why identifying a stored profile offline is otherwise impossible.
|
|
87
|
+
*/
|
|
86
88
|
const preserveCredentials = async (path, credentials, apiClient) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return { ...(id ? { id } : {}), ...(email ? { email } : {}) };
|
|
89
|
+
const { data: { id, email } } = await apiClient.getCurrentUserInfo();
|
|
90
|
+
const stored = {
|
|
91
|
+
...credentials,
|
|
92
|
+
type: OAUTH,
|
|
93
|
+
...id !== void 0 ? { user_id: id } : {}
|
|
94
|
+
};
|
|
95
|
+
writeCredentials(path, stored);
|
|
96
|
+
log.debug("Saved credentials to %s", path);
|
|
97
|
+
log.debug("Credentials MD5 hash: %s", md5hash(JSON.stringify(stored)));
|
|
98
|
+
return {
|
|
99
|
+
...id ? { id } : {},
|
|
100
|
+
...email ? { email } : {}
|
|
101
|
+
};
|
|
101
102
|
};
|
|
102
103
|
const handleExistingToken = async (tokenSet, props, credentialsPath) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
104
|
+
if (tokenSet.access_token && tokenSet.expires_at > Date.now()) {
|
|
105
|
+
log.debug("Using existing valid access_token");
|
|
106
|
+
const apiClient = getApiClient({
|
|
107
|
+
apiKey: tokenSet.access_token,
|
|
108
|
+
apiHost: props.apiHost
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
apiKey: tokenSet.access_token,
|
|
112
|
+
apiClient
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
log.debug(tokenSet.expires_at < Date.now() ? "Token is expired, attempting refresh" : "Token is missing access_token, attempting refresh");
|
|
116
|
+
if (!tokenSet.refresh_token) {
|
|
117
|
+
log.debug("TokenSet is missing refresh_token, starting authentication");
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const refreshedTokenSet = await refreshToken({
|
|
122
|
+
oauthHost: props.oauthHost,
|
|
123
|
+
clientId: props.clientId,
|
|
124
|
+
allowUnsafeTls: props.allowUnsafeTls
|
|
125
|
+
}, tokenSet);
|
|
126
|
+
const extendedTokenSet = extendTokenSet(refreshedTokenSet);
|
|
127
|
+
const apiKey = extendedTokenSet.access_token;
|
|
128
|
+
const apiClient = getApiClient({
|
|
129
|
+
apiKey,
|
|
130
|
+
apiHost: props.apiHost
|
|
131
|
+
});
|
|
132
|
+
await preserveCredentials(credentialsPath, extendedTokenSet, apiClient);
|
|
133
|
+
log.debug("Token refresh successful");
|
|
134
|
+
return {
|
|
135
|
+
apiKey,
|
|
136
|
+
apiClient
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const typedErr = err instanceof Error ? err : /* @__PURE__ */ new Error("Unknown error");
|
|
140
|
+
log.debug("Failed to refresh token: %s", typedErr.message);
|
|
141
|
+
throw new Error("AUTH_REFRESH_FAILED");
|
|
142
|
+
}
|
|
142
143
|
};
|
|
143
144
|
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
145
|
+
* The credential a stored file can authenticate with right now — its API key, or an OAuth
|
|
146
|
+
* access token, refreshed first when it has expired. Never launches a browser.
|
|
147
|
+
*
|
|
148
|
+
* The `profile` subcommands need exactly this. They deliberately skip `ensureAuth`, but
|
|
149
|
+
* `rotate-key` still has to call the API as the account it is rotating, and falling through to
|
|
150
|
+
* an interactive login would be authenticating something other than what the command is about.
|
|
151
|
+
*/
|
|
152
|
+
const usableCredential = async (props, at) => {
|
|
153
|
+
const credentialsPath = at.path;
|
|
154
|
+
const stored = readCredentials(at);
|
|
155
|
+
if (stored === null) return null;
|
|
156
|
+
const credential = interpretCredentials(stored, at);
|
|
157
|
+
if (credential.kind === "api_key") return {
|
|
158
|
+
apiKey: credential.apiKey,
|
|
159
|
+
kind: API_KEY
|
|
160
|
+
};
|
|
161
|
+
try {
|
|
162
|
+
const result = await handleExistingToken(stored, props, credentialsPath);
|
|
163
|
+
return result ? {
|
|
164
|
+
apiKey: result.apiKey,
|
|
165
|
+
kind: OAUTH
|
|
166
|
+
} : null;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
log.debug("Could not use the stored OAuth session at %s: %s", credentialsPath, err instanceof Error ? err.message : "unknown error");
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
170
171
|
};
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (err) {
|
|
279
|
-
if (!(err instanceof Error && err.message === "AUTH_REFRESH_FAILED")) {
|
|
280
|
-
throw err;
|
|
281
|
-
}
|
|
282
|
-
// A refresh that failed is recoverable by logging in again.
|
|
283
|
-
log.debug("Ensure auth failed, starting authentication", err);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
log.debug("No usable credentials at %s, starting authentication", credentialsPath);
|
|
288
|
-
}
|
|
289
|
-
// `dev` never launches the interactive browser flow. With no usable
|
|
290
|
-
// credentials it proceeds without an API client; env injection is skipped
|
|
291
|
-
// and the function still runs locally.
|
|
292
|
-
if (isLocalDev) {
|
|
293
|
-
log.debug("dev: no usable credentials; running without env injection");
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (isBootstrap) {
|
|
297
|
-
log.debug("bootstrap: no usable credentials; continuing without auth");
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (isInit) {
|
|
301
|
-
log.debug("init: skipping global auth; init manages its own auth flow");
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
// Start new auth flow if no valid token exists or refresh failed. Pass the resolved
|
|
305
|
-
// profile rather than the raw flag, so a name that came from `NEON_PROFILE` is written to
|
|
306
|
-
// its own file instead of overwriting `DEFAULT`'s.
|
|
307
|
-
const apiKey = await authFlow({ ...props, profile: selection.profile });
|
|
308
|
-
props.apiKey = apiKey;
|
|
309
|
-
props.apiClient = getApiClient({
|
|
310
|
-
apiKey,
|
|
311
|
-
apiHost: props.apiHost,
|
|
312
|
-
});
|
|
313
|
-
setAuthContext({
|
|
314
|
-
source: "stored-credentials",
|
|
315
|
-
configDir: props.configDir,
|
|
316
|
-
profile: selection.profile,
|
|
317
|
-
credentialsPath,
|
|
318
|
-
});
|
|
172
|
+
const ensureAuth = async (props) => {
|
|
173
|
+
if (props._.length === 0 || props.help) return;
|
|
174
|
+
if (isCurrentBranchProbe(props)) return;
|
|
175
|
+
if (isConfigInit(props)) return;
|
|
176
|
+
if (isProfileCommand(props)) return;
|
|
177
|
+
const isLocalDev = props._[0] === "dev";
|
|
178
|
+
const isBootstrap = props._[0] === "bootstrap";
|
|
179
|
+
const isInit = props._[0] === "init";
|
|
180
|
+
if (props._[0] === "auth") {
|
|
181
|
+
props.apiClient = getApiClient({
|
|
182
|
+
apiKey: props.apiKey,
|
|
183
|
+
apiHost: props.apiHost
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const selection = selectCredential({
|
|
188
|
+
...credentialInputs(),
|
|
189
|
+
profileFlag: props.profile
|
|
190
|
+
});
|
|
191
|
+
const displaced = displacedProfileWarning(selection);
|
|
192
|
+
if (displaced !== null) log.warning(displaced);
|
|
193
|
+
if (selection.source !== "profile") {
|
|
194
|
+
props.apiKey = selection.apiKey;
|
|
195
|
+
log.debug("Using an API key to authorize requests");
|
|
196
|
+
setAuthContext({
|
|
197
|
+
source: "api-key",
|
|
198
|
+
configDir: props.configDir
|
|
199
|
+
});
|
|
200
|
+
props.apiClient = getApiClient({
|
|
201
|
+
apiKey: props.apiKey,
|
|
202
|
+
apiHost: props.apiHost
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
props.apiKey = "";
|
|
207
|
+
const credentialsPath = credentialsPathForName(props.configDir, selection.profile);
|
|
208
|
+
const at = {
|
|
209
|
+
path: credentialsPath,
|
|
210
|
+
profile: selection.profile
|
|
211
|
+
};
|
|
212
|
+
const stored = readCredentials(at);
|
|
213
|
+
if (stored !== null) {
|
|
214
|
+
log.debug("Trying to read credentials from %s", credentialsPath);
|
|
215
|
+
const credential = interpretCredentials(stored, at);
|
|
216
|
+
if (credential.kind === "api_key") {
|
|
217
|
+
log.debug("Using profile \"%s\"'s API key to authorize requests", selection.profile);
|
|
218
|
+
props.apiKey = credential.apiKey;
|
|
219
|
+
setAuthContext({
|
|
220
|
+
source: "profile-api-key",
|
|
221
|
+
configDir: props.configDir,
|
|
222
|
+
profile: selection.profile,
|
|
223
|
+
credentialsPath
|
|
224
|
+
});
|
|
225
|
+
props.apiClient = getApiClient({
|
|
226
|
+
apiKey: credential.apiKey,
|
|
227
|
+
apiHost: props.apiHost
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const result = await handleExistingToken(stored, props, credentialsPath);
|
|
233
|
+
if (result) {
|
|
234
|
+
props.apiKey = result.apiKey;
|
|
235
|
+
props.apiClient = result.apiClient;
|
|
236
|
+
setAuthContext({
|
|
237
|
+
source: "stored-credentials",
|
|
238
|
+
configDir: props.configDir,
|
|
239
|
+
profile: selection.profile,
|
|
240
|
+
credentialsPath
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
} catch (err) {
|
|
245
|
+
if (!(err instanceof Error && err.message === "AUTH_REFRESH_FAILED")) throw err;
|
|
246
|
+
log.debug("Ensure auth failed, starting authentication", err);
|
|
247
|
+
}
|
|
248
|
+
} else log.debug("No usable credentials at %s, starting authentication", credentialsPath);
|
|
249
|
+
if (isLocalDev) {
|
|
250
|
+
log.debug("dev: no usable credentials; running without env injection");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (isBootstrap) {
|
|
254
|
+
log.debug("bootstrap: no usable credentials; continuing without auth");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (isInit) {
|
|
258
|
+
log.debug("init: skipping global auth; init manages its own auth flow");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const apiKey = await authFlow({
|
|
262
|
+
...props,
|
|
263
|
+
profile: selection.profile
|
|
264
|
+
});
|
|
265
|
+
props.apiKey = apiKey;
|
|
266
|
+
props.apiClient = getApiClient({
|
|
267
|
+
apiKey,
|
|
268
|
+
apiHost: props.apiHost
|
|
269
|
+
});
|
|
270
|
+
setAuthContext({
|
|
271
|
+
source: "stored-credentials",
|
|
272
|
+
configDir: props.configDir,
|
|
273
|
+
profile: selection.profile,
|
|
274
|
+
credentialsPath
|
|
275
|
+
});
|
|
319
276
|
};
|
|
320
277
|
/**
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const typedErr = err instanceof Error ? err : new Error("Unknown error");
|
|
342
|
-
log.error("Failed to delete credentials: %s", typedErr.message);
|
|
343
|
-
throw new Error("CREDENTIALS_DELETE_FAILED");
|
|
344
|
-
}
|
|
278
|
+
* Delete one credentials file — used by the 401 handler to clear an OAuth token set the API
|
|
279
|
+
* has rejected, so the next command logs in again instead of failing the same way.
|
|
280
|
+
*
|
|
281
|
+
* It takes the exact path rather than a directory and an optional profile. Deriving the path
|
|
282
|
+
* here meant re-running selection from partial state, and the 401 handler has no parsed
|
|
283
|
+
* arguments: it passed only the config directory, so a rejected token on a
|
|
284
|
+
* `--profile`-selected account deleted whatever `DEFAULT` pointed at — signing the user out
|
|
285
|
+
* of an account whose credentials the failed request had never touched.
|
|
286
|
+
*/
|
|
287
|
+
const deleteCredentialsAt = (credentialsPath) => {
|
|
288
|
+
try {
|
|
289
|
+
if (existsSync(credentialsPath)) {
|
|
290
|
+
rmSync(credentialsPath);
|
|
291
|
+
log.info("Deleted credentials from %s", credentialsPath);
|
|
292
|
+
} else log.debug("Credentials file %s does not exist", credentialsPath);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const typedErr = err instanceof Error ? err : /* @__PURE__ */ new Error("Unknown error");
|
|
295
|
+
log.error("Failed to delete credentials: %s", typedErr.message);
|
|
296
|
+
throw new Error("CREDENTIALS_DELETE_FAILED");
|
|
297
|
+
}
|
|
345
298
|
};
|
|
346
299
|
const md5hash = (s) => createHash("md5").update(s).digest("hex");
|
|
300
|
+
//#endregion
|
|
301
|
+
export { aliases, authFlow, auth_exports, builder, command, credentialsPathForName, deleteCredentialsAt, describe, ensureAuth, handler, usableCredential };
|