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