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
|
@@ -1,155 +1,154 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { writer } from '../writer.js';
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { codeFromBody, isNeonApiError, retryOnLock } from "../api.js";
|
|
3
|
+
import { NeonAuthEmailVerificationMethod, NeonAuthOauthProviderId, NeonAuthOauthProviderType, NeonAuthSupportedAuthProvider, } from "../utils/api_enums.js";
|
|
4
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
5
|
+
import { writer } from "../writer.js";
|
|
7
6
|
// Shared styled output helpers
|
|
8
7
|
const printKvBlock = (title, entries) => {
|
|
9
8
|
process.stdout.write(`\n${chalk.green(title)}\n`);
|
|
10
9
|
for (const [key, value] of entries) {
|
|
11
|
-
process.stdout.write(` ${chalk.green(key)} ${value ??
|
|
10
|
+
process.stdout.write(` ${chalk.green(key)} ${value ?? ""}\n`);
|
|
12
11
|
}
|
|
13
|
-
process.stdout.write(
|
|
12
|
+
process.stdout.write("\n");
|
|
14
13
|
};
|
|
15
14
|
const printMessage = (message) => {
|
|
16
15
|
process.stdout.write(`\n${chalk.green(message)}\n\n`);
|
|
17
16
|
};
|
|
18
17
|
const INTEGRATION_RESPONSE_FIELDS = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
"auth_provider",
|
|
19
|
+
"db_name",
|
|
20
|
+
"base_url",
|
|
21
|
+
"schema_name",
|
|
22
|
+
"table_name",
|
|
23
|
+
"jwks_url",
|
|
25
24
|
];
|
|
26
25
|
const INTEGRATION_STATUS_FIELDS = [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
"auth_provider",
|
|
27
|
+
"branch_id",
|
|
28
|
+
"db_name",
|
|
29
|
+
"base_url",
|
|
30
|
+
"created_at",
|
|
31
|
+
"jwks_url",
|
|
33
32
|
];
|
|
34
|
-
const OAUTH_PROVIDER_FIELDS = [
|
|
35
|
-
const ALLOW_LOCALHOST_FIELDS = [
|
|
33
|
+
const OAUTH_PROVIDER_FIELDS = ["id", "type", "client_id"];
|
|
34
|
+
const ALLOW_LOCALHOST_FIELDS = ["allow_localhost"];
|
|
36
35
|
const SUPPORTED_OAUTH_PROVIDERS = [
|
|
37
36
|
NeonAuthOauthProviderId.Google,
|
|
38
37
|
NeonAuthOauthProviderId.Github,
|
|
39
38
|
NeonAuthOauthProviderId.Vercel,
|
|
40
39
|
];
|
|
41
|
-
const DOMAIN_FIELDS = [
|
|
40
|
+
const DOMAIN_FIELDS = ["domain"];
|
|
42
41
|
const EMAIL_PASSWORD_FIELDS = [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
"enabled",
|
|
43
|
+
"email_verification_method",
|
|
44
|
+
"require_email_verification",
|
|
45
|
+
"auto_sign_in_after_verification",
|
|
46
|
+
"send_verification_email_on_sign_up",
|
|
47
|
+
"send_verification_email_on_sign_in",
|
|
48
|
+
"disable_sign_up",
|
|
50
49
|
];
|
|
51
50
|
const EMAIL_PROVIDER_FIELDS = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
"type",
|
|
52
|
+
"host",
|
|
53
|
+
"port",
|
|
54
|
+
"username",
|
|
55
|
+
"sender_email",
|
|
56
|
+
"sender_name",
|
|
58
57
|
];
|
|
59
58
|
const ORGANIZATION_FIELDS = [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
"enabled",
|
|
60
|
+
"organization_limit",
|
|
61
|
+
"creator_role",
|
|
63
62
|
];
|
|
64
63
|
const WEBHOOK_FIELDS = [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
"enabled",
|
|
65
|
+
"webhook_url",
|
|
66
|
+
"enabled_events",
|
|
67
|
+
"timeout_seconds",
|
|
69
68
|
];
|
|
70
|
-
const TEST_EMAIL_FIELDS = [
|
|
71
|
-
export const command =
|
|
72
|
-
export const describe =
|
|
69
|
+
const TEST_EMAIL_FIELDS = ["success", "error_message"];
|
|
70
|
+
export const command = "neon-auth";
|
|
71
|
+
export const describe = "Manage Neon Auth";
|
|
73
72
|
export const builder = (argv) => {
|
|
74
73
|
return argv
|
|
75
|
-
.usage(
|
|
74
|
+
.usage("$0 neon-auth <sub-command> [options]")
|
|
76
75
|
.options({
|
|
77
|
-
|
|
78
|
-
describe:
|
|
79
|
-
type:
|
|
76
|
+
"project-id": {
|
|
77
|
+
describe: "Project ID",
|
|
78
|
+
type: "string",
|
|
80
79
|
},
|
|
81
80
|
branch: {
|
|
82
|
-
describe:
|
|
83
|
-
type:
|
|
81
|
+
describe: "Branch ID or name",
|
|
82
|
+
type: "string",
|
|
84
83
|
},
|
|
85
84
|
})
|
|
86
85
|
.middleware(fillSingleProject)
|
|
87
|
-
.command(
|
|
88
|
-
|
|
89
|
-
describe:
|
|
90
|
-
type:
|
|
86
|
+
.command("enable", "Enable Neon Auth on a branch", (yargs) => yargs.options({
|
|
87
|
+
"database-name": {
|
|
88
|
+
describe: "Database name to use for auth data",
|
|
89
|
+
type: "string",
|
|
91
90
|
},
|
|
92
91
|
}), async (args) => {
|
|
93
92
|
await enable(args);
|
|
94
93
|
})
|
|
95
|
-
.command(
|
|
94
|
+
.command("status", "Get Neon Auth status for a branch", (yargs) => yargs, async (args) => {
|
|
96
95
|
await status(args);
|
|
97
96
|
})
|
|
98
|
-
.command(
|
|
99
|
-
|
|
100
|
-
describe:
|
|
101
|
-
type:
|
|
97
|
+
.command("disable", "Disable Neon Auth on a branch", (yargs) => yargs.options({
|
|
98
|
+
"delete-data": {
|
|
99
|
+
describe: "Permanently delete all Neon Auth data and schema from the database",
|
|
100
|
+
type: "boolean",
|
|
102
101
|
default: false,
|
|
103
102
|
},
|
|
104
103
|
}), async (args) => {
|
|
105
104
|
await disable(args);
|
|
106
105
|
})
|
|
107
|
-
.command(
|
|
106
|
+
.command("oauth-provider", "Manage OAuth providers", (yargs) => {
|
|
108
107
|
return yargs
|
|
109
|
-
.usage(
|
|
110
|
-
.command(
|
|
108
|
+
.usage("$0 neon-auth oauth-provider <sub-command> [options]")
|
|
109
|
+
.command("list", "List OAuth providers", (yargs) => yargs, async (args) => {
|
|
111
110
|
await oauthProviderList(args);
|
|
112
111
|
})
|
|
113
|
-
.command(
|
|
114
|
-
|
|
115
|
-
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(
|
|
116
|
-
type:
|
|
112
|
+
.command("add", "Add an OAuth provider", (yargs) => yargs.options({
|
|
113
|
+
"provider-id": {
|
|
114
|
+
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(", ")}`,
|
|
115
|
+
type: "string",
|
|
117
116
|
choices: SUPPORTED_OAUTH_PROVIDERS,
|
|
118
117
|
demandOption: true,
|
|
119
118
|
},
|
|
120
|
-
|
|
119
|
+
"oauth-client-id": {
|
|
121
120
|
describe: "OAuth client ID from your provider app. Omit to use Neon's shared OAuth app.",
|
|
122
|
-
type:
|
|
121
|
+
type: "string",
|
|
123
122
|
},
|
|
124
|
-
|
|
123
|
+
"oauth-client-secret": {
|
|
125
124
|
describe: "OAuth client secret from your provider app. Omit to use Neon's shared OAuth app.",
|
|
126
|
-
type:
|
|
125
|
+
type: "string",
|
|
127
126
|
},
|
|
128
127
|
}), async (args) => {
|
|
129
128
|
await oauthProviderAdd(args);
|
|
130
129
|
})
|
|
131
|
-
.command(
|
|
132
|
-
|
|
133
|
-
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(
|
|
134
|
-
type:
|
|
130
|
+
.command("update", "Update an OAuth provider", (yargs) => yargs.options({
|
|
131
|
+
"provider-id": {
|
|
132
|
+
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(", ")}`,
|
|
133
|
+
type: "string",
|
|
135
134
|
choices: SUPPORTED_OAUTH_PROVIDERS,
|
|
136
135
|
demandOption: true,
|
|
137
136
|
},
|
|
138
|
-
|
|
137
|
+
"oauth-client-id": {
|
|
139
138
|
describe: "OAuth client ID from your provider app. Omit to use Neon's shared OAuth app.",
|
|
140
|
-
type:
|
|
139
|
+
type: "string",
|
|
141
140
|
},
|
|
142
|
-
|
|
141
|
+
"oauth-client-secret": {
|
|
143
142
|
describe: "OAuth client secret from your provider app. Omit to use Neon's shared OAuth app.",
|
|
144
|
-
type:
|
|
143
|
+
type: "string",
|
|
145
144
|
},
|
|
146
145
|
}), async (args) => {
|
|
147
146
|
await oauthProviderUpdate(args);
|
|
148
147
|
})
|
|
149
|
-
.command(
|
|
150
|
-
|
|
151
|
-
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(
|
|
152
|
-
type:
|
|
148
|
+
.command("delete", "Delete an OAuth provider", (yargs) => yargs.options({
|
|
149
|
+
"provider-id": {
|
|
150
|
+
describe: `OAuth provider ID. Supported values: ${SUPPORTED_OAUTH_PROVIDERS.join(", ")}`,
|
|
151
|
+
type: "string",
|
|
153
152
|
choices: SUPPORTED_OAUTH_PROVIDERS,
|
|
154
153
|
demandOption: true,
|
|
155
154
|
},
|
|
@@ -157,277 +156,283 @@ export const builder = (argv) => {
|
|
|
157
156
|
await oauthProviderDelete(args);
|
|
158
157
|
});
|
|
159
158
|
})
|
|
160
|
-
.command(
|
|
159
|
+
.command("domain", "Manage redirect URI trusted domains", (yargs) => {
|
|
161
160
|
return yargs
|
|
162
|
-
.usage(
|
|
163
|
-
.command(
|
|
161
|
+
.usage("$0 neon-auth domain <sub-command> [options]")
|
|
162
|
+
.command("list", "List trusted domains", (yargs) => yargs, async (args) => {
|
|
164
163
|
await domainList(args);
|
|
165
164
|
})
|
|
166
|
-
.command(
|
|
167
|
-
.usage(
|
|
168
|
-
.positional(
|
|
169
|
-
describe:
|
|
170
|
-
type:
|
|
165
|
+
.command("add <domain>", "Add a trusted domain", (yargs) => yargs
|
|
166
|
+
.usage("$0 neon-auth domain add <domain> [options]")
|
|
167
|
+
.positional("domain", {
|
|
168
|
+
describe: "Domain to add",
|
|
169
|
+
type: "string",
|
|
171
170
|
demandOption: true,
|
|
172
171
|
}), async (args) => {
|
|
173
172
|
await domainAdd(args);
|
|
174
173
|
})
|
|
175
|
-
.command(
|
|
176
|
-
.usage(
|
|
177
|
-
.positional(
|
|
178
|
-
describe:
|
|
179
|
-
type:
|
|
174
|
+
.command("delete <domain>", "Delete a trusted domain", (yargs) => yargs
|
|
175
|
+
.usage("$0 neon-auth domain delete <domain> [options]")
|
|
176
|
+
.positional("domain", {
|
|
177
|
+
describe: "Domain to delete",
|
|
178
|
+
type: "string",
|
|
180
179
|
demandOption: true,
|
|
181
180
|
}), async (args) => {
|
|
182
181
|
await domainDelete(args);
|
|
183
182
|
})
|
|
184
|
-
.command(
|
|
185
|
-
.usage(
|
|
186
|
-
.command(
|
|
183
|
+
.command("allow-localhost", "Manage localhost connection settings", (yargs) => yargs
|
|
184
|
+
.usage("$0 neon-auth domain allow-localhost <sub-command> [options]")
|
|
185
|
+
.command("get", "Get localhost connection setting", (yargs) => yargs, async (args) => {
|
|
187
186
|
await allowLocalhostGet(args);
|
|
188
187
|
})
|
|
189
|
-
.command(
|
|
188
|
+
.command("enable", "Allow localhost connections", (yargs) => yargs, async (args) => {
|
|
190
189
|
await allowLocalhostEnable(args);
|
|
191
190
|
})
|
|
192
|
-
.command(
|
|
191
|
+
.command("disable", "Restrict localhost connections", (yargs) => yargs, async (args) => {
|
|
193
192
|
await allowLocalhostDisable(args);
|
|
194
193
|
}));
|
|
195
194
|
})
|
|
196
|
-
.command(
|
|
195
|
+
.command("config", "Manage Neon Auth configuration", (yargs) => {
|
|
197
196
|
return yargs
|
|
198
|
-
.usage(
|
|
199
|
-
.command(
|
|
197
|
+
.usage("$0 neon-auth config <sub-command> [options]")
|
|
198
|
+
.command("email-password", "Manage email and password authentication settings", (yargs) => {
|
|
200
199
|
return yargs
|
|
201
|
-
.usage(
|
|
202
|
-
.command(
|
|
200
|
+
.usage("$0 neon-auth config email-password <sub-command> [options]")
|
|
201
|
+
.command("get", "Get email and password config", (yargs) => yargs, async (args) => {
|
|
203
202
|
await emailPasswordGet(args);
|
|
204
203
|
})
|
|
205
|
-
.command(
|
|
204
|
+
.command("update", "Update email and password config", (yargs) => yargs.options({
|
|
206
205
|
enabled: {
|
|
207
|
-
describe:
|
|
208
|
-
type:
|
|
206
|
+
describe: "Enable email and password authentication",
|
|
207
|
+
type: "boolean",
|
|
209
208
|
},
|
|
210
|
-
|
|
211
|
-
describe:
|
|
212
|
-
type:
|
|
209
|
+
"email-verification-method": {
|
|
210
|
+
describe: "Email verification method",
|
|
211
|
+
type: "string",
|
|
213
212
|
choices: Object.values(NeonAuthEmailVerificationMethod),
|
|
214
213
|
},
|
|
215
|
-
|
|
216
|
-
describe:
|
|
217
|
-
type:
|
|
214
|
+
"require-email-verification": {
|
|
215
|
+
describe: "Require email verification before users can sign in",
|
|
216
|
+
type: "boolean",
|
|
218
217
|
},
|
|
219
|
-
|
|
220
|
-
describe:
|
|
221
|
-
type:
|
|
218
|
+
"auto-sign-in-after-verification": {
|
|
219
|
+
describe: "Auto sign in users after verifying their email",
|
|
220
|
+
type: "boolean",
|
|
222
221
|
},
|
|
223
|
-
|
|
224
|
-
describe:
|
|
225
|
-
type:
|
|
222
|
+
"send-verification-email-on-sign-up": {
|
|
223
|
+
describe: "Send verification email on sign up",
|
|
224
|
+
type: "boolean",
|
|
226
225
|
},
|
|
227
|
-
|
|
228
|
-
describe:
|
|
229
|
-
type:
|
|
226
|
+
"send-verification-email-on-sign-in": {
|
|
227
|
+
describe: "Send verification email on sign in",
|
|
228
|
+
type: "boolean",
|
|
230
229
|
},
|
|
231
|
-
|
|
232
|
-
describe:
|
|
233
|
-
type:
|
|
230
|
+
"disable-sign-up": {
|
|
231
|
+
describe: "Disable new user sign ups",
|
|
232
|
+
type: "boolean",
|
|
234
233
|
},
|
|
235
234
|
}), async (args) => {
|
|
236
235
|
await emailPasswordUpdate(args);
|
|
237
236
|
});
|
|
238
237
|
})
|
|
239
|
-
.command(
|
|
238
|
+
.command("email-provider", "Manage email provider configuration", (yargs) => {
|
|
240
239
|
return yargs
|
|
241
|
-
.usage(
|
|
242
|
-
.command(
|
|
240
|
+
.usage("$0 neon-auth config email-provider <sub-command> [options]")
|
|
241
|
+
.command("get", "Get email provider config", (yargs) => yargs, async (args) => {
|
|
243
242
|
await emailProviderGet(args);
|
|
244
243
|
})
|
|
245
|
-
.command(
|
|
244
|
+
.command("update", "Update email provider config", (yargs) => yargs.options({
|
|
246
245
|
type: {
|
|
247
|
-
describe:
|
|
248
|
-
type:
|
|
249
|
-
choices: [
|
|
246
|
+
describe: "Email provider type",
|
|
247
|
+
type: "string",
|
|
248
|
+
choices: [
|
|
249
|
+
"standard",
|
|
250
|
+
"shared",
|
|
251
|
+
],
|
|
250
252
|
demandOption: true,
|
|
251
253
|
},
|
|
252
254
|
host: {
|
|
253
|
-
describe:
|
|
254
|
-
type:
|
|
255
|
+
describe: "SMTP host (required for standard)",
|
|
256
|
+
type: "string",
|
|
255
257
|
},
|
|
256
258
|
port: {
|
|
257
|
-
describe:
|
|
258
|
-
type:
|
|
259
|
+
describe: "SMTP port (required for standard)",
|
|
260
|
+
type: "number",
|
|
259
261
|
},
|
|
260
262
|
username: {
|
|
261
|
-
describe:
|
|
262
|
-
type:
|
|
263
|
+
describe: "SMTP username (required for standard)",
|
|
264
|
+
type: "string",
|
|
263
265
|
},
|
|
264
266
|
password: {
|
|
265
|
-
describe:
|
|
266
|
-
type:
|
|
267
|
+
describe: "SMTP password (required for standard)",
|
|
268
|
+
type: "string",
|
|
267
269
|
},
|
|
268
|
-
|
|
269
|
-
describe:
|
|
270
|
-
type:
|
|
270
|
+
"sender-email": {
|
|
271
|
+
describe: "Sender email address",
|
|
272
|
+
type: "string",
|
|
271
273
|
},
|
|
272
|
-
|
|
273
|
-
describe:
|
|
274
|
-
type:
|
|
274
|
+
"sender-name": {
|
|
275
|
+
describe: "Sender display name",
|
|
276
|
+
type: "string",
|
|
275
277
|
},
|
|
276
278
|
}), async (args) => {
|
|
277
279
|
await emailProviderUpdate(args);
|
|
278
280
|
})
|
|
279
|
-
.command(
|
|
280
|
-
|
|
281
|
-
describe:
|
|
282
|
-
type:
|
|
281
|
+
.command("test", "Send a test email", (yargs) => yargs.options({
|
|
282
|
+
"recipient-email": {
|
|
283
|
+
describe: "Email address to send test email to",
|
|
284
|
+
type: "string",
|
|
283
285
|
demandOption: true,
|
|
284
286
|
},
|
|
285
287
|
host: {
|
|
286
|
-
describe:
|
|
287
|
-
type:
|
|
288
|
+
describe: "SMTP host",
|
|
289
|
+
type: "string",
|
|
288
290
|
demandOption: true,
|
|
289
291
|
},
|
|
290
292
|
port: {
|
|
291
|
-
describe:
|
|
292
|
-
type:
|
|
293
|
+
describe: "SMTP port",
|
|
294
|
+
type: "number",
|
|
293
295
|
demandOption: true,
|
|
294
296
|
},
|
|
295
297
|
username: {
|
|
296
|
-
describe:
|
|
297
|
-
type:
|
|
298
|
+
describe: "SMTP username",
|
|
299
|
+
type: "string",
|
|
298
300
|
demandOption: true,
|
|
299
301
|
},
|
|
300
302
|
password: {
|
|
301
|
-
describe:
|
|
302
|
-
type:
|
|
303
|
+
describe: "SMTP password",
|
|
304
|
+
type: "string",
|
|
303
305
|
demandOption: true,
|
|
304
306
|
},
|
|
305
|
-
|
|
306
|
-
describe:
|
|
307
|
-
type:
|
|
307
|
+
"sender-email": {
|
|
308
|
+
describe: "Sender email address",
|
|
309
|
+
type: "string",
|
|
308
310
|
demandOption: true,
|
|
309
311
|
},
|
|
310
|
-
|
|
311
|
-
describe:
|
|
312
|
-
type:
|
|
312
|
+
"sender-name": {
|
|
313
|
+
describe: "Sender display name",
|
|
314
|
+
type: "string",
|
|
313
315
|
demandOption: true,
|
|
314
316
|
},
|
|
315
317
|
}), async (args) => {
|
|
316
318
|
await emailProviderTest(args);
|
|
317
319
|
});
|
|
318
320
|
})
|
|
319
|
-
.command(
|
|
321
|
+
.command("organization", "Manage organization plugin settings", (yargs) => {
|
|
320
322
|
return yargs
|
|
321
|
-
.usage(
|
|
322
|
-
.command(
|
|
323
|
+
.usage("$0 neon-auth config organization <sub-command> [options]")
|
|
324
|
+
.command("get", "Get organization plugin config", (yargs) => yargs, async (args) => {
|
|
323
325
|
await organizationGet(args);
|
|
324
326
|
})
|
|
325
|
-
.command(
|
|
327
|
+
.command("update", "Update organization plugin config", (yargs) => yargs.options({
|
|
326
328
|
enabled: {
|
|
327
|
-
describe:
|
|
328
|
-
type:
|
|
329
|
+
describe: "Enable the organization plugin",
|
|
330
|
+
type: "boolean",
|
|
329
331
|
},
|
|
330
332
|
limit: {
|
|
331
|
-
describe:
|
|
332
|
-
type:
|
|
333
|
+
describe: "Maximum number of organizations a user can create",
|
|
334
|
+
type: "number",
|
|
333
335
|
},
|
|
334
|
-
|
|
335
|
-
describe:
|
|
336
|
-
type:
|
|
337
|
-
choices: [
|
|
336
|
+
"creator-role": {
|
|
337
|
+
describe: "Role assigned to organization creator",
|
|
338
|
+
type: "string",
|
|
339
|
+
choices: [
|
|
340
|
+
"admin",
|
|
341
|
+
"owner",
|
|
342
|
+
],
|
|
338
343
|
},
|
|
339
344
|
}), async (args) => {
|
|
340
345
|
await organizationUpdate(args);
|
|
341
346
|
});
|
|
342
347
|
})
|
|
343
|
-
.command(
|
|
348
|
+
.command("webhook", "Manage webhook configuration", (yargs) => {
|
|
344
349
|
return yargs
|
|
345
|
-
.usage(
|
|
346
|
-
.command(
|
|
350
|
+
.usage("$0 neon-auth config webhook <sub-command> [options]")
|
|
351
|
+
.command("get", "Get webhook config", (yargs) => yargs, async (args) => {
|
|
347
352
|
await webhookGet(args);
|
|
348
353
|
})
|
|
349
|
-
.command(
|
|
354
|
+
.command("update", "Update webhook config", (yargs) => yargs.options({
|
|
350
355
|
enabled: {
|
|
351
|
-
describe:
|
|
352
|
-
type:
|
|
356
|
+
describe: "Enable webhooks",
|
|
357
|
+
type: "boolean",
|
|
353
358
|
demandOption: true,
|
|
354
359
|
},
|
|
355
360
|
url: {
|
|
356
|
-
describe:
|
|
357
|
-
type:
|
|
361
|
+
describe: "Webhook endpoint URL",
|
|
362
|
+
type: "string",
|
|
358
363
|
},
|
|
359
|
-
|
|
360
|
-
describe:
|
|
361
|
-
type:
|
|
364
|
+
"enabled-events": {
|
|
365
|
+
describe: "Events to enable",
|
|
366
|
+
type: "string",
|
|
362
367
|
choices: [
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
368
|
+
"user.before_create",
|
|
369
|
+
"user.created",
|
|
370
|
+
"send.otp",
|
|
371
|
+
"send.magic_link",
|
|
367
372
|
],
|
|
368
373
|
array: true,
|
|
369
374
|
},
|
|
370
375
|
timeout: {
|
|
371
|
-
describe:
|
|
372
|
-
type:
|
|
376
|
+
describe: "Webhook timeout in seconds (1-10)",
|
|
377
|
+
type: "number",
|
|
373
378
|
},
|
|
374
379
|
}), async (args) => {
|
|
375
380
|
await webhookUpdate(args);
|
|
376
381
|
});
|
|
377
382
|
});
|
|
378
383
|
})
|
|
379
|
-
.command(
|
|
384
|
+
.command("plugins", "View Neon Auth plugin configurations", (yargs) => {
|
|
380
385
|
return yargs
|
|
381
|
-
.usage(
|
|
382
|
-
.command(
|
|
386
|
+
.usage("$0 neon-auth plugins <sub-command> [options]")
|
|
387
|
+
.command("list", "List all plugin configurations", (yargs) => yargs, async (args) => {
|
|
383
388
|
await pluginsList(args);
|
|
384
389
|
})
|
|
385
|
-
.command(
|
|
386
|
-
.usage(
|
|
387
|
-
.positional(
|
|
388
|
-
describe:
|
|
389
|
-
type:
|
|
390
|
+
.command("get <plugin-name>", "Get a specific plugin configuration", (yargs) => yargs
|
|
391
|
+
.usage("$0 neon-auth plugins get <plugin-name> [options]")
|
|
392
|
+
.positional("plugin-name", {
|
|
393
|
+
describe: "Plugin name (e.g. organization, email_provider, email_and_password, oauth_providers, allow_localhost)",
|
|
394
|
+
type: "string",
|
|
390
395
|
demandOption: true,
|
|
391
396
|
}), async (args) => {
|
|
392
397
|
await pluginsGet(args);
|
|
393
398
|
});
|
|
394
399
|
})
|
|
395
|
-
.command(
|
|
400
|
+
.command("user", "Manage Neon Auth users", (yargs) => {
|
|
396
401
|
return yargs
|
|
397
|
-
.usage(
|
|
398
|
-
.command(
|
|
402
|
+
.usage("$0 neon-auth user <sub-command> [options]")
|
|
403
|
+
.command("create", "Create an auth user", (yargs) => yargs.options({
|
|
399
404
|
email: {
|
|
400
|
-
describe:
|
|
401
|
-
type:
|
|
405
|
+
describe: "User email address",
|
|
406
|
+
type: "string",
|
|
402
407
|
demandOption: true,
|
|
403
408
|
},
|
|
404
409
|
name: {
|
|
405
|
-
describe:
|
|
406
|
-
type:
|
|
410
|
+
describe: "User display name (defaults to email if not provided)",
|
|
411
|
+
type: "string",
|
|
407
412
|
},
|
|
408
413
|
}), async (args) => {
|
|
409
414
|
await userCreate(args);
|
|
410
415
|
})
|
|
411
|
-
.command(
|
|
412
|
-
.usage(
|
|
413
|
-
.positional(
|
|
414
|
-
describe:
|
|
415
|
-
type:
|
|
416
|
+
.command("delete <user-id>", "Delete an auth user", (yargs) => yargs
|
|
417
|
+
.usage("$0 neon-auth user delete <user-id> [options]")
|
|
418
|
+
.positional("user-id", {
|
|
419
|
+
describe: "ID of the user to delete",
|
|
420
|
+
type: "string",
|
|
416
421
|
demandOption: true,
|
|
417
422
|
}), async (args) => {
|
|
418
423
|
await userDelete(args);
|
|
419
424
|
})
|
|
420
|
-
.command(
|
|
421
|
-
.usage(
|
|
422
|
-
.positional(
|
|
423
|
-
describe:
|
|
424
|
-
type:
|
|
425
|
+
.command("set-role <user-id>", "Set roles for an auth user", (yargs) => yargs
|
|
426
|
+
.usage("$0 neon-auth user set-role <user-id> [options]")
|
|
427
|
+
.positional("user-id", {
|
|
428
|
+
describe: "ID of the user to update",
|
|
429
|
+
type: "string",
|
|
425
430
|
demandOption: true,
|
|
426
431
|
})
|
|
427
432
|
.options({
|
|
428
433
|
roles: {
|
|
429
|
-
describe:
|
|
430
|
-
type:
|
|
434
|
+
describe: "Roles to assign",
|
|
435
|
+
type: "string",
|
|
431
436
|
array: true,
|
|
432
437
|
demandOption: true,
|
|
433
438
|
},
|
|
@@ -453,7 +458,7 @@ const enable = async (props) => {
|
|
|
453
458
|
})));
|
|
454
459
|
}
|
|
455
460
|
catch (err) {
|
|
456
|
-
if (
|
|
461
|
+
if (isNeonApiError(err) && err.status === 409) {
|
|
457
462
|
alreadyEnabled = true;
|
|
458
463
|
({ data } = await props.apiClient.getNeonAuth(props.projectId, branchId));
|
|
459
464
|
}
|
|
@@ -461,27 +466,29 @@ const enable = async (props) => {
|
|
|
461
466
|
throw err;
|
|
462
467
|
}
|
|
463
468
|
}
|
|
464
|
-
if (props.output ===
|
|
469
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
465
470
|
writer(props).end(data, { fields: INTEGRATION_RESPONSE_FIELDS });
|
|
466
471
|
return;
|
|
467
472
|
}
|
|
468
473
|
// Access type-specific fields loosely — CREATE response has schema/table,
|
|
469
474
|
// GET response (already-enabled path) has db_name instead.
|
|
470
475
|
const d = data;
|
|
471
|
-
printKvBlock(alreadyEnabled ?
|
|
472
|
-
[
|
|
473
|
-
...(d.db_name
|
|
474
|
-
|
|
476
|
+
printKvBlock(alreadyEnabled ? "Neon Auth is already enabled" : "Neon Auth enabled", [
|
|
477
|
+
["Auth Provider:", data.auth_provider],
|
|
478
|
+
...(d.db_name
|
|
479
|
+
? [["Database: ", d.db_name]]
|
|
480
|
+
: []),
|
|
481
|
+
["Base URL: ", data.base_url],
|
|
475
482
|
...(d.schema_name
|
|
476
|
-
? [[
|
|
483
|
+
? [["Schema Name: ", d.schema_name]]
|
|
477
484
|
: []),
|
|
478
485
|
...(d.table_name
|
|
479
|
-
? [[
|
|
486
|
+
? [["Table Name: ", d.table_name]]
|
|
480
487
|
: []),
|
|
481
|
-
[
|
|
488
|
+
["JWKS URL: ", data.jwks_url],
|
|
482
489
|
]);
|
|
483
490
|
if (data.base_url) {
|
|
484
|
-
process.stdout.write(` ${chalk.green(
|
|
491
|
+
process.stdout.write(` ${chalk.green("Set this environment variable in your application:")}\n`);
|
|
485
492
|
process.stdout.write(` NEON_AUTH_BASE_URL=${data.base_url}\n\n`);
|
|
486
493
|
}
|
|
487
494
|
};
|
|
@@ -492,23 +499,23 @@ const status = async (props) => {
|
|
|
492
499
|
({ data } = await props.apiClient.getNeonAuth(props.projectId, branchId));
|
|
493
500
|
}
|
|
494
501
|
catch (err) {
|
|
495
|
-
if (
|
|
496
|
-
printMessage(
|
|
502
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
503
|
+
printMessage("Neon Auth is not configured for this branch");
|
|
497
504
|
return;
|
|
498
505
|
}
|
|
499
506
|
throw err;
|
|
500
507
|
}
|
|
501
|
-
if (props.output ===
|
|
508
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
502
509
|
writer(props).end(data, { fields: INTEGRATION_STATUS_FIELDS });
|
|
503
510
|
return;
|
|
504
511
|
}
|
|
505
|
-
printKvBlock(
|
|
506
|
-
[
|
|
507
|
-
[
|
|
508
|
-
[
|
|
509
|
-
[
|
|
510
|
-
[
|
|
511
|
-
[
|
|
512
|
+
printKvBlock("Neon Auth status", [
|
|
513
|
+
["Auth Provider:", data.auth_provider],
|
|
514
|
+
["Branch ID: ", data.branch_id],
|
|
515
|
+
["Database: ", data.db_name],
|
|
516
|
+
["Base URL: ", data.base_url],
|
|
517
|
+
["Created At: ", data.created_at],
|
|
518
|
+
["JWKS URL: ", data.jwks_url],
|
|
512
519
|
]);
|
|
513
520
|
};
|
|
514
521
|
const disable = async (props) => {
|
|
@@ -516,23 +523,23 @@ const disable = async (props) => {
|
|
|
516
523
|
await retryOnLock(() => props.apiClient.disableNeonAuth(props.projectId, branchId, {
|
|
517
524
|
delete_data: props.deleteData,
|
|
518
525
|
}));
|
|
519
|
-
printMessage(
|
|
526
|
+
printMessage("Neon Auth has been disabled");
|
|
520
527
|
};
|
|
521
528
|
// --- OAuth provider ---
|
|
522
|
-
const SHARED_PROVIDER_DISCLAIMER =
|
|
523
|
-
|
|
524
|
-
|
|
529
|
+
const SHARED_PROVIDER_DISCLAIMER = "Shared keys are created by the Neon team for development only " +
|
|
530
|
+
"and should not be used for production apps. It helps you get started, " +
|
|
531
|
+
"but will show Neon branding (logo and name) on the OAuth consent screen.";
|
|
525
532
|
const oauthProviderList = async (props) => {
|
|
526
533
|
const branchId = await resolveBranch(props);
|
|
527
534
|
const { data } = await props.apiClient.listBranchNeonAuthOauthProviders(props.projectId, branchId);
|
|
528
|
-
if (data.providers.length === 0 && props.output ===
|
|
529
|
-
printMessage(
|
|
535
|
+
if (data.providers.length === 0 && props.output === "table") {
|
|
536
|
+
printMessage("No OAuth providers are configured for this branch.");
|
|
530
537
|
return;
|
|
531
538
|
}
|
|
532
539
|
writer(props).end(data.providers, { fields: OAUTH_PROVIDER_FIELDS });
|
|
533
540
|
const hasShared = data.providers.some((p) => p.type === NeonAuthOauthProviderType.Shared);
|
|
534
|
-
if (hasShared && props.output ===
|
|
535
|
-
process.stdout.write(`\n${chalk.yellow(
|
|
541
|
+
if (hasShared && props.output === "table") {
|
|
542
|
+
process.stdout.write(`\n${chalk.yellow("Caution:")} ${SHARED_PROVIDER_DISCLAIMER}\n\n`);
|
|
536
543
|
}
|
|
537
544
|
};
|
|
538
545
|
const oauthProviderAdd = async (props) => {
|
|
@@ -546,24 +553,23 @@ const oauthProviderAdd = async (props) => {
|
|
|
546
553
|
}));
|
|
547
554
|
}
|
|
548
555
|
catch (err) {
|
|
549
|
-
if (
|
|
550
|
-
err.
|
|
551
|
-
'INVALID_SHARED_OAUTH_PROVIDER') {
|
|
556
|
+
if (isNeonApiError(err) &&
|
|
557
|
+
codeFromBody(err.data) === "INVALID_SHARED_OAUTH_PROVIDER") {
|
|
552
558
|
throw new Error(`The "${props.providerId}" provider requires your own OAuth app credentials.\n` +
|
|
553
559
|
`Re-run with --oauth-client-id and --oauth-client-secret to provide them.\n` +
|
|
554
560
|
`Create an OAuth app at your provider and use those credentials.`);
|
|
555
561
|
}
|
|
556
562
|
throw err;
|
|
557
563
|
}
|
|
558
|
-
if (props.output ===
|
|
564
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
559
565
|
writer(props).end(data, { fields: OAUTH_PROVIDER_FIELDS });
|
|
560
566
|
}
|
|
561
567
|
else {
|
|
562
|
-
printKvBlock(
|
|
563
|
-
[
|
|
564
|
-
[
|
|
568
|
+
printKvBlock("OAuth provider added", [
|
|
569
|
+
["ID: ", data.id],
|
|
570
|
+
["Type: ", data.type],
|
|
565
571
|
...(data.client_id
|
|
566
|
-
? [[
|
|
572
|
+
? [["Client ID: ", data.client_id]]
|
|
567
573
|
: []),
|
|
568
574
|
]);
|
|
569
575
|
}
|
|
@@ -579,24 +585,23 @@ const oauthProviderUpdate = async (props) => {
|
|
|
579
585
|
}));
|
|
580
586
|
}
|
|
581
587
|
catch (err) {
|
|
582
|
-
if (
|
|
583
|
-
err.
|
|
584
|
-
'INVALID_SHARED_OAUTH_PROVIDER') {
|
|
588
|
+
if (isNeonApiError(err) &&
|
|
589
|
+
codeFromBody(err.data) === "INVALID_SHARED_OAUTH_PROVIDER") {
|
|
585
590
|
throw new Error(`The "${props.providerId}" provider requires your own OAuth app credentials.\n` +
|
|
586
591
|
`Re-run with --oauth-client-id and --oauth-client-secret to provide them.\n` +
|
|
587
592
|
`Create an OAuth app at your provider and use those credentials.`);
|
|
588
593
|
}
|
|
589
594
|
throw err;
|
|
590
595
|
}
|
|
591
|
-
if (props.output ===
|
|
596
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
592
597
|
writer(props).end(data, { fields: OAUTH_PROVIDER_FIELDS });
|
|
593
598
|
}
|
|
594
599
|
else {
|
|
595
|
-
printKvBlock(
|
|
596
|
-
[
|
|
597
|
-
[
|
|
600
|
+
printKvBlock("OAuth provider updated", [
|
|
601
|
+
["ID: ", data.id],
|
|
602
|
+
["Type: ", data.type],
|
|
598
603
|
...(data.client_id
|
|
599
|
-
? [[
|
|
604
|
+
? [["Client ID: ", data.client_id]]
|
|
600
605
|
: []),
|
|
601
606
|
]);
|
|
602
607
|
}
|
|
@@ -604,23 +609,23 @@ const oauthProviderUpdate = async (props) => {
|
|
|
604
609
|
};
|
|
605
610
|
const CALLBACK_INSTRUCTIONS = {
|
|
606
611
|
github: {
|
|
607
|
-
lead:
|
|
608
|
-
urlLabel:
|
|
612
|
+
lead: "Create an OAuth app in the GitHub Developer Portal and add the following authorization callback URL:",
|
|
613
|
+
urlLabel: "callback/github",
|
|
609
614
|
},
|
|
610
615
|
vercel: {
|
|
611
|
-
lead:
|
|
612
|
-
urlLabel:
|
|
616
|
+
lead: "Create a Vercel App in your Vercel Dashboard and add the following authorization callback URL:",
|
|
617
|
+
urlLabel: "callback/vercel",
|
|
613
618
|
},
|
|
614
619
|
google: {
|
|
615
|
-
lead:
|
|
616
|
-
urlLabel:
|
|
620
|
+
lead: "Get Google credentials by creating an OAuth client in Google Cloud Console > Credentials, and add the following authorized redirect URL:",
|
|
621
|
+
urlLabel: "callback/google",
|
|
617
622
|
},
|
|
618
623
|
};
|
|
619
624
|
const printCallbackInstructions = async (props, branchId, providerId) => {
|
|
620
625
|
const instructions = CALLBACK_INSTRUCTIONS[providerId];
|
|
621
626
|
if (!instructions)
|
|
622
627
|
return;
|
|
623
|
-
if (props.output ===
|
|
628
|
+
if (props.output === "json" || props.output === "yaml")
|
|
624
629
|
return;
|
|
625
630
|
let baseUrl;
|
|
626
631
|
try {
|
|
@@ -632,8 +637,8 @@ const printCallbackInstructions = async (props, branchId, providerId) => {
|
|
|
632
637
|
}
|
|
633
638
|
if (!baseUrl)
|
|
634
639
|
return;
|
|
635
|
-
const callbackUrl = `${baseUrl.replace(/\/$/,
|
|
636
|
-
printKvBlock(instructions.lead, [[
|
|
640
|
+
const callbackUrl = `${baseUrl.replace(/\/$/, "")}/${instructions.urlLabel}`;
|
|
641
|
+
printKvBlock(instructions.lead, [["URL: ", callbackUrl]]);
|
|
637
642
|
};
|
|
638
643
|
const oauthProviderDelete = async (props) => {
|
|
639
644
|
const branchId = await resolveBranch(props);
|
|
@@ -644,8 +649,8 @@ const oauthProviderDelete = async (props) => {
|
|
|
644
649
|
const domainList = async (props) => {
|
|
645
650
|
const branchId = await resolveBranch(props);
|
|
646
651
|
const { data } = await props.apiClient.listBranchNeonAuthTrustedDomains(props.projectId, branchId);
|
|
647
|
-
if (data.domains.length === 0 && props.output ===
|
|
648
|
-
printMessage(
|
|
652
|
+
if (data.domains.length === 0 && props.output === "table") {
|
|
653
|
+
printMessage("No trusted domains are configured for this branch.");
|
|
649
654
|
return;
|
|
650
655
|
}
|
|
651
656
|
writer(props).end(data.domains, { fields: DOMAIN_FIELDS });
|
|
@@ -658,7 +663,7 @@ const validateDomainUri = (domain) => {
|
|
|
658
663
|
catch {
|
|
659
664
|
throw new Error(`Invalid domain URI "${domain}". Must be a full URI including scheme, e.g. https://${domain}`);
|
|
660
665
|
}
|
|
661
|
-
if (![
|
|
666
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
662
667
|
throw new Error(`Invalid domain URI "${domain}". Must use http or https scheme, e.g. https://${url.host}`);
|
|
663
668
|
}
|
|
664
669
|
};
|
|
@@ -688,12 +693,12 @@ const domainDelete = async (props) => {
|
|
|
688
693
|
const allowLocalhostGet = async (props) => {
|
|
689
694
|
const branchId = await resolveBranch(props);
|
|
690
695
|
const { data } = await props.apiClient.getNeonAuthAllowLocalhost(props.projectId, branchId);
|
|
691
|
-
if (props.output ===
|
|
696
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
692
697
|
writer(props).end(data, { fields: ALLOW_LOCALHOST_FIELDS });
|
|
693
698
|
return;
|
|
694
699
|
}
|
|
695
|
-
printKvBlock(
|
|
696
|
-
[
|
|
700
|
+
printKvBlock("Localhost connection settings", [
|
|
701
|
+
["Allow localhost:", String(data.allow_localhost)],
|
|
697
702
|
]);
|
|
698
703
|
};
|
|
699
704
|
const allowLocalhostEnable = async (props) => {
|
|
@@ -701,42 +706,42 @@ const allowLocalhostEnable = async (props) => {
|
|
|
701
706
|
await props.apiClient.updateNeonAuthAllowLocalhost(props.projectId, branchId, {
|
|
702
707
|
allow_localhost: true,
|
|
703
708
|
});
|
|
704
|
-
printMessage(
|
|
709
|
+
printMessage("Localhost connections allowed");
|
|
705
710
|
};
|
|
706
711
|
const allowLocalhostDisable = async (props) => {
|
|
707
712
|
const branchId = await resolveBranch(props);
|
|
708
713
|
await props.apiClient.updateNeonAuthAllowLocalhost(props.projectId, branchId, {
|
|
709
714
|
allow_localhost: false,
|
|
710
715
|
});
|
|
711
|
-
printMessage(
|
|
716
|
+
printMessage("Localhost connections restricted");
|
|
712
717
|
};
|
|
713
718
|
// --- Email and password ---
|
|
714
719
|
const printEmailPasswordEntries = (data) => [
|
|
715
|
-
[
|
|
716
|
-
[
|
|
717
|
-
[
|
|
720
|
+
["Enabled: ", String(data.enabled)],
|
|
721
|
+
["Verification Method: ", data.email_verification_method],
|
|
722
|
+
["Require Verification: ", String(data.require_email_verification)],
|
|
718
723
|
[
|
|
719
|
-
|
|
724
|
+
"Auto Sign In After Verify: ",
|
|
720
725
|
String(data.auto_sign_in_after_verification),
|
|
721
726
|
],
|
|
722
727
|
[
|
|
723
|
-
|
|
728
|
+
"Send Email On Sign Up: ",
|
|
724
729
|
String(data.send_verification_email_on_sign_up),
|
|
725
730
|
],
|
|
726
731
|
[
|
|
727
|
-
|
|
732
|
+
"Send Email On Sign In: ",
|
|
728
733
|
String(data.send_verification_email_on_sign_in),
|
|
729
734
|
],
|
|
730
|
-
[
|
|
735
|
+
["Disable Sign Up: ", String(data.disable_sign_up)],
|
|
731
736
|
];
|
|
732
737
|
const emailPasswordGet = async (props) => {
|
|
733
738
|
const branchId = await resolveBranch(props);
|
|
734
739
|
const { data } = await props.apiClient.getNeonAuthEmailAndPasswordConfig(props.projectId, branchId);
|
|
735
|
-
if (props.output ===
|
|
740
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
736
741
|
writer(props).end(data, { fields: EMAIL_PASSWORD_FIELDS });
|
|
737
742
|
return;
|
|
738
743
|
}
|
|
739
|
-
printKvBlock(
|
|
744
|
+
printKvBlock("Email & password auth configuration", printEmailPasswordEntries(data));
|
|
740
745
|
};
|
|
741
746
|
const emailPasswordUpdate = async (props) => {
|
|
742
747
|
const branchId = await resolveBranch(props);
|
|
@@ -749,45 +754,50 @@ const emailPasswordUpdate = async (props) => {
|
|
|
749
754
|
send_verification_email_on_sign_in: props.sendVerificationEmailOnSignIn,
|
|
750
755
|
disable_sign_up: props.disableSignUp,
|
|
751
756
|
});
|
|
752
|
-
if (props.output ===
|
|
757
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
753
758
|
writer(props).end(data, { fields: EMAIL_PASSWORD_FIELDS });
|
|
754
759
|
return;
|
|
755
760
|
}
|
|
756
|
-
printKvBlock(
|
|
761
|
+
printKvBlock("Email & password auth configuration updated", printEmailPasswordEntries(data));
|
|
757
762
|
};
|
|
758
763
|
// --- Email provider ---
|
|
759
764
|
const printEmailProviderEntries = (data) => [
|
|
760
|
-
[
|
|
761
|
-
...(data.type ===
|
|
765
|
+
["Type: ", data.type],
|
|
766
|
+
...(data.type === "standard"
|
|
762
767
|
? [
|
|
763
|
-
[
|
|
764
|
-
[
|
|
765
|
-
|
|
768
|
+
["Host: ", data.host],
|
|
769
|
+
[
|
|
770
|
+
"Port: ",
|
|
771
|
+
data.port != null ? String(data.port) : undefined,
|
|
772
|
+
],
|
|
773
|
+
["Username: ", data.username],
|
|
766
774
|
]
|
|
767
775
|
: []),
|
|
768
|
-
[
|
|
769
|
-
[
|
|
776
|
+
["Sender Email: ", data.sender_email],
|
|
777
|
+
["Sender Name: ", data.sender_name],
|
|
770
778
|
];
|
|
771
779
|
const emailProviderGet = async (props) => {
|
|
772
780
|
const branchId = await resolveBranch(props);
|
|
773
781
|
const { data } = await props.apiClient.getNeonAuthEmailProvider(props.projectId, branchId);
|
|
774
|
-
if (props.output ===
|
|
775
|
-
writer(props).end(data, {
|
|
782
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
783
|
+
writer(props).end(data, {
|
|
784
|
+
fields: EMAIL_PROVIDER_FIELDS,
|
|
785
|
+
});
|
|
776
786
|
return;
|
|
777
787
|
}
|
|
778
|
-
printKvBlock(
|
|
788
|
+
printKvBlock("Email provider configuration", printEmailProviderEntries(data));
|
|
779
789
|
};
|
|
780
790
|
const emailProviderUpdate = async (props) => {
|
|
781
|
-
if (props.type ===
|
|
791
|
+
if (props.type === "standard" &&
|
|
782
792
|
(!props.host || !props.port || !props.username || !props.password)) {
|
|
783
|
-
throw new Error(
|
|
793
|
+
throw new Error("--host, --port, --username, and --password are required for standard email provider");
|
|
784
794
|
}
|
|
785
|
-
const warnSharedSender = props.type ===
|
|
795
|
+
const warnSharedSender = props.type === "shared" && (props.senderEmail || props.senderName);
|
|
786
796
|
const branchId = await resolveBranch(props);
|
|
787
797
|
let config;
|
|
788
|
-
if (props.type ===
|
|
798
|
+
if (props.type === "standard") {
|
|
789
799
|
config = {
|
|
790
|
-
type:
|
|
800
|
+
type: "standard",
|
|
791
801
|
host: props.host,
|
|
792
802
|
port: props.port,
|
|
793
803
|
username: props.username,
|
|
@@ -798,18 +808,20 @@ const emailProviderUpdate = async (props) => {
|
|
|
798
808
|
}
|
|
799
809
|
else {
|
|
800
810
|
config = {
|
|
801
|
-
type:
|
|
811
|
+
type: "shared",
|
|
802
812
|
};
|
|
803
813
|
}
|
|
804
814
|
const { data } = await props.apiClient.updateNeonAuthEmailProvider(props.projectId, branchId, config);
|
|
805
|
-
if (props.output ===
|
|
806
|
-
writer(props).end(data, {
|
|
815
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
816
|
+
writer(props).end(data, {
|
|
817
|
+
fields: EMAIL_PROVIDER_FIELDS,
|
|
818
|
+
});
|
|
807
819
|
}
|
|
808
820
|
else {
|
|
809
|
-
printKvBlock(
|
|
821
|
+
printKvBlock("Email provider configuration updated", printEmailProviderEntries(data));
|
|
810
822
|
}
|
|
811
823
|
if (warnSharedSender) {
|
|
812
|
-
process.stderr.write(`${chalk.yellow(
|
|
824
|
+
process.stderr.write(`${chalk.yellow("Warning:")} --sender-email and --sender-name are ignored for the shared email provider. ` +
|
|
813
825
|
`These values only take effect with --type standard.\n\n`);
|
|
814
826
|
}
|
|
815
827
|
};
|
|
@@ -824,39 +836,41 @@ const emailProviderTest = async (props) => {
|
|
|
824
836
|
sender_email: props.senderEmail,
|
|
825
837
|
sender_name: props.senderName,
|
|
826
838
|
});
|
|
827
|
-
if (props.output ===
|
|
839
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
828
840
|
writer(props).end(data, { fields: TEST_EMAIL_FIELDS });
|
|
829
841
|
}
|
|
830
842
|
else if (data.success) {
|
|
831
|
-
printMessage(
|
|
843
|
+
printMessage("Test email sent successfully");
|
|
832
844
|
}
|
|
833
845
|
else {
|
|
834
|
-
process.stdout.write(`\n${chalk.red(
|
|
846
|
+
process.stdout.write(`\n${chalk.red("Test email failed")}\n ${data.error_message ?? "Unknown error"}\n\n`);
|
|
835
847
|
}
|
|
836
848
|
};
|
|
837
849
|
// --- Organization plugin ---
|
|
838
850
|
const printOrganizationEntries = (data) => [
|
|
839
|
-
[
|
|
840
|
-
[
|
|
841
|
-
[
|
|
851
|
+
["Enabled: ", String(data.enabled)],
|
|
852
|
+
["Org Limit: ", String(data.organization_limit)],
|
|
853
|
+
["Creator Role: ", data.creator_role],
|
|
842
854
|
];
|
|
843
855
|
const organizationGet = async (props) => {
|
|
844
856
|
const branchId = await resolveBranch(props);
|
|
845
857
|
const { data } = await props.apiClient.getNeonAuthPluginConfigs(props.projectId, branchId);
|
|
846
858
|
const org = data.organization;
|
|
847
859
|
if (!org) {
|
|
848
|
-
if (props.output ===
|
|
849
|
-
writer(props).end({}, {
|
|
860
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
861
|
+
writer(props).end({}, {
|
|
862
|
+
fields: ORGANIZATION_FIELDS,
|
|
863
|
+
});
|
|
850
864
|
return;
|
|
851
865
|
}
|
|
852
|
-
printMessage(
|
|
866
|
+
printMessage("No organization plugin config found.");
|
|
853
867
|
return;
|
|
854
868
|
}
|
|
855
|
-
if (props.output ===
|
|
869
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
856
870
|
writer(props).end(org, { fields: ORGANIZATION_FIELDS });
|
|
857
871
|
return;
|
|
858
872
|
}
|
|
859
|
-
printKvBlock(
|
|
873
|
+
printKvBlock("Organization configuration", printOrganizationEntries(org));
|
|
860
874
|
};
|
|
861
875
|
const organizationUpdate = async (props) => {
|
|
862
876
|
const branchId = await resolveBranch(props);
|
|
@@ -865,30 +879,30 @@ const organizationUpdate = async (props) => {
|
|
|
865
879
|
organization_limit: props.limit,
|
|
866
880
|
creator_role: props.creatorRole,
|
|
867
881
|
});
|
|
868
|
-
if (props.output ===
|
|
882
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
869
883
|
writer(props).end(data, { fields: ORGANIZATION_FIELDS });
|
|
870
884
|
return;
|
|
871
885
|
}
|
|
872
|
-
printKvBlock(
|
|
886
|
+
printKvBlock("Organization configuration updated", printOrganizationEntries(data));
|
|
873
887
|
};
|
|
874
888
|
// --- Webhook ---
|
|
875
889
|
const printWebhookEntries = (data) => [
|
|
876
|
-
[
|
|
877
|
-
[
|
|
878
|
-
[
|
|
890
|
+
["Enabled: ", String(data.enabled)],
|
|
891
|
+
["URL: ", data.webhook_url ?? ""],
|
|
892
|
+
["Events: ", (data.enabled_events ?? []).join(", ")],
|
|
879
893
|
[
|
|
880
|
-
|
|
881
|
-
data.timeout_seconds != null ? String(data.timeout_seconds) :
|
|
894
|
+
"Timeout (sec): ",
|
|
895
|
+
data.timeout_seconds != null ? String(data.timeout_seconds) : "",
|
|
882
896
|
],
|
|
883
897
|
];
|
|
884
898
|
const webhookGet = async (props) => {
|
|
885
899
|
const branchId = await resolveBranch(props);
|
|
886
900
|
const { data } = await props.apiClient.getNeonAuthWebhookConfig(props.projectId, branchId);
|
|
887
|
-
if (props.output ===
|
|
901
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
888
902
|
writer(props).end(data, { fields: WEBHOOK_FIELDS });
|
|
889
903
|
return;
|
|
890
904
|
}
|
|
891
|
-
printKvBlock(
|
|
905
|
+
printKvBlock("Webhook configuration", printWebhookEntries(data));
|
|
892
906
|
};
|
|
893
907
|
const webhookUpdate = async (props) => {
|
|
894
908
|
const branchId = await resolveBranch(props);
|
|
@@ -898,28 +912,28 @@ const webhookUpdate = async (props) => {
|
|
|
898
912
|
enabled_events: props.enabledEvents,
|
|
899
913
|
timeout_seconds: props.timeout,
|
|
900
914
|
});
|
|
901
|
-
if (props.output ===
|
|
915
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
902
916
|
writer(props).end(data, { fields: WEBHOOK_FIELDS });
|
|
903
917
|
return;
|
|
904
918
|
}
|
|
905
|
-
printKvBlock(
|
|
919
|
+
printKvBlock("Webhook configuration updated", printWebhookEntries(data));
|
|
906
920
|
};
|
|
907
921
|
// --- Plugins ---
|
|
908
|
-
const pluginTitle = (name) => name.replace(/_/g,
|
|
909
|
-
|
|
922
|
+
const pluginTitle = (name) => name.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()) +
|
|
923
|
+
" configuration";
|
|
910
924
|
const formatValue = (v) => {
|
|
911
925
|
if (v == null)
|
|
912
|
-
return
|
|
913
|
-
if (typeof v ===
|
|
926
|
+
return "";
|
|
927
|
+
if (typeof v === "string")
|
|
914
928
|
return v;
|
|
915
|
-
if (typeof v ===
|
|
929
|
+
if (typeof v === "number" || typeof v === "boolean")
|
|
916
930
|
return String(v);
|
|
917
931
|
return JSON.stringify(v);
|
|
918
932
|
};
|
|
919
933
|
const pluginsList = async (props) => {
|
|
920
934
|
const branchId = await resolveBranch(props);
|
|
921
935
|
const { data } = await props.apiClient.getNeonAuthPluginConfigs(props.projectId, branchId);
|
|
922
|
-
if (props.output ===
|
|
936
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
923
937
|
writer(props).end(data, {
|
|
924
938
|
fields: Object.keys(data),
|
|
925
939
|
});
|
|
@@ -927,59 +941,60 @@ const pluginsList = async (props) => {
|
|
|
927
941
|
}
|
|
928
942
|
const summarize = (value) => {
|
|
929
943
|
if (value == null)
|
|
930
|
-
return
|
|
931
|
-
if (typeof value ===
|
|
944
|
+
return "not configured";
|
|
945
|
+
if (typeof value === "boolean")
|
|
932
946
|
return String(value);
|
|
933
|
-
if (typeof value ===
|
|
947
|
+
if (typeof value === "string")
|
|
934
948
|
return value;
|
|
935
|
-
if (typeof value ===
|
|
949
|
+
if (typeof value === "number")
|
|
936
950
|
return String(value);
|
|
937
951
|
if (Array.isArray(value))
|
|
938
|
-
return value.length === 1
|
|
939
|
-
|
|
952
|
+
return value.length === 1
|
|
953
|
+
? "1 item"
|
|
954
|
+
: `${String(value.length)} items`;
|
|
955
|
+
if (typeof value === "object") {
|
|
940
956
|
const obj = value;
|
|
941
|
-
if (
|
|
942
|
-
return obj.enabled ?
|
|
943
|
-
if (
|
|
957
|
+
if ("enabled" in obj)
|
|
958
|
+
return obj.enabled ? "enabled" : "disabled";
|
|
959
|
+
if ("type" in obj)
|
|
944
960
|
return formatValue(obj.type);
|
|
945
961
|
}
|
|
946
962
|
return JSON.stringify(value);
|
|
947
963
|
};
|
|
948
964
|
const entries = Object.entries(data).map(([key, value]) => [key.padEnd(24), summarize(value)]);
|
|
949
|
-
printKvBlock(
|
|
965
|
+
printKvBlock("Neon Auth plugins", entries);
|
|
950
966
|
};
|
|
951
967
|
const pluginsGet = async (props) => {
|
|
952
968
|
const branchId = await resolveBranch(props);
|
|
953
969
|
const { data } = await props.apiClient.getNeonAuthPluginConfigs(props.projectId, branchId);
|
|
954
970
|
const plugin = data[props.pluginName];
|
|
955
971
|
if (plugin === undefined) {
|
|
956
|
-
const available = Object.keys(data).join(
|
|
972
|
+
const available = Object.keys(data).join(", ");
|
|
957
973
|
throw new Error(`Unknown plugin "${props.pluginName}". Available plugins: ${available}`);
|
|
958
974
|
}
|
|
959
|
-
if (props.output ===
|
|
960
|
-
const fields = typeof plugin ===
|
|
975
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
976
|
+
const fields = typeof plugin === "object" && !Array.isArray(plugin)
|
|
961
977
|
? Object.keys(plugin)
|
|
962
978
|
: [];
|
|
963
979
|
writer(props).end(plugin, { fields: fields });
|
|
964
980
|
return;
|
|
965
981
|
}
|
|
966
|
-
if (typeof plugin ===
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
]);
|
|
982
|
+
if (typeof plugin === "object" &&
|
|
983
|
+
!Array.isArray(plugin) &&
|
|
984
|
+
plugin != null) {
|
|
985
|
+
const entries = Object.entries(plugin).map(([k, v]) => [`${k}:`.padEnd(18), formatValue(v)]);
|
|
971
986
|
printKvBlock(pluginTitle(props.pluginName), entries);
|
|
972
987
|
}
|
|
973
988
|
else if (Array.isArray(plugin)) {
|
|
974
989
|
const entries = plugin.map((item, i) => [
|
|
975
990
|
`[${i}]:`.padEnd(18),
|
|
976
|
-
typeof item ===
|
|
991
|
+
typeof item === "object" ? JSON.stringify(item) : String(item),
|
|
977
992
|
]);
|
|
978
993
|
printKvBlock(pluginTitle(props.pluginName), entries);
|
|
979
994
|
}
|
|
980
995
|
else {
|
|
981
996
|
printKvBlock(pluginTitle(props.pluginName), [
|
|
982
|
-
[
|
|
997
|
+
["Value:".padEnd(18), String(plugin)],
|
|
983
998
|
]);
|
|
984
999
|
}
|
|
985
1000
|
};
|
|
@@ -992,10 +1007,10 @@ const userCreate = async (props) => {
|
|
|
992
1007
|
};
|
|
993
1008
|
const { data } = await props.apiClient.createBranchNeonAuthNewUser(props.projectId, branchId, requestBody);
|
|
994
1009
|
const displayName = requestBody.name !== props.email ? requestBody.name : undefined;
|
|
995
|
-
printKvBlock(
|
|
996
|
-
[
|
|
997
|
-
[
|
|
998
|
-
...(displayName ? [[
|
|
1010
|
+
printKvBlock("User created", [
|
|
1011
|
+
["ID: ", data.id],
|
|
1012
|
+
["Email: ", requestBody.email],
|
|
1013
|
+
...(displayName ? [["Name: ", displayName]] : []),
|
|
999
1014
|
]);
|
|
1000
1015
|
};
|
|
1001
1016
|
const userDelete = async (props) => {
|
|
@@ -1006,8 +1021,8 @@ const userDelete = async (props) => {
|
|
|
1006
1021
|
const userSetRole = async (props) => {
|
|
1007
1022
|
const branchId = await resolveBranch(props);
|
|
1008
1023
|
const { data } = await props.apiClient.updateNeonAuthUserRole(props.projectId, branchId, props.userId, { roles: props.roles });
|
|
1009
|
-
printKvBlock(
|
|
1010
|
-
[
|
|
1011
|
-
[
|
|
1024
|
+
printKvBlock("Roles updated", [
|
|
1025
|
+
["User ID: ", data.id],
|
|
1026
|
+
["Roles: ", props.roles.join(", ")],
|
|
1012
1027
|
]);
|
|
1013
1028
|
};
|