@mtndev/cli 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/cjs/__chunks/VH7oDXx4.cjs +28 -0
- package/cjs/commands/aws/ec2/allow-ip/handler.cjs +261 -0
- package/cjs/commands/aws/ec2/allow-ip/handler.d.ts +6 -0
- package/cjs/commands/aws/ec2/allow-ip/index.cjs +25 -0
- package/cjs/commands/aws/ec2/allow-ip/index.d.ts +3 -0
- package/cjs/commands/aws/ec2/allow-ip/schema.cjs +33 -0
- package/cjs/commands/aws/ec2/allow-ip/schema.d.ts +33 -0
- package/cjs/commands/aws/ec2/allow-ip/utils.cjs +379 -0
- package/cjs/commands/aws/ec2/allow-ip/utils.d.ts +189 -0
- package/cjs/commands/aws/iam/admin-group.cjs +111 -0
- package/cjs/commands/aws/iam/admin-group.d.ts +55 -0
- package/cjs/commands/aws/iam/break-glass/handler.cjs +127 -0
- package/cjs/commands/aws/iam/break-glass/handler.d.ts +7 -0
- package/cjs/commands/aws/iam/break-glass/index.cjs +7 -0
- package/cjs/commands/aws/iam/break-glass/index.d.ts +2 -0
- package/cjs/commands/aws/iam/break-glass/schema.cjs +21 -0
- package/cjs/commands/aws/iam/break-glass/schema.d.ts +25 -0
- package/cjs/commands/aws/iam/check-admin/handler.cjs +89 -0
- package/cjs/commands/aws/iam/check-admin/handler.d.ts +5 -0
- package/cjs/commands/aws/iam/check-admin/index.cjs +7 -0
- package/cjs/commands/aws/iam/check-admin/index.d.ts +2 -0
- package/cjs/commands/aws/iam/check-admin/schema.cjs +19 -0
- package/cjs/commands/aws/iam/check-admin/schema.d.ts +19 -0
- package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
- package/cjs/commands/aws/iam/policies/sts-federation.json +11 -0
- package/cjs/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
- package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
- package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
- package/cjs/commands/aws/iam/policies/user-admins-write-list.json +117 -0
- package/cjs/commands/aws/iam/remove-admin/handler.cjs +120 -0
- package/cjs/commands/aws/iam/remove-admin/handler.d.ts +6 -0
- package/cjs/commands/aws/iam/remove-admin/index.cjs +7 -0
- package/cjs/commands/aws/iam/remove-admin/index.d.ts +2 -0
- package/cjs/commands/aws/iam/remove-admin/schema.cjs +22 -0
- package/cjs/commands/aws/iam/remove-admin/schema.d.ts +28 -0
- package/cjs/commands/aws/iam/setup-acl/handler.cjs +227 -0
- package/cjs/commands/aws/iam/setup-acl/handler.d.ts +6 -0
- package/cjs/commands/aws/iam/setup-acl/index.cjs +13 -0
- package/cjs/commands/aws/iam/setup-acl/index.d.ts +3 -0
- package/cjs/commands/aws/iam/setup-acl/schema.cjs +19 -0
- package/cjs/commands/aws/iam/setup-acl/schema.d.ts +24 -0
- package/cjs/commands/aws/iam/setup-acl/utils.cjs +305 -0
- package/cjs/commands/aws/iam/setup-acl/utils.d.ts +61 -0
- package/cjs/commands/aws/iam/users/handler.cjs +59 -0
- package/cjs/commands/aws/iam/users/handler.d.ts +5 -0
- package/cjs/commands/aws/iam/users/index.cjs +18 -0
- package/cjs/commands/aws/iam/users/index.d.ts +3 -0
- package/cjs/commands/aws/iam/users/schema.cjs +17 -0
- package/cjs/commands/aws/iam/users/schema.d.ts +18 -0
- package/cjs/commands/aws/iam/users/utils.cjs +454 -0
- package/cjs/commands/aws/iam/users/utils.d.ts +119 -0
- package/cjs/commands/aws/index.cjs +223 -0
- package/cjs/commands/aws/index.d.ts +14 -0
- package/cjs/commands/aws/route53/add-record/handler.cjs +264 -0
- package/cjs/commands/aws/route53/add-record/handler.d.ts +36 -0
- package/cjs/commands/aws/route53/add-record/index.cjs +26 -0
- package/cjs/commands/aws/route53/add-record/index.d.ts +3 -0
- package/cjs/commands/aws/route53/add-record/schema.cjs +43 -0
- package/cjs/commands/aws/route53/add-record/schema.d.ts +45 -0
- package/cjs/commands/aws/route53/add-record/utils.cjs +404 -0
- package/cjs/commands/aws/route53/add-record/utils.d.ts +216 -0
- package/cjs/commands/aws/route53/remove-record/handler.cjs +114 -0
- package/cjs/commands/aws/route53/remove-record/handler.d.ts +6 -0
- package/cjs/commands/aws/route53/remove-record/index.cjs +5 -0
- package/cjs/commands/aws/route53/remove-record/index.d.ts +2 -0
- package/cjs/commands/aws/route53/remove-record/schema.cjs +22 -0
- package/cjs/commands/aws/route53/remove-record/schema.d.ts +30 -0
- package/cjs/commands/aws/schema.cjs +19 -0
- package/cjs/commands/aws/schema.d.ts +13 -0
- package/cjs/commands/aws/ses/send-email/handler.cjs +73 -0
- package/cjs/commands/aws/ses/send-email/handler.d.ts +5 -0
- package/cjs/commands/aws/ses/send-email/index.cjs +9 -0
- package/cjs/commands/aws/ses/send-email/index.d.ts +3 -0
- package/cjs/commands/aws/ses/send-email/schema.cjs +24 -0
- package/cjs/commands/aws/ses/send-email/schema.d.ts +39 -0
- package/cjs/commands/aws/ses/send-email/utils.cjs +87 -0
- package/cjs/commands/aws/ses/send-email/utils.d.ts +47 -0
- package/cjs/commands/aws/ses/smtp-password.cjs +72 -0
- package/cjs/commands/aws/ses/smtp-password.d.ts +21 -0
- package/cjs/commands/aws/utils/exec-aws.cjs +145 -0
- package/cjs/commands/aws/utils/exec-aws.d.ts +79 -0
- package/cjs/commands/aws/utils/resolve-policy-path.cjs +15 -0
- package/cjs/commands/aws/utils/resolve-policy-path.d.ts +8 -0
- package/cjs/commands/aws/utils/resolve-profile.cjs +93 -0
- package/cjs/commands/aws/utils/resolve-profile.d.ts +28 -0
- package/cjs/commands/aws/utils/tty.cjs +54 -0
- package/cjs/commands/aws/utils/tty.d.ts +24 -0
- package/cjs/commands/db/create-readonly-user/handler.cjs +254 -0
- package/cjs/commands/db/create-readonly-user/handler.d.ts +5 -0
- package/cjs/commands/db/create-readonly-user/index.cjs +18 -0
- package/cjs/commands/db/create-readonly-user/index.d.ts +3 -0
- package/cjs/commands/db/create-readonly-user/mysql.cjs +102 -0
- package/cjs/commands/db/create-readonly-user/mysql.d.ts +33 -0
- package/cjs/commands/db/create-readonly-user/postgres.cjs +209 -0
- package/cjs/commands/db/create-readonly-user/postgres.d.ts +31 -0
- package/cjs/commands/db/create-readonly-user/schema.cjs +39 -0
- package/cjs/commands/db/create-readonly-user/schema.d.ts +55 -0
- package/cjs/commands/db/create-readonly-user/utils.cjs +160 -0
- package/cjs/commands/db/create-readonly-user/utils.d.ts +112 -0
- package/cjs/commands/db/index.cjs +46 -0
- package/cjs/commands/db/index.d.ts +6 -0
- package/cjs/commands/doppler/handler.cjs +95 -9
- package/cjs/commands/doppler/index.cjs +33 -41
- package/cjs/commands/doppler/index.d.ts +1 -0
- package/cjs/commands/doppler/schema.cjs +130 -6
- package/cjs/commands/doppler/schema.d.ts +233 -17
- package/cjs/commands/doppler/set-secret.cjs +378 -0
- package/cjs/commands/doppler/set-secret.d.ts +113 -0
- package/cjs/commands/doppler/utils/exec.cjs +106 -12
- package/cjs/commands/doppler/utils.cjs +412 -30
- package/cjs/commands/doppler/utils.d.ts +32 -8
- package/cjs/commands/icons/handler.cjs +82 -8
- package/cjs/commands/icons/index.cjs +15 -31
- package/cjs/commands/icons/schema.cjs +101 -13
- package/cjs/commands/icons/schema.d.ts +1 -1
- package/cjs/commands/icons/utils.cjs +131 -13
- package/cjs/commands/index.cjs +54 -82
- package/cjs/commands/schema.cjs +25 -8
- package/cjs/index.cjs +3 -4
- package/esm/commands/aws/ec2/allow-ip/handler.d.ts +6 -0
- package/esm/commands/aws/ec2/allow-ip/handler.js +258 -0
- package/esm/commands/aws/ec2/allow-ip/index.d.ts +3 -0
- package/esm/commands/aws/ec2/allow-ip/index.js +4 -0
- package/esm/commands/aws/ec2/allow-ip/schema.d.ts +33 -0
- package/esm/commands/aws/ec2/allow-ip/schema.js +29 -0
- package/esm/commands/aws/ec2/allow-ip/utils.d.ts +189 -0
- package/esm/commands/aws/ec2/allow-ip/utils.js +363 -0
- package/esm/commands/aws/iam/admin-group.d.ts +55 -0
- package/esm/commands/aws/iam/admin-group.js +104 -0
- package/esm/commands/aws/iam/break-glass/handler.d.ts +7 -0
- package/esm/commands/aws/iam/break-glass/handler.js +124 -0
- package/esm/commands/aws/iam/break-glass/index.d.ts +2 -0
- package/esm/commands/aws/iam/break-glass/index.js +4 -0
- package/esm/commands/aws/iam/break-glass/schema.d.ts +25 -0
- package/esm/commands/aws/iam/break-glass/schema.js +19 -0
- package/esm/commands/aws/iam/check-admin/handler.d.ts +5 -0
- package/esm/commands/aws/iam/check-admin/handler.js +86 -0
- package/esm/commands/aws/iam/check-admin/index.d.ts +2 -0
- package/esm/commands/aws/iam/check-admin/index.js +4 -0
- package/esm/commands/aws/iam/check-admin/schema.d.ts +19 -0
- package/esm/commands/aws/iam/check-admin/schema.js +17 -0
- package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
- package/esm/commands/aws/iam/policies/sts-federation.json +11 -0
- package/esm/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
- package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
- package/esm/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
- package/esm/commands/aws/iam/policies/user-admins-write-list.json +117 -0
- package/esm/commands/aws/iam/remove-admin/handler.d.ts +6 -0
- package/esm/commands/aws/iam/remove-admin/handler.js +117 -0
- package/esm/commands/aws/iam/remove-admin/index.d.ts +2 -0
- package/esm/commands/aws/iam/remove-admin/index.js +4 -0
- package/esm/commands/aws/iam/remove-admin/schema.d.ts +28 -0
- package/esm/commands/aws/iam/remove-admin/schema.js +20 -0
- package/esm/commands/aws/iam/setup-acl/handler.d.ts +6 -0
- package/esm/commands/aws/iam/setup-acl/handler.js +224 -0
- package/esm/commands/aws/iam/setup-acl/index.d.ts +3 -0
- package/esm/commands/aws/iam/setup-acl/index.js +4 -0
- package/esm/commands/aws/iam/setup-acl/schema.d.ts +24 -0
- package/esm/commands/aws/iam/setup-acl/schema.js +18 -0
- package/esm/commands/aws/iam/setup-acl/utils.d.ts +61 -0
- package/esm/commands/aws/iam/setup-acl/utils.js +298 -0
- package/esm/commands/aws/iam/users/handler.d.ts +5 -0
- package/esm/commands/aws/iam/users/handler.js +56 -0
- package/esm/commands/aws/iam/users/index.d.ts +3 -0
- package/esm/commands/aws/iam/users/index.js +4 -0
- package/esm/commands/aws/iam/users/schema.d.ts +18 -0
- package/esm/commands/aws/iam/users/schema.js +16 -0
- package/esm/commands/aws/iam/users/utils.d.ts +119 -0
- package/esm/commands/aws/iam/users/utils.js +440 -0
- package/esm/commands/aws/index.d.ts +14 -0
- package/esm/commands/aws/index.js +142 -0
- package/esm/commands/aws/route53/add-record/handler.d.ts +36 -0
- package/esm/commands/aws/route53/add-record/handler.js +258 -0
- package/esm/commands/aws/route53/add-record/index.d.ts +3 -0
- package/esm/commands/aws/route53/add-record/index.js +4 -0
- package/esm/commands/aws/route53/add-record/schema.d.ts +45 -0
- package/esm/commands/aws/route53/add-record/schema.js +40 -0
- package/esm/commands/aws/route53/add-record/utils.d.ts +216 -0
- package/esm/commands/aws/route53/add-record/utils.js +389 -0
- package/esm/commands/aws/route53/remove-record/handler.d.ts +6 -0
- package/esm/commands/aws/route53/remove-record/handler.js +111 -0
- package/esm/commands/aws/route53/remove-record/index.d.ts +2 -0
- package/esm/commands/aws/route53/remove-record/index.js +3 -0
- package/esm/commands/aws/route53/remove-record/schema.d.ts +30 -0
- package/esm/commands/aws/route53/remove-record/schema.js +21 -0
- package/esm/commands/aws/schema.d.ts +13 -0
- package/esm/commands/aws/schema.js +16 -0
- package/esm/commands/aws/ses/send-email/handler.d.ts +5 -0
- package/esm/commands/aws/ses/send-email/handler.js +70 -0
- package/esm/commands/aws/ses/send-email/index.d.ts +3 -0
- package/esm/commands/aws/ses/send-email/index.js +4 -0
- package/esm/commands/aws/ses/send-email/schema.d.ts +39 -0
- package/esm/commands/aws/ses/send-email/schema.js +23 -0
- package/esm/commands/aws/ses/send-email/utils.d.ts +47 -0
- package/esm/commands/aws/ses/send-email/utils.js +84 -0
- package/esm/commands/aws/ses/smtp-password.d.ts +21 -0
- package/esm/commands/aws/ses/smtp-password.js +69 -0
- package/esm/commands/aws/utils/exec-aws.d.ts +79 -0
- package/esm/commands/aws/utils/exec-aws.js +137 -0
- package/esm/commands/aws/utils/resolve-policy-path.d.ts +8 -0
- package/esm/commands/aws/utils/resolve-policy-path.js +14 -0
- package/esm/commands/aws/utils/resolve-profile.d.ts +28 -0
- package/esm/commands/aws/utils/resolve-profile.js +88 -0
- package/esm/commands/aws/utils/tty.d.ts +24 -0
- package/esm/commands/aws/utils/tty.js +51 -0
- package/esm/commands/db/create-readonly-user/handler.d.ts +5 -0
- package/esm/commands/db/create-readonly-user/handler.js +251 -0
- package/esm/commands/db/create-readonly-user/index.d.ts +3 -0
- package/esm/commands/db/create-readonly-user/index.js +4 -0
- package/esm/commands/db/create-readonly-user/mysql.d.ts +33 -0
- package/esm/commands/db/create-readonly-user/mysql.js +98 -0
- package/esm/commands/db/create-readonly-user/postgres.d.ts +31 -0
- package/esm/commands/db/create-readonly-user/postgres.js +205 -0
- package/esm/commands/db/create-readonly-user/schema.d.ts +55 -0
- package/esm/commands/db/create-readonly-user/schema.js +35 -0
- package/esm/commands/db/create-readonly-user/utils.d.ts +112 -0
- package/esm/commands/db/create-readonly-user/utils.js +151 -0
- package/esm/commands/db/index.d.ts +6 -0
- package/esm/commands/db/index.js +31 -0
- package/esm/commands/doppler/handler.js +92 -9
- package/esm/commands/doppler/index.d.ts +1 -0
- package/esm/commands/doppler/index.js +6 -41
- package/esm/commands/doppler/schema.d.ts +233 -17
- package/esm/commands/doppler/schema.js +125 -7
- package/esm/commands/doppler/set-secret.d.ts +113 -0
- package/esm/commands/doppler/set-secret.js +370 -0
- package/esm/commands/doppler/utils/exec.js +100 -12
- package/esm/commands/doppler/utils.d.ts +32 -8
- package/esm/commands/doppler/utils.js +396 -30
- package/esm/commands/icons/handler.js +79 -8
- package/esm/commands/icons/index.js +4 -31
- package/esm/commands/icons/schema.d.ts +1 -1
- package/esm/commands/icons/schema.js +97 -15
- package/esm/commands/icons/utils.js +125 -13
- package/esm/commands/index.js +49 -78
- package/esm/commands/schema.js +22 -7
- package/esm/index.js +3 -4
- package/package.json +12 -2
- package/vitest.config.ts +11 -0
- package/cjs/__chunks/4GFJUCSP.cjs +0 -52
- package/cjs/__chunks/4SFWHCB3.cjs +0 -133
- package/cjs/__chunks/B2KFW7T7.cjs +0 -1
- package/cjs/__chunks/EMMC33QA.cjs +0 -401
- package/cjs/__chunks/INFOPAHN.cjs +0 -117
- package/cjs/__chunks/J36P6UNE.cjs +0 -75
- package/cjs/__chunks/NIMXXHJQ.cjs +0 -92
- package/cjs/__chunks/NSRFG4DF.cjs +0 -100
- package/cjs/__chunks/WNJ3ZYLI.cjs +0 -1
- package/cjs/__chunks/ZMDIOZPT.cjs +0 -22
- package/esm/__chunks/46HGXFME.js +0 -0
- package/esm/__chunks/6G4PXXKF.js +0 -75
- package/esm/__chunks/6L5RNR7G.js +0 -100
- package/esm/__chunks/FL7AILGH.js +0 -133
- package/esm/__chunks/HJSXC6HP.js +0 -117
- package/esm/__chunks/O262GK4B.js +0 -401
- package/esm/__chunks/O3CCA76Q.js +0 -22
- package/esm/__chunks/SJXQJ7RS.js +0 -92
- package/esm/__chunks/SNCKIAOR.js +0 -0
- package/esm/__chunks/X3R5PSWH.js +0 -52
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../../../__chunks/VH7oDXx4.cjs");
|
|
3
|
+
const require_commands_aws_utils_tty = require("../../aws/utils/tty.cjs");
|
|
4
|
+
const require_commands_db_create_readonly_user_utils = require("./utils.cjs");
|
|
5
|
+
const require_commands_db_create_readonly_user_mysql = require("./mysql.cjs");
|
|
6
|
+
const require_commands_db_create_readonly_user_postgres = require("./postgres.cjs");
|
|
7
|
+
const require_commands_db_create_readonly_user_schema = require("./schema.cjs");
|
|
8
|
+
let chalk = require("chalk");
|
|
9
|
+
chalk = require_rolldown_runtime.__toESM(chalk, 1);
|
|
10
|
+
let zod = require("zod");
|
|
11
|
+
//#region src/commands/db/create-readonly-user/handler.js
|
|
12
|
+
/**
|
|
13
|
+
* @param {"postgres" | "mysql" | undefined} engineOption
|
|
14
|
+
* @param {string | null} url
|
|
15
|
+
* @returns {Promise<"postgres" | "mysql">}
|
|
16
|
+
*/
|
|
17
|
+
async function resolveEngine(engineOption, url) {
|
|
18
|
+
if (engineOption) return engineOption;
|
|
19
|
+
if (url) {
|
|
20
|
+
const detected = require_commands_db_create_readonly_user_utils.detectEngineFromUrl(url);
|
|
21
|
+
if (detected) return detected;
|
|
22
|
+
}
|
|
23
|
+
const index = await require_commands_aws_utils_tty.promptChoice({
|
|
24
|
+
message: "Choose a database engine:",
|
|
25
|
+
options: ["postgres", "mysql"]
|
|
26
|
+
});
|
|
27
|
+
const chosen = require_commands_db_create_readonly_user_schema.ENGINES[index];
|
|
28
|
+
if (!chosen) throw new Error("No engine selected");
|
|
29
|
+
return chosen;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @param {{ urlOption?: string; engine: "postgres" | "mysql" }} params
|
|
33
|
+
* @returns {Promise<string>}
|
|
34
|
+
*/
|
|
35
|
+
async function resolveUrl(params) {
|
|
36
|
+
const { urlOption, engine } = params;
|
|
37
|
+
const fromEnv = require_commands_db_create_readonly_user_utils.resolveAdminUrl({
|
|
38
|
+
url: urlOption,
|
|
39
|
+
engine
|
|
40
|
+
});
|
|
41
|
+
if (fromEnv) return fromEnv;
|
|
42
|
+
const raw = await require_commands_aws_utils_tty.prompt(` Admin connection URL (e.g. ${engine === "postgres" ? "postgresql://admin:pass@host:5432/postgres" : "mysql://admin:pass@host:3306/"}): `);
|
|
43
|
+
if (!raw) throw new Error("Admin connection URL is required (pass --url or set DATABASE_URL)");
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @param {{
|
|
48
|
+
* databaseOption?: string;
|
|
49
|
+
* databases: string[];
|
|
50
|
+
* }} params
|
|
51
|
+
* @returns {Promise<{ mode: "all" | "one"; databases: string[] }>}
|
|
52
|
+
*/
|
|
53
|
+
async function resolveScope(params) {
|
|
54
|
+
const { databaseOption, databases } = params;
|
|
55
|
+
if (databaseOption) {
|
|
56
|
+
const normalized = databaseOption.trim().toLowerCase();
|
|
57
|
+
if (normalized === "all" || normalized === "*") return {
|
|
58
|
+
mode: "all",
|
|
59
|
+
databases
|
|
60
|
+
};
|
|
61
|
+
if (!databases.includes(databaseOption)) throw new Error(`Database "${databaseOption}" not found. Available: ${databases.join(", ") || "(none)"}`);
|
|
62
|
+
return {
|
|
63
|
+
mode: "one",
|
|
64
|
+
databases: [databaseOption]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (databases.length === 0) throw new Error("No user databases found to grant access on.");
|
|
68
|
+
const options = [...databases.map((name) => name), `all (${databases.length} databases)`];
|
|
69
|
+
const index = await require_commands_aws_utils_tty.promptChoice({
|
|
70
|
+
message: "Grant read-only on which database?",
|
|
71
|
+
options
|
|
72
|
+
});
|
|
73
|
+
if (index === options.length - 1) return {
|
|
74
|
+
mode: "all",
|
|
75
|
+
databases
|
|
76
|
+
};
|
|
77
|
+
const chosen = databases[index];
|
|
78
|
+
if (!chosen) throw new Error("No database selected");
|
|
79
|
+
return {
|
|
80
|
+
mode: "one",
|
|
81
|
+
databases: [chosen]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @param {string | undefined} usernameOption
|
|
86
|
+
* @returns {Promise<string>}
|
|
87
|
+
*/
|
|
88
|
+
async function resolveUsername(usernameOption) {
|
|
89
|
+
if (usernameOption) return usernameOption;
|
|
90
|
+
const username = await require_commands_aws_utils_tty.prompt(` Read-only username [default: readonly]: `) || "readonly";
|
|
91
|
+
if (!/^[A-Za-z0-9_]+$/.test(username)) throw new Error("Username must contain only letters, numbers, and underscores");
|
|
92
|
+
return username;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* @param {string | undefined} passwordOption
|
|
96
|
+
* @returns {Promise<{ password: string; generated: boolean }>}
|
|
97
|
+
*/
|
|
98
|
+
async function resolvePassword(passwordOption) {
|
|
99
|
+
if (passwordOption) return {
|
|
100
|
+
password: passwordOption,
|
|
101
|
+
generated: false
|
|
102
|
+
};
|
|
103
|
+
if (await require_commands_aws_utils_tty.promptYesNo({
|
|
104
|
+
message: " Generate a random password?",
|
|
105
|
+
defaultYes: true
|
|
106
|
+
})) return {
|
|
107
|
+
password: require_commands_db_create_readonly_user_utils.generatePassword(),
|
|
108
|
+
generated: true
|
|
109
|
+
};
|
|
110
|
+
const password = await require_commands_aws_utils_tty.prompt(" Password: ");
|
|
111
|
+
if (!password) throw new Error("Password is required");
|
|
112
|
+
return {
|
|
113
|
+
password,
|
|
114
|
+
generated: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Redact password in a URL for display.
|
|
119
|
+
* @param {string} url
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
function redactUrl(url) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = new URL(url);
|
|
125
|
+
if (parsed.password) parsed.password = "***";
|
|
126
|
+
return parsed.toString();
|
|
127
|
+
} catch {
|
|
128
|
+
return url;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* `db create-readonly-user` handler.
|
|
133
|
+
* @param {unknown} options
|
|
134
|
+
*/
|
|
135
|
+
async function createReadonlyUserCommand(options) {
|
|
136
|
+
try {
|
|
137
|
+
const { engine: engineOption, url: urlOption, database: databaseOption, username: usernameOption, password: passwordOption, hostPattern: hostPatternOption, ssl: sslOption, yes, dryRun, verbose } = require_commands_db_create_readonly_user_schema.createReadonlyUserOptionsSchema.parse(options);
|
|
138
|
+
console.log(chalk.default.blue("\nCreate read-only database user"));
|
|
139
|
+
console.log("");
|
|
140
|
+
const engine = await resolveEngine(engineOption, require_commands_db_create_readonly_user_utils.resolveAdminUrl({
|
|
141
|
+
url: urlOption,
|
|
142
|
+
engine: engineOption
|
|
143
|
+
}));
|
|
144
|
+
console.log(`Engine: ${engine}`);
|
|
145
|
+
const url = await resolveUrl({
|
|
146
|
+
urlOption,
|
|
147
|
+
engine
|
|
148
|
+
});
|
|
149
|
+
const config = require_commands_db_create_readonly_user_utils.parseConnectionUrl({
|
|
150
|
+
url,
|
|
151
|
+
engine,
|
|
152
|
+
sslOption
|
|
153
|
+
});
|
|
154
|
+
console.log(`Admin: ${config.user}@${config.host}:${config.port}` + (config.database ? `/${config.database}` : ""));
|
|
155
|
+
console.log("");
|
|
156
|
+
const listed = engine === "postgres" ? await require_commands_db_create_readonly_user_postgres.listPostgresDatabases(config) : await require_commands_db_create_readonly_user_mysql.listMysqlDatabases(config);
|
|
157
|
+
if (verbose) {
|
|
158
|
+
console.log(`Found databases: ${listed.join(", ") || "(none)"}`);
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
const scope = await resolveScope({
|
|
162
|
+
databaseOption,
|
|
163
|
+
databases: listed
|
|
164
|
+
});
|
|
165
|
+
console.log(scope.mode === "all" ? `Scope: all (${scope.databases.length} databases)` : `Scope: ${scope.databases[0]}`);
|
|
166
|
+
console.log("");
|
|
167
|
+
const username = await resolveUsername(usernameOption);
|
|
168
|
+
const { password, generated } = await resolvePassword(passwordOption);
|
|
169
|
+
const hostPattern = hostPatternOption ?? "%";
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log("Plan:");
|
|
172
|
+
console.log(` User: ${username}`);
|
|
173
|
+
console.log(` Password: ${generated ? "(generated)" : "(provided)"}${dryRun ? " [dry-run]" : ""}`);
|
|
174
|
+
if (engine === "mysql") console.log(` Host: ${hostPattern}`);
|
|
175
|
+
console.log(scope.mode === "all" ? ` Grants: SELECT on ALL databases (${scope.databases.length})` : ` Grants: SELECT on ${scope.databases[0]}`);
|
|
176
|
+
if (scope.mode === "all" && engine === "postgres") console.log(chalk.default.yellow(" Note: Databases created later will NOT get these grants automatically — re-run this command."));
|
|
177
|
+
console.log("");
|
|
178
|
+
if (!yes && !dryRun) {
|
|
179
|
+
if (!await require_commands_aws_utils_tty.promptYesNo({
|
|
180
|
+
message: "Apply these changes?",
|
|
181
|
+
defaultYes: false
|
|
182
|
+
})) {
|
|
183
|
+
console.log("Aborted.");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log("");
|
|
187
|
+
}
|
|
188
|
+
const result = engine === "postgres" ? await require_commands_db_create_readonly_user_postgres.applyPostgresReadonly({
|
|
189
|
+
config,
|
|
190
|
+
username,
|
|
191
|
+
password,
|
|
192
|
+
databases: scope.databases,
|
|
193
|
+
dryRun,
|
|
194
|
+
verbose
|
|
195
|
+
}) : await require_commands_db_create_readonly_user_mysql.applyMysqlReadonly({
|
|
196
|
+
config,
|
|
197
|
+
username,
|
|
198
|
+
password,
|
|
199
|
+
databases: scope.mode === "all" ? "all" : scope.databases,
|
|
200
|
+
hostPattern,
|
|
201
|
+
dryRun,
|
|
202
|
+
verbose
|
|
203
|
+
});
|
|
204
|
+
if (dryRun) {
|
|
205
|
+
console.log(chalk.default.cyan("Dry run — SQL that would run:"));
|
|
206
|
+
for (const statement of result.statements) console.log(` ${statement}`);
|
|
207
|
+
console.log("");
|
|
208
|
+
console.log(chalk.default.cyan("No changes made."));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log(chalk.default.green(result.created ? `Created user "${username}" and granted read-only access.` : `Updated user "${username}" password and refreshed read-only grants.`));
|
|
212
|
+
console.log("");
|
|
213
|
+
const sampleDb = scope.mode === "one" ? scope.databases[0] ?? "" : scope.databases[0] ?? "";
|
|
214
|
+
const readonlyUrl = require_commands_db_create_readonly_user_utils.buildReadonlyUrl({
|
|
215
|
+
engine,
|
|
216
|
+
host: config.host,
|
|
217
|
+
port: config.port,
|
|
218
|
+
username,
|
|
219
|
+
password,
|
|
220
|
+
database: sampleDb
|
|
221
|
+
});
|
|
222
|
+
console.log("Connection (secret shown once — copy now):");
|
|
223
|
+
console.log("────────────────────────────────────────────────────────────────");
|
|
224
|
+
console.log(readonlyUrl);
|
|
225
|
+
if (scope.mode === "all") {
|
|
226
|
+
console.log("");
|
|
227
|
+
console.log(chalk.default.gray(` (URL uses database "${sampleDb}" as an example; user can connect to any granted database.)`));
|
|
228
|
+
}
|
|
229
|
+
console.log("────────────────────────────────────────────────────────────────");
|
|
230
|
+
console.log("");
|
|
231
|
+
console.log(chalk.default.gray(`Admin URL used: ${redactUrl(url)}`));
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error instanceof zod.ZodError) {
|
|
234
|
+
console.error(chalk.default.red("\n❌ Invalid command options:"));
|
|
235
|
+
error.errors.forEach((err) => {
|
|
236
|
+
console.error(chalk.default.red(` ${err.path.join(".")}: ${err.message}`));
|
|
237
|
+
});
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
const verbose = options?.verbose || false;
|
|
241
|
+
console.error(chalk.default.red("\n❌ create-readonly-user failed:"));
|
|
242
|
+
if (error instanceof Error) {
|
|
243
|
+
console.error(chalk.default.red(` ${error.message}`));
|
|
244
|
+
if (verbose && error.stack) {
|
|
245
|
+
console.error(chalk.default.gray("\nStack trace:"));
|
|
246
|
+
console.error(chalk.default.gray(error.stack));
|
|
247
|
+
}
|
|
248
|
+
} else console.error(chalk.default.red(` ${String(error)}`));
|
|
249
|
+
console.error(chalk.default.yellow("\n💡 Pass --url or set DATABASE_URL. Use --verbose for details.\n"));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
exports.createReadonlyUserCommand = createReadonlyUserCommand;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_commands_db_create_readonly_user_utils = require("./utils.cjs");
|
|
3
|
+
const require_commands_db_create_readonly_user_schema = require("./schema.cjs");
|
|
4
|
+
const require_commands_db_create_readonly_user_handler = require("./handler.cjs");
|
|
5
|
+
exports.DEFAULT_MYSQL_HOST_PATTERN = require_commands_db_create_readonly_user_schema.DEFAULT_MYSQL_HOST_PATTERN;
|
|
6
|
+
exports.DEFAULT_READONLY_USERNAME = require_commands_db_create_readonly_user_schema.DEFAULT_READONLY_USERNAME;
|
|
7
|
+
exports.ENGINES = require_commands_db_create_readonly_user_schema.ENGINES;
|
|
8
|
+
exports.buildReadonlyUrl = require_commands_db_create_readonly_user_utils.buildReadonlyUrl;
|
|
9
|
+
exports.createReadonlyUserCommand = require_commands_db_create_readonly_user_handler.createReadonlyUserCommand;
|
|
10
|
+
exports.createReadonlyUserOptionsSchema = require_commands_db_create_readonly_user_schema.createReadonlyUserOptionsSchema;
|
|
11
|
+
exports.detectEngineFromUrl = require_commands_db_create_readonly_user_utils.detectEngineFromUrl;
|
|
12
|
+
exports.generatePassword = require_commands_db_create_readonly_user_utils.generatePassword;
|
|
13
|
+
exports.isSystemDatabase = require_commands_db_create_readonly_user_utils.isSystemDatabase;
|
|
14
|
+
exports.parseConnectionUrl = require_commands_db_create_readonly_user_utils.parseConnectionUrl;
|
|
15
|
+
exports.quoteIdent = require_commands_db_create_readonly_user_utils.quoteIdent;
|
|
16
|
+
exports.quoteLiteral = require_commands_db_create_readonly_user_utils.quoteLiteral;
|
|
17
|
+
exports.resolveAdminUrl = require_commands_db_create_readonly_user_utils.resolveAdminUrl;
|
|
18
|
+
exports.resolveSsl = require_commands_db_create_readonly_user_utils.resolveSsl;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../../../__chunks/VH7oDXx4.cjs");
|
|
3
|
+
const require_commands_db_create_readonly_user_utils = require("./utils.cjs");
|
|
4
|
+
let mysql2_promise = require("mysql2/promise");
|
|
5
|
+
mysql2_promise = require_rolldown_runtime.__toESM(mysql2_promise, 1);
|
|
6
|
+
//#region src/commands/db/create-readonly-user/mysql.js
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import("./utils.js").ConnectionConfig} ConnectionConfig
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @param {ConnectionConfig} config
|
|
12
|
+
* @returns {Promise<import("mysql2/promise").Connection>}
|
|
13
|
+
*/
|
|
14
|
+
async function createConnection(config) {
|
|
15
|
+
return mysql2_promise.default.createConnection({
|
|
16
|
+
host: config.host,
|
|
17
|
+
port: config.port,
|
|
18
|
+
user: config.user,
|
|
19
|
+
password: config.password,
|
|
20
|
+
database: config.database || void 0,
|
|
21
|
+
ssl: config.ssl && typeof config.ssl === "object" ? config.ssl : void 0,
|
|
22
|
+
multipleStatements: false
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List schemas/databases visible to the admin user (excluding system schemas).
|
|
27
|
+
* @param {ConnectionConfig} config
|
|
28
|
+
* @returns {Promise<string[]>}
|
|
29
|
+
*/
|
|
30
|
+
async function listMysqlDatabases(config) {
|
|
31
|
+
const connection = await createConnection(config);
|
|
32
|
+
try {
|
|
33
|
+
const [rows] = await connection.query("SHOW DATABASES");
|
|
34
|
+
return rows.map((row) => row.Database ?? "").filter((name) => Boolean(name)).filter((name) => !require_commands_db_create_readonly_user_utils.isSystemDatabase({
|
|
35
|
+
engine: "mysql",
|
|
36
|
+
name
|
|
37
|
+
})).sort((a, b) => a.localeCompare(b));
|
|
38
|
+
} finally {
|
|
39
|
+
await connection.end();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create/update a MySQL user and grant SELECT on one database or *.* .
|
|
44
|
+
* @param {{
|
|
45
|
+
* config: ConnectionConfig;
|
|
46
|
+
* username: string;
|
|
47
|
+
* password: string;
|
|
48
|
+
* databases: string[] | "all";
|
|
49
|
+
* hostPattern: string;
|
|
50
|
+
* dryRun?: boolean;
|
|
51
|
+
* verbose?: boolean;
|
|
52
|
+
* }} params
|
|
53
|
+
* @returns {Promise<{ created: boolean; statements: string[]; databases: string[] | "all" }>}
|
|
54
|
+
*/
|
|
55
|
+
async function applyMysqlReadonly(params) {
|
|
56
|
+
const { config, username, password, databases, hostPattern, dryRun = false, verbose = false } = params;
|
|
57
|
+
const userLiteral = require_commands_db_create_readonly_user_utils.quoteLiteral(username);
|
|
58
|
+
const hostLiteral = require_commands_db_create_readonly_user_utils.quoteLiteral(hostPattern);
|
|
59
|
+
const passwordLiteral = require_commands_db_create_readonly_user_utils.quoteLiteral(password);
|
|
60
|
+
const account = `${userLiteral}@${hostLiteral}`;
|
|
61
|
+
/** @type {string[]} */
|
|
62
|
+
const statements = [];
|
|
63
|
+
const createSql = `CREATE USER IF NOT EXISTS ${account} IDENTIFIED BY ${passwordLiteral}`;
|
|
64
|
+
const alterSql = `ALTER USER ${account} IDENTIFIED BY ${passwordLiteral}`;
|
|
65
|
+
/** @type {string[]} */
|
|
66
|
+
const grantSqls = [];
|
|
67
|
+
if (databases === "all") grantSqls.push(`GRANT SELECT, SHOW VIEW, SHOW DATABASES ON *.* TO ${account}`);
|
|
68
|
+
else for (const database of databases) {
|
|
69
|
+
const dbIdent = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
70
|
+
engine: "mysql",
|
|
71
|
+
name: database
|
|
72
|
+
});
|
|
73
|
+
grantSqls.push(`GRANT SELECT, SHOW VIEW ON ${dbIdent}.* TO ${account}`);
|
|
74
|
+
}
|
|
75
|
+
const flushSql = "FLUSH PRIVILEGES";
|
|
76
|
+
statements.push(createSql, alterSql, ...grantSqls, flushSql);
|
|
77
|
+
if (dryRun) return {
|
|
78
|
+
created: true,
|
|
79
|
+
statements: statements.map((sql) => sql.includes("IDENTIFIED BY") ? sql.replace(/IDENTIFIED BY '.*'/, "IDENTIFIED BY ***") : sql),
|
|
80
|
+
databases
|
|
81
|
+
};
|
|
82
|
+
const connection = await createConnection(config);
|
|
83
|
+
try {
|
|
84
|
+
if (verbose) console.log(` Connected to MySQL as ${config.user}@${config.host}`);
|
|
85
|
+
const [existingRows] = await connection.query("SELECT 1 AS ok FROM mysql.user WHERE User = ? AND Host = ? LIMIT 1", [username, hostPattern]);
|
|
86
|
+
const existed = Array.isArray(existingRows) && existingRows.length > 0;
|
|
87
|
+
await connection.query(createSql);
|
|
88
|
+
await connection.query(alterSql);
|
|
89
|
+
for (const grantSql of grantSqls) await connection.query(grantSql);
|
|
90
|
+
await connection.query(flushSql);
|
|
91
|
+
return {
|
|
92
|
+
created: !existed,
|
|
93
|
+
statements: statements.map((sql) => sql.includes("IDENTIFIED BY") ? sql.replace(/IDENTIFIED BY '.*'/, "IDENTIFIED BY ***") : sql),
|
|
94
|
+
databases
|
|
95
|
+
};
|
|
96
|
+
} finally {
|
|
97
|
+
await connection.end();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
exports.applyMysqlReadonly = applyMysqlReadonly;
|
|
102
|
+
exports.listMysqlDatabases = listMysqlDatabases;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List schemas/databases visible to the admin user (excluding system schemas).
|
|
3
|
+
* @param {ConnectionConfig} config
|
|
4
|
+
* @returns {Promise<string[]>}
|
|
5
|
+
*/
|
|
6
|
+
export function listMysqlDatabases(config: ConnectionConfig): Promise<string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Create/update a MySQL user and grant SELECT on one database or *.* .
|
|
9
|
+
* @param {{
|
|
10
|
+
* config: ConnectionConfig;
|
|
11
|
+
* username: string;
|
|
12
|
+
* password: string;
|
|
13
|
+
* databases: string[] | "all";
|
|
14
|
+
* hostPattern: string;
|
|
15
|
+
* dryRun?: boolean;
|
|
16
|
+
* verbose?: boolean;
|
|
17
|
+
* }} params
|
|
18
|
+
* @returns {Promise<{ created: boolean; statements: string[]; databases: string[] | "all" }>}
|
|
19
|
+
*/
|
|
20
|
+
export function applyMysqlReadonly(params: {
|
|
21
|
+
config: ConnectionConfig;
|
|
22
|
+
username: string;
|
|
23
|
+
password: string;
|
|
24
|
+
databases: string[] | "all";
|
|
25
|
+
hostPattern: string;
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
verbose?: boolean;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
created: boolean;
|
|
30
|
+
statements: string[];
|
|
31
|
+
databases: string[] | "all";
|
|
32
|
+
}>;
|
|
33
|
+
export type ConnectionConfig = import("./utils.js").ConnectionConfig;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../../../__chunks/VH7oDXx4.cjs");
|
|
3
|
+
const require_commands_db_create_readonly_user_utils = require("./utils.cjs");
|
|
4
|
+
let pg = require("pg");
|
|
5
|
+
pg = require_rolldown_runtime.__toESM(pg, 1);
|
|
6
|
+
//#region src/commands/db/create-readonly-user/postgres.js
|
|
7
|
+
const { Client } = pg.default;
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import("./utils.js").ConnectionConfig} ConnectionConfig
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @param {ConnectionConfig} config
|
|
13
|
+
* @param {string} [database]
|
|
14
|
+
* @returns {import("pg").Client}
|
|
15
|
+
*/
|
|
16
|
+
function createClient(config, database) {
|
|
17
|
+
return new Client({
|
|
18
|
+
host: config.host,
|
|
19
|
+
port: config.port,
|
|
20
|
+
user: config.user,
|
|
21
|
+
password: config.password,
|
|
22
|
+
database: database ?? (config.database || "postgres"),
|
|
23
|
+
ssl: config.ssl
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @param {import("pg").Client} client
|
|
28
|
+
* @param {string} sql
|
|
29
|
+
* @param {unknown[]} [params]
|
|
30
|
+
*/
|
|
31
|
+
async function query(client, sql, params) {
|
|
32
|
+
return client.query(sql, params);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* List non-template databases visible to the admin user.
|
|
36
|
+
* @param {ConnectionConfig} config
|
|
37
|
+
* @returns {Promise<string[]>}
|
|
38
|
+
*/
|
|
39
|
+
async function listPostgresDatabases(config) {
|
|
40
|
+
const client = createClient(config, config.database || "postgres");
|
|
41
|
+
await client.connect();
|
|
42
|
+
try {
|
|
43
|
+
return (await query(client, `SELECT datname
|
|
44
|
+
FROM pg_database
|
|
45
|
+
WHERE datistemplate = false
|
|
46
|
+
ORDER BY datname`)).rows.map((row) => row.datname).filter((name) => !require_commands_db_create_readonly_user_utils.isSystemDatabase({
|
|
47
|
+
engine: "postgres",
|
|
48
|
+
name
|
|
49
|
+
}));
|
|
50
|
+
} finally {
|
|
51
|
+
await client.end();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Ensure a login role exists (create or update password).
|
|
56
|
+
* @param {import("pg").Client} client
|
|
57
|
+
* @param {{ username: string; password: string }} params
|
|
58
|
+
* @returns {Promise<{ created: boolean; statements: string[] }>}
|
|
59
|
+
*/
|
|
60
|
+
async function ensureRole(client, params) {
|
|
61
|
+
const { username, password } = params;
|
|
62
|
+
const ident = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
63
|
+
engine: "postgres",
|
|
64
|
+
name: username
|
|
65
|
+
});
|
|
66
|
+
const passwordLiteral = require_commands_db_create_readonly_user_utils.quoteLiteral(password);
|
|
67
|
+
const existing = await query(client, "SELECT 1 FROM pg_roles WHERE rolname = $1", [username]);
|
|
68
|
+
/** @type {string[]} */
|
|
69
|
+
const statements = [];
|
|
70
|
+
if (existing.rowCount && existing.rowCount > 0) {
|
|
71
|
+
const sql = `ALTER ROLE ${ident} WITH LOGIN PASSWORD ${passwordLiteral}`;
|
|
72
|
+
statements.push(sql);
|
|
73
|
+
await query(client, sql);
|
|
74
|
+
return {
|
|
75
|
+
created: false,
|
|
76
|
+
statements
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const sql = `CREATE ROLE ${ident} WITH LOGIN PASSWORD ${passwordLiteral}`;
|
|
80
|
+
statements.push(sql);
|
|
81
|
+
await query(client, sql);
|
|
82
|
+
return {
|
|
83
|
+
created: true,
|
|
84
|
+
statements
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Grant read-only privileges inside one database (schemas, tables, sequences, defaults).
|
|
89
|
+
* @param {import("pg").Client} client
|
|
90
|
+
* @param {{ username: string }} params
|
|
91
|
+
* @returns {Promise<string[]>}
|
|
92
|
+
*/
|
|
93
|
+
async function grantReadonlyInDatabase(client, params) {
|
|
94
|
+
const { username } = params;
|
|
95
|
+
const ident = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
96
|
+
engine: "postgres",
|
|
97
|
+
name: username
|
|
98
|
+
});
|
|
99
|
+
/** @type {string[]} */
|
|
100
|
+
const statements = [];
|
|
101
|
+
const schemas = (await query(client, `SELECT nspname
|
|
102
|
+
FROM pg_namespace
|
|
103
|
+
WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
104
|
+
AND nspname NOT LIKE 'pg_temp_%'
|
|
105
|
+
AND nspname NOT LIKE 'pg_toast_temp_%'
|
|
106
|
+
ORDER BY nspname`)).rows.map((row) => row.nspname);
|
|
107
|
+
if (schemas.length === 0) schemas.push("public");
|
|
108
|
+
for (const schema of schemas) {
|
|
109
|
+
const schemaIdent = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
110
|
+
engine: "postgres",
|
|
111
|
+
name: schema
|
|
112
|
+
});
|
|
113
|
+
const grants = [
|
|
114
|
+
`GRANT USAGE ON SCHEMA ${schemaIdent} TO ${ident}`,
|
|
115
|
+
`GRANT SELECT ON ALL TABLES IN SCHEMA ${schemaIdent} TO ${ident}`,
|
|
116
|
+
`GRANT SELECT ON ALL SEQUENCES IN SCHEMA ${schemaIdent} TO ${ident}`,
|
|
117
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA ${schemaIdent} GRANT SELECT ON TABLES TO ${ident}`,
|
|
118
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA ${schemaIdent} GRANT SELECT ON SEQUENCES TO ${ident}`
|
|
119
|
+
];
|
|
120
|
+
for (const sql of grants) {
|
|
121
|
+
statements.push(sql);
|
|
122
|
+
await query(client, sql);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return statements;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create/update a Postgres role and grant read-only on one or all databases.
|
|
129
|
+
* @param {{
|
|
130
|
+
* config: ConnectionConfig;
|
|
131
|
+
* username: string;
|
|
132
|
+
* password: string;
|
|
133
|
+
* databases: string[];
|
|
134
|
+
* dryRun?: boolean;
|
|
135
|
+
* verbose?: boolean;
|
|
136
|
+
* }} params
|
|
137
|
+
* @returns {Promise<{ created: boolean; statements: string[]; databases: string[] }>}
|
|
138
|
+
*/
|
|
139
|
+
async function applyPostgresReadonly(params) {
|
|
140
|
+
const { config, username, password, databases, dryRun = false, verbose = false } = params;
|
|
141
|
+
/** @type {string[]} */
|
|
142
|
+
const statements = [];
|
|
143
|
+
let created = false;
|
|
144
|
+
if (dryRun) {
|
|
145
|
+
const ident = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
146
|
+
engine: "postgres",
|
|
147
|
+
name: username
|
|
148
|
+
});
|
|
149
|
+
statements.push(`-- Would CREATE/ALTER ROLE ${ident} WITH LOGIN PASSWORD ***`);
|
|
150
|
+
for (const database of databases) {
|
|
151
|
+
const dbIdent = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
152
|
+
engine: "postgres",
|
|
153
|
+
name: database
|
|
154
|
+
});
|
|
155
|
+
statements.push(`GRANT CONNECT ON DATABASE ${dbIdent} TO ${ident}`);
|
|
156
|
+
statements.push(`-- In database ${database}: GRANT USAGE/SELECT on all user schemas + ALTER DEFAULT PRIVILEGES`);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
created: true,
|
|
160
|
+
statements,
|
|
161
|
+
databases
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const adminDb = config.database || "postgres";
|
|
165
|
+
const adminClient = createClient(config, adminDb);
|
|
166
|
+
await adminClient.connect();
|
|
167
|
+
try {
|
|
168
|
+
if (verbose) console.log(` Connected to Postgres as ${config.user}@${config.host}/${adminDb}`);
|
|
169
|
+
const roleResult = await ensureRole(adminClient, {
|
|
170
|
+
username,
|
|
171
|
+
password
|
|
172
|
+
});
|
|
173
|
+
created = roleResult.created;
|
|
174
|
+
statements.push(...roleResult.statements);
|
|
175
|
+
const ident = require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
176
|
+
engine: "postgres",
|
|
177
|
+
name: username
|
|
178
|
+
});
|
|
179
|
+
for (const database of databases) {
|
|
180
|
+
const connectSql = `GRANT CONNECT ON DATABASE ${require_commands_db_create_readonly_user_utils.quoteIdent({
|
|
181
|
+
engine: "postgres",
|
|
182
|
+
name: database
|
|
183
|
+
})} TO ${ident}`;
|
|
184
|
+
statements.push(connectSql);
|
|
185
|
+
await query(adminClient, connectSql);
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
await adminClient.end();
|
|
189
|
+
}
|
|
190
|
+
for (const database of databases) {
|
|
191
|
+
const dbClient = createClient(config, database);
|
|
192
|
+
await dbClient.connect();
|
|
193
|
+
try {
|
|
194
|
+
if (verbose) console.log(` Granting read-only in database ${database}`);
|
|
195
|
+
const dbStatements = await grantReadonlyInDatabase(dbClient, { username });
|
|
196
|
+
statements.push(...dbStatements);
|
|
197
|
+
} finally {
|
|
198
|
+
await dbClient.end();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
created,
|
|
203
|
+
statements,
|
|
204
|
+
databases
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
exports.applyPostgresReadonly = applyPostgresReadonly;
|
|
209
|
+
exports.listPostgresDatabases = listPostgresDatabases;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List non-template databases visible to the admin user.
|
|
3
|
+
* @param {ConnectionConfig} config
|
|
4
|
+
* @returns {Promise<string[]>}
|
|
5
|
+
*/
|
|
6
|
+
export function listPostgresDatabases(config: ConnectionConfig): Promise<string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Create/update a Postgres role and grant read-only on one or all databases.
|
|
9
|
+
* @param {{
|
|
10
|
+
* config: ConnectionConfig;
|
|
11
|
+
* username: string;
|
|
12
|
+
* password: string;
|
|
13
|
+
* databases: string[];
|
|
14
|
+
* dryRun?: boolean;
|
|
15
|
+
* verbose?: boolean;
|
|
16
|
+
* }} params
|
|
17
|
+
* @returns {Promise<{ created: boolean; statements: string[]; databases: string[] }>}
|
|
18
|
+
*/
|
|
19
|
+
export function applyPostgresReadonly(params: {
|
|
20
|
+
config: ConnectionConfig;
|
|
21
|
+
username: string;
|
|
22
|
+
password: string;
|
|
23
|
+
databases: string[];
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
created: boolean;
|
|
28
|
+
statements: string[];
|
|
29
|
+
databases: string[];
|
|
30
|
+
}>;
|
|
31
|
+
export type ConnectionConfig = import("./utils.js").ConnectionConfig;
|