@wirechunk/cli 0.0.1-rc.2 → 0.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/build/main.js +181 -17
- package/package.json +1 -1
- package/src/commands/create-user.ts +16 -13
- package/src/commands/edit-admin.ts +77 -15
- package/src/commands/ext-dev/init-db.ts +3 -0
- package/src/main.ts +7 -2
- package/src/users/permissions.ts +13 -10
package/build/main.js
CHANGED
|
@@ -3892,7 +3892,7 @@ function customAlphabet(alphabet2, size = 21) {
|
|
|
3892
3892
|
return customRandom(alphabet2, size, random);
|
|
3893
3893
|
}
|
|
3894
3894
|
const alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
3895
|
-
customAlphabet(alphabet, 22);
|
|
3895
|
+
const cleanSmallId = customAlphabet(alphabet, 22);
|
|
3896
3896
|
const tinyAlphabet = "23456789abcdefghijkmnopqrstuvwxyz";
|
|
3897
3897
|
const startWithLowercaseLetterPattern = /^[a-z]/;
|
|
3898
3898
|
const startsWithLowercaseLetter = (s) => startWithLowercaseLetterPattern.test(s);
|
|
@@ -57347,9 +57347,6 @@ const detailedUniqueIntegrityConstraintViolationError = (error2) => {
|
|
|
57347
57347
|
return message;
|
|
57348
57348
|
};
|
|
57349
57349
|
const isDuplicateDatabaseError = (error2) => !!error2 && typeof error2 === "object" && "code" in error2 && error2.code === "42P04";
|
|
57350
|
-
const insertOrgResult = z.object({
|
|
57351
|
-
id: z.string()
|
|
57352
|
-
});
|
|
57353
57350
|
const insertUserResult = z.object({
|
|
57354
57351
|
id: z.string()
|
|
57355
57352
|
});
|
|
@@ -57396,14 +57393,14 @@ const createUser = async (opts, env2) => {
|
|
|
57396
57393
|
throw new Error("Either --org-id or --platform-id must be specified");
|
|
57397
57394
|
}
|
|
57398
57395
|
platformId = await db2.maybeOneFirst(
|
|
57399
|
-
distExports$1.sql.type(findOrgResult)`select "platformId" from "
|
|
57396
|
+
distExports$1.sql.type(findOrgResult)`select "platformId" from "Orgs" where "id" = ${orgId}`
|
|
57400
57397
|
);
|
|
57401
57398
|
if (!platformId) {
|
|
57402
57399
|
throw new Error(`No org found with ID ${orgId}`);
|
|
57403
57400
|
}
|
|
57404
57401
|
} else if (orgId) {
|
|
57405
57402
|
const orgPlatformId = await db2.maybeOneFirst(
|
|
57406
|
-
distExports$1.sql.type(findOrgResult)`select "platformId" from "
|
|
57403
|
+
distExports$1.sql.type(findOrgResult)`select "platformId" from "Orgs" where "id" = ${orgId}`
|
|
57407
57404
|
);
|
|
57408
57405
|
if (!orgPlatformId) {
|
|
57409
57406
|
throw new Error(`No org found with ID ${orgId}`);
|
|
@@ -57424,10 +57421,11 @@ const createUser = async (opts, env2) => {
|
|
|
57424
57421
|
console.log(`Found platform ${platform.name} (ID ${platform.id})`);
|
|
57425
57422
|
}
|
|
57426
57423
|
if (!orgId) {
|
|
57427
|
-
orgId =
|
|
57424
|
+
orgId = cleanSmallId();
|
|
57425
|
+
await db2.maybeOne(
|
|
57428
57426
|
distExports$1.sql.type(
|
|
57429
|
-
|
|
57430
|
-
)`insert into "
|
|
57427
|
+
voidSelectSchema
|
|
57428
|
+
)`insert into "Orgs" ("id", "platformId") values (${orgId}, ${platform.id})`
|
|
57431
57429
|
);
|
|
57432
57430
|
if (opts.verbose) {
|
|
57433
57431
|
console.log(`Created org ID ${orgId}`);
|
|
@@ -57437,8 +57435,15 @@ const createUser = async (opts, env2) => {
|
|
|
57437
57435
|
const user2 = await db2.one(
|
|
57438
57436
|
distExports$1.sql.type(
|
|
57439
57437
|
insertUserResult
|
|
57440
|
-
)`insert into "Users" ("platformId", "email", "emailVerified", "password", "passwordStatus", "orgId", "
|
|
57438
|
+
)`insert into "Users" ("platformId", "email", "emailVerified", "password", "passwordStatus", "orgId", "role", "status", "firstName", "lastName") values (${platformId}, ${email}, ${opts.emailVerified}, ${password}, 'Ok', ${orgId}, ${role}, ${status}, ${firstName}, ${lastName}) returning "id"`
|
|
57441
57439
|
);
|
|
57440
|
+
if (orgPrimary) {
|
|
57441
|
+
await db2.maybeOne(
|
|
57442
|
+
distExports$1.sql.type(
|
|
57443
|
+
voidSelectSchema
|
|
57444
|
+
)`update "Orgs" set "primaryUserId" = ${user2.id} where "id" = ${orgId}`
|
|
57445
|
+
);
|
|
57446
|
+
}
|
|
57442
57447
|
return user2;
|
|
57443
57448
|
});
|
|
57444
57449
|
console.log(`Created user (ID ${user.id})`);
|
|
@@ -57451,13 +57456,107 @@ const createUser = async (opts, env2) => {
|
|
|
57451
57456
|
process.exit(1);
|
|
57452
57457
|
}
|
|
57453
57458
|
};
|
|
57454
|
-
const
|
|
57459
|
+
const Permission = {
|
|
57460
|
+
/** Create (i.e., add) extensions. */
|
|
57461
|
+
CreateExtension: "CreateExtension",
|
|
57462
|
+
/** Create sites. */
|
|
57463
|
+
CreateSite: "CreateSite",
|
|
57464
|
+
/** Create page and form templates. */
|
|
57465
|
+
CreateTemplate: "CreateTemplate",
|
|
57466
|
+
/** Create a user in any org. */
|
|
57467
|
+
CreateUser: "CreateUser",
|
|
57468
|
+
/** Edit or manage everything else not covered by other permissions. */
|
|
57469
|
+
Edit: "Edit",
|
|
57470
|
+
/** Edit, including creating and deleting, any component. */
|
|
57471
|
+
EditComponent: "EditComponent",
|
|
57472
|
+
/** Edit, including creating and deleting, any course. */
|
|
57473
|
+
EditCourse: "EditCourse",
|
|
57474
|
+
/** Edit, including creating and deleting, any custom component. */
|
|
57475
|
+
EditCustomComponent: "EditCustomComponent",
|
|
57476
|
+
/** Edit, including creating and deleting, any custom field. */
|
|
57477
|
+
EditCustomField: "EditCustomField",
|
|
57478
|
+
/** Edit any customer site, including its pages and forms, but not necessarily domain. */
|
|
57479
|
+
EditCustomerSite: "EditCustomerSite",
|
|
57480
|
+
/** Edit any extension. */
|
|
57481
|
+
EditExtension: "EditExtension",
|
|
57482
|
+
/** Edit any help ticket's status. */
|
|
57483
|
+
EditHelpTicketStatus: "EditHelpTicketStatus",
|
|
57484
|
+
/** Edit any platform site, including its pages and forms, but not necessarily domain. */
|
|
57485
|
+
EditPlatformSite: "EditPlatformSite",
|
|
57486
|
+
/** Edit, including creating and deleting, any sequence. */
|
|
57487
|
+
EditSequence: "EditSequence",
|
|
57488
|
+
/** Edit any user's position in a sequence. */
|
|
57489
|
+
EditSequenceUser: "EditSequenceUser",
|
|
57490
|
+
/** Edit any site's settings, pages, forms, and layouts. */
|
|
57491
|
+
EditSite: "EditSite",
|
|
57492
|
+
/** Edit any site's domain. */
|
|
57493
|
+
EditSiteDomain: "EditSiteDomain",
|
|
57494
|
+
/** Edit any site's TLS certificate, including creating and deleting certificates. Does not including editing TLS certificates. */
|
|
57495
|
+
EditSiteTlsCertificate: "EditSiteTlsCertificate",
|
|
57496
|
+
/** Edit any subscription. */
|
|
57497
|
+
EditSubscription: "EditSubscription",
|
|
57498
|
+
/** Edit any page and form template. */
|
|
57499
|
+
EditTemplate: "EditTemplate",
|
|
57500
|
+
/** Edit any user's email address. */
|
|
57501
|
+
EditUserEmail: "EditUserEmail",
|
|
57502
|
+
/** Edit which org any user is in and whether a user is an org owner. */
|
|
57503
|
+
EditUserOrg: "EditUserOrg",
|
|
57504
|
+
/** Edit any user's first and last name. */
|
|
57505
|
+
EditUserProfile: "EditUserProfile",
|
|
57506
|
+
/** Edit any user's role. */
|
|
57507
|
+
EditUserRole: "EditUserRole",
|
|
57508
|
+
/** Edit any user's status. */
|
|
57509
|
+
EditUserStatus: "EditUserStatus",
|
|
57510
|
+
/** Sync any form template to forms. */
|
|
57511
|
+
SyncFormTemplateToForms: "SyncFormTemplateToForms",
|
|
57512
|
+
/** Sync any page template to pages. */
|
|
57513
|
+
SyncPageTemplateToPages: "SyncPageTemplateToPages",
|
|
57514
|
+
/** View anything except for sites. */
|
|
57515
|
+
View: "View",
|
|
57516
|
+
/** View any course. */
|
|
57517
|
+
ViewCourse: "ViewCourse",
|
|
57518
|
+
/** View any extension. */
|
|
57519
|
+
ViewExtension: "ViewExtension",
|
|
57520
|
+
/** View any site, including pages, forms, and layouts, and components. */
|
|
57521
|
+
ViewSite: "ViewSite",
|
|
57522
|
+
/** View any page or form template. */
|
|
57523
|
+
ViewTemplate: "ViewTemplate"
|
|
57524
|
+
};
|
|
57525
|
+
const allPermissions = Object.values(Permission);
|
|
57526
|
+
const revokeAllUserPlatformPermissions = async ({
|
|
57527
|
+
platformAdminId
|
|
57528
|
+
}, db) => {
|
|
57529
|
+
await db.query(
|
|
57530
|
+
distExports$1.sql.type(
|
|
57531
|
+
voidSelectSchema
|
|
57532
|
+
)`delete from "PlatformAdminPermissions" where "id" = ${platformAdminId}`
|
|
57533
|
+
);
|
|
57534
|
+
};
|
|
57535
|
+
const grantAllUserPlatformPermissions = async ({
|
|
57536
|
+
platformAdminId
|
|
57537
|
+
}, db) => {
|
|
57538
|
+
await db.query(
|
|
57539
|
+
distExports$1.sql.type(
|
|
57540
|
+
voidSelectSchema
|
|
57541
|
+
)`insert into "PlatformAdminPermissions" ("id", "platformAdminId", "permission") values ${distExports$1.sql.join(
|
|
57542
|
+
allPermissions.map(
|
|
57543
|
+
(permission) => distExports$1.sql.fragment`(${cleanSmallId()}, ${platformAdminId}, ${permission})`
|
|
57544
|
+
),
|
|
57545
|
+
distExports$1.sql.fragment`,`
|
|
57546
|
+
)} on conflict ("platformAdminId", "permission") do nothing`
|
|
57547
|
+
);
|
|
57548
|
+
};
|
|
57549
|
+
const findPlatformAdminSchema = z.object({
|
|
57455
57550
|
id: z.string(),
|
|
57456
|
-
platformId: z.string()
|
|
57551
|
+
platformId: z.string(),
|
|
57552
|
+
active: z.boolean()
|
|
57553
|
+
});
|
|
57554
|
+
const findUserSchema = z.object({
|
|
57555
|
+
id: z.string()
|
|
57457
57556
|
});
|
|
57458
57557
|
const editAdmin = async (opts, env2) => {
|
|
57459
57558
|
const db = await distExports$1.createPool(requireCoreDbUrl(env2));
|
|
57460
|
-
const { platformId, userId, owner, revokeAllPermissions } = opts;
|
|
57559
|
+
const { platformId, userId, owner, active, revokeAllPermissions } = opts;
|
|
57461
57560
|
if (owner && revokeAllPermissions) {
|
|
57462
57561
|
console.error(
|
|
57463
57562
|
"Cannot set a user as a platform owner and revoke all permissions at the same time"
|
|
@@ -57466,9 +57565,9 @@ const editAdmin = async (opts, env2) => {
|
|
|
57466
57565
|
}
|
|
57467
57566
|
try {
|
|
57468
57567
|
await db.transaction(async (db2) => {
|
|
57469
|
-
|
|
57568
|
+
let platformAdmin = await db2.maybeOne(
|
|
57470
57569
|
distExports$1.sql.type(
|
|
57471
|
-
|
|
57570
|
+
findPlatformAdminSchema
|
|
57472
57571
|
)`select "id" from "PlatformAdmins" where "platformId" = ${platformId} and "userId" = ${userId}`
|
|
57473
57572
|
);
|
|
57474
57573
|
if (!platformAdmin) {
|
|
@@ -57479,6 +57578,66 @@ const editAdmin = async (opts, env2) => {
|
|
|
57479
57578
|
throw new Error(`User with ID ${userId} not found`);
|
|
57480
57579
|
}
|
|
57481
57580
|
}
|
|
57581
|
+
if (owner) {
|
|
57582
|
+
if (!platformAdmin) {
|
|
57583
|
+
platformAdmin = await db2.one(
|
|
57584
|
+
distExports$1.sql.type(findPlatformAdminSchema)`
|
|
57585
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
57586
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, ${active ?? true}, true)
|
|
57587
|
+
returning "id", "platformId", "active"
|
|
57588
|
+
`
|
|
57589
|
+
);
|
|
57590
|
+
}
|
|
57591
|
+
await grantAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db2);
|
|
57592
|
+
if (opts.verbose) {
|
|
57593
|
+
console.log("Set the user as an owner on the platform");
|
|
57594
|
+
}
|
|
57595
|
+
} else if (owner === false) {
|
|
57596
|
+
if (platformAdmin) {
|
|
57597
|
+
await db2.query(
|
|
57598
|
+
distExports$1.sql.type(voidSelectSchema)`
|
|
57599
|
+
update "PlatformAdmins"
|
|
57600
|
+
set "owner" = false
|
|
57601
|
+
where "id" = ${platformAdmin.id}
|
|
57602
|
+
`
|
|
57603
|
+
);
|
|
57604
|
+
if (opts.verbose) {
|
|
57605
|
+
console.log("Removed the user’s owner privileges on the platform");
|
|
57606
|
+
}
|
|
57607
|
+
} else {
|
|
57608
|
+
console.log("This user is not an admin on this platform");
|
|
57609
|
+
}
|
|
57610
|
+
}
|
|
57611
|
+
if (typeof active === "boolean") {
|
|
57612
|
+
if (platformAdmin) {
|
|
57613
|
+
await db2.query(
|
|
57614
|
+
distExports$1.sql.type(voidSelectSchema)`
|
|
57615
|
+
update "PlatformAdmins"
|
|
57616
|
+
set "active" = ${active}
|
|
57617
|
+
where "id" = ${platformAdmin.id}
|
|
57618
|
+
`
|
|
57619
|
+
);
|
|
57620
|
+
} else {
|
|
57621
|
+
if (active) {
|
|
57622
|
+
await db2.one(
|
|
57623
|
+
distExports$1.sql.type(voidSelectSchema)`
|
|
57624
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
57625
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, false, ${active})
|
|
57626
|
+
`
|
|
57627
|
+
);
|
|
57628
|
+
} else {
|
|
57629
|
+
console.log("This user is not an admin on this platform");
|
|
57630
|
+
}
|
|
57631
|
+
}
|
|
57632
|
+
}
|
|
57633
|
+
if (revokeAllPermissions) {
|
|
57634
|
+
if (platformAdmin) {
|
|
57635
|
+
await revokeAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db2);
|
|
57636
|
+
console.log("Revoked all platform permissions of user");
|
|
57637
|
+
} else {
|
|
57638
|
+
console.log("This user is not an admin on this platform");
|
|
57639
|
+
}
|
|
57640
|
+
}
|
|
57482
57641
|
});
|
|
57483
57642
|
} catch (e) {
|
|
57484
57643
|
if (e instanceof distExports$1.UniqueIntegrityConstraintViolationError) {
|
|
@@ -57654,6 +57813,9 @@ const initDb = async (opts, env2) => {
|
|
|
57654
57813
|
end if;
|
|
57655
57814
|
end; $$
|
|
57656
57815
|
`);
|
|
57816
|
+
await db.query(distExports$1.sql.unsafe`
|
|
57817
|
+
grant ${extRoleIdent} to ${distExports$1.sql.identifier([coreDbUrlObject.username])}
|
|
57818
|
+
`);
|
|
57657
57819
|
await db.query(distExports$1.sql.unsafe`
|
|
57658
57820
|
grant connect, create, temporary on database ${distExports$1.sql.identifier([extDb.dbName])} to ${extRoleIdent}
|
|
57659
57821
|
`);
|
|
@@ -57730,9 +57892,11 @@ extDev.command("db-connect-info").description(
|
|
|
57730
57892
|
"--extension-id <string>",
|
|
57731
57893
|
"the ID of the extension, can be set with an EXTENSION_ID environment variable instead"
|
|
57732
57894
|
).action(withOptionsAndEnv(dbConnectInfo));
|
|
57733
|
-
extDev.command("init-db").description(
|
|
57895
|
+
extDev.command("init-db").description(
|
|
57896
|
+
"initialize a development database for an extension, useful for testing, assumes the extension role exists"
|
|
57897
|
+
).option(
|
|
57734
57898
|
"--extension-id <string>",
|
|
57735
57899
|
"the ID of the extension, can be set with an EXTENSION_ID environment variable instead"
|
|
57736
57900
|
).option("--db-name <string>", "a custom name for the database, applicable only for testing").action(withOptionsAndEnv(initDb));
|
|
57737
|
-
program.command("edit-admin").description("edit a platform admin user").requiredOption("--platform-id <string>", "the ID of the platform to edit").requiredOption("--user-id <string>", "the ID of the admin user to edit").option("--owner", "grants the user full permission to manage everything on the platform").option("--revoke-all-permissions", "revokes all permission of the user on their platform").action(withOptionsAndEnv(editAdmin));
|
|
57901
|
+
program.command("edit-admin").description("edit a platform admin user or make a user a platform admin").requiredOption("--platform-id <string>", "the ID of the platform to edit").requiredOption("--user-id <string>", "the ID of the admin user to edit").option("--owner", "grants the user full permission to manage everything on the platform").option("--no-owner", "removes owner privileges on the platform").option("--active", "activates or deactivates the user’s admin access on the platform").option("--no-active", "deactivates the user’s admin access on the platform").option("--revoke-all-permissions", "revokes all permission of the user on their platform").action(withOptionsAndEnv(editAdmin));
|
|
57738
57902
|
await program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hashPassword, validatePasswordComplexity } from '@wirechunk/backend-lib/passwords.ts';
|
|
2
|
+
import { cleanSmallId } from '@wirechunk/lib/clean-small-id.ts';
|
|
2
3
|
import { normalizeEmailAddress } from '@wirechunk/lib/emails.ts';
|
|
3
4
|
import { createPool, sql, UniqueIntegrityConstraintViolationError } from 'slonik';
|
|
4
5
|
import { z } from 'zod';
|
|
@@ -6,10 +7,7 @@ import type { Env } from '../env.ts';
|
|
|
6
7
|
import { requireCoreDbUrl } from '../env.ts';
|
|
7
8
|
import { detailedUniqueIntegrityConstraintViolationError } from '../errors.ts';
|
|
8
9
|
import type { WithGlobalOptions } from '../global-options.ts';
|
|
9
|
-
|
|
10
|
-
const insertOrgResult = z.object({
|
|
11
|
-
id: z.string(),
|
|
12
|
-
});
|
|
10
|
+
import { voidSelectSchema } from '../util.ts';
|
|
13
11
|
|
|
14
12
|
const insertUserResult = z.object({
|
|
15
13
|
id: z.string(),
|
|
@@ -78,7 +76,7 @@ export const createUser = async (
|
|
|
78
76
|
throw new Error('Either --org-id or --platform-id must be specified');
|
|
79
77
|
}
|
|
80
78
|
platformId = await db.maybeOneFirst(
|
|
81
|
-
sql.type(findOrgResult)`select "platformId" from "
|
|
79
|
+
sql.type(findOrgResult)`select "platformId" from "Orgs" where "id" = ${orgId}`,
|
|
82
80
|
);
|
|
83
81
|
if (!platformId) {
|
|
84
82
|
throw new Error(`No org found with ID ${orgId}`);
|
|
@@ -86,7 +84,7 @@ export const createUser = async (
|
|
|
86
84
|
} else if (orgId) {
|
|
87
85
|
// Verify that the specified org belongs to the specified platform.
|
|
88
86
|
const orgPlatformId = await db.maybeOneFirst(
|
|
89
|
-
sql.type(findOrgResult)`select "platformId" from "
|
|
87
|
+
sql.type(findOrgResult)`select "platformId" from "Orgs" where "id" = ${orgId}`,
|
|
90
88
|
);
|
|
91
89
|
if (!orgPlatformId) {
|
|
92
90
|
throw new Error(`No org found with ID ${orgId}`);
|
|
@@ -107,10 +105,11 @@ export const createUser = async (
|
|
|
107
105
|
console.log(`Found platform ${platform.name} (ID ${platform.id})`);
|
|
108
106
|
}
|
|
109
107
|
if (!orgId) {
|
|
110
|
-
orgId =
|
|
108
|
+
orgId = cleanSmallId();
|
|
109
|
+
await db.maybeOne(
|
|
111
110
|
sql.type(
|
|
112
|
-
|
|
113
|
-
)`insert into "
|
|
111
|
+
voidSelectSchema,
|
|
112
|
+
)`insert into "Orgs" ("id", "platformId") values (${orgId}, ${platform.id})`,
|
|
114
113
|
);
|
|
115
114
|
if (opts.verbose) {
|
|
116
115
|
console.log(`Created org ID ${orgId}`);
|
|
@@ -121,12 +120,16 @@ export const createUser = async (
|
|
|
121
120
|
const user = await db.one(
|
|
122
121
|
sql.type(
|
|
123
122
|
insertUserResult,
|
|
124
|
-
)`insert into "Users" ("platformId", "email", "emailVerified", "password", "passwordStatus", "orgId", "
|
|
123
|
+
)`insert into "Users" ("platformId", "email", "emailVerified", "password", "passwordStatus", "orgId", "role", "status", "firstName", "lastName") values (${platformId}, ${email}, ${opts.emailVerified}, ${password}, 'Ok', ${orgId}, ${role}, ${status}, ${firstName}, ${lastName}) returning "id"`,
|
|
125
124
|
);
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if (orgPrimary) {
|
|
127
|
+
await db.maybeOne(
|
|
128
|
+
sql.type(
|
|
129
|
+
voidSelectSchema,
|
|
130
|
+
)`update "Orgs" set "primaryUserId" = ${user.id} where "id" = ${orgId}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
130
133
|
|
|
131
134
|
return user;
|
|
132
135
|
});
|
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
import { cleanSmallId } from '@wirechunk/lib/clean-small-id.ts';
|
|
1
2
|
import { createPool, sql, UniqueIntegrityConstraintViolationError } from 'slonik';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { Env } from '../env.ts';
|
|
4
5
|
import { requireCoreDbUrl } from '../env.ts';
|
|
5
6
|
import { detailedUniqueIntegrityConstraintViolationError } from '../errors.ts';
|
|
6
7
|
import type { WithGlobalOptions } from '../global-options.ts';
|
|
8
|
+
import {
|
|
9
|
+
grantAllUserPlatformPermissions,
|
|
10
|
+
revokeAllUserPlatformPermissions,
|
|
11
|
+
} from '../users/permissions.ts';
|
|
12
|
+
import { voidSelectSchema } from '../util.ts';
|
|
7
13
|
|
|
8
|
-
const
|
|
14
|
+
const findPlatformAdminSchema = z.object({
|
|
9
15
|
id: z.string(),
|
|
10
16
|
platformId: z.string(),
|
|
17
|
+
active: z.boolean(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const findUserSchema = z.object({
|
|
21
|
+
id: z.string(),
|
|
11
22
|
});
|
|
12
23
|
|
|
13
24
|
type EditAdminOptions = {
|
|
14
25
|
platformId: string;
|
|
15
26
|
userId: string;
|
|
16
27
|
owner?: boolean;
|
|
28
|
+
active?: boolean;
|
|
17
29
|
revokeAllPermissions?: boolean;
|
|
18
30
|
};
|
|
19
31
|
|
|
@@ -22,7 +34,7 @@ export const editAdmin = async (
|
|
|
22
34
|
env: Env,
|
|
23
35
|
): Promise<void> => {
|
|
24
36
|
const db = await createPool(requireCoreDbUrl(env));
|
|
25
|
-
const { platformId, userId, owner, revokeAllPermissions } = opts;
|
|
37
|
+
const { platformId, userId, owner, active, revokeAllPermissions } = opts;
|
|
26
38
|
|
|
27
39
|
if (owner && revokeAllPermissions) {
|
|
28
40
|
console.error(
|
|
@@ -33,9 +45,9 @@ export const editAdmin = async (
|
|
|
33
45
|
|
|
34
46
|
try {
|
|
35
47
|
await db.transaction(async (db) => {
|
|
36
|
-
|
|
48
|
+
let platformAdmin = await db.maybeOne(
|
|
37
49
|
sql.type(
|
|
38
|
-
|
|
50
|
+
findPlatformAdminSchema,
|
|
39
51
|
)`select "id" from "PlatformAdmins" where "platformId" = ${platformId} and "userId" = ${userId}`,
|
|
40
52
|
);
|
|
41
53
|
if (!platformAdmin) {
|
|
@@ -46,17 +58,67 @@ export const editAdmin = async (
|
|
|
46
58
|
throw new Error(`User with ID ${userId} not found`);
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
if (owner) {
|
|
62
|
+
if (!platformAdmin) {
|
|
63
|
+
platformAdmin = await db.one(
|
|
64
|
+
sql.type(findPlatformAdminSchema)`
|
|
65
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
66
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, ${active ?? true}, true)
|
|
67
|
+
returning "id", "platformId", "active"
|
|
68
|
+
`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
await grantAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db);
|
|
72
|
+
if (opts.verbose) {
|
|
73
|
+
console.log('Set the user as an owner on the platform');
|
|
74
|
+
}
|
|
75
|
+
} else if (owner === false) {
|
|
76
|
+
if (platformAdmin) {
|
|
77
|
+
await db.query(
|
|
78
|
+
sql.type(voidSelectSchema)`
|
|
79
|
+
update "PlatformAdmins"
|
|
80
|
+
set "owner" = false
|
|
81
|
+
where "id" = ${platformAdmin.id}
|
|
82
|
+
`,
|
|
83
|
+
);
|
|
84
|
+
if (opts.verbose) {
|
|
85
|
+
console.log('Removed the user’s owner privileges on the platform');
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log('This user is not an admin on this platform');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (typeof active === 'boolean') {
|
|
92
|
+
if (platformAdmin) {
|
|
93
|
+
await db.query(
|
|
94
|
+
sql.type(voidSelectSchema)`
|
|
95
|
+
update "PlatformAdmins"
|
|
96
|
+
set "active" = ${active}
|
|
97
|
+
where "id" = ${platformAdmin.id}
|
|
98
|
+
`,
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
if (active) {
|
|
102
|
+
// Automatically create a platform admin.
|
|
103
|
+
await db.one(
|
|
104
|
+
sql.type(voidSelectSchema)`
|
|
105
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
106
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, false, ${active})
|
|
107
|
+
`,
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
console.log('This user is not an admin on this platform');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (revokeAllPermissions) {
|
|
115
|
+
if (platformAdmin) {
|
|
116
|
+
await revokeAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db);
|
|
117
|
+
console.log('Revoked all platform permissions of user');
|
|
118
|
+
} else {
|
|
119
|
+
console.log('This user is not an admin on this platform');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
60
122
|
});
|
|
61
123
|
} catch (e) {
|
|
62
124
|
if (e instanceof UniqueIntegrityConstraintViolationError) {
|
|
@@ -190,6 +190,9 @@ export const initDb = async (
|
|
|
190
190
|
end if;
|
|
191
191
|
end; $$
|
|
192
192
|
`);
|
|
193
|
+
await db.query(sql.unsafe`
|
|
194
|
+
grant ${extRoleIdent} to ${sql.identifier([coreDbUrlObject.username])}
|
|
195
|
+
`);
|
|
193
196
|
|
|
194
197
|
await db.query(sql.unsafe`
|
|
195
198
|
grant connect, create, temporary on database ${sql.identifier([extDb.dbName])} to ${extRoleIdent}
|
package/src/main.ts
CHANGED
|
@@ -106,7 +106,9 @@ extDev
|
|
|
106
106
|
|
|
107
107
|
extDev
|
|
108
108
|
.command('init-db')
|
|
109
|
-
.description(
|
|
109
|
+
.description(
|
|
110
|
+
'initialize a development database for an extension, useful for testing, assumes the extension role exists',
|
|
111
|
+
)
|
|
110
112
|
.option(
|
|
111
113
|
'--extension-id <string>',
|
|
112
114
|
'the ID of the extension, can be set with an EXTENSION_ID environment variable instead',
|
|
@@ -116,10 +118,13 @@ extDev
|
|
|
116
118
|
|
|
117
119
|
program
|
|
118
120
|
.command('edit-admin')
|
|
119
|
-
.description('edit a platform admin user')
|
|
121
|
+
.description('edit a platform admin user or make a user a platform admin')
|
|
120
122
|
.requiredOption('--platform-id <string>', 'the ID of the platform to edit')
|
|
121
123
|
.requiredOption('--user-id <string>', 'the ID of the admin user to edit')
|
|
122
124
|
.option('--owner', 'grants the user full permission to manage everything on the platform')
|
|
125
|
+
.option('--no-owner', 'removes owner privileges on the platform')
|
|
126
|
+
.option('--active', 'activates or deactivates the user’s admin access on the platform')
|
|
127
|
+
.option('--no-active', 'deactivates the user’s admin access on the platform')
|
|
123
128
|
.option('--revoke-all-permissions', 'revokes all permission of the user on their platform')
|
|
124
129
|
.action(withOptionsAndEnv(editAdmin));
|
|
125
130
|
|
package/src/users/permissions.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cleanSmallId } from '@wirechunk/lib/clean-small-id.ts';
|
|
1
2
|
import { Permission } from '@wirechunk/lib/graphql-api-enums.ts';
|
|
2
3
|
import type { CommonQueryMethods } from 'slonik';
|
|
3
4
|
import { sql } from 'slonik';
|
|
@@ -7,33 +8,35 @@ export const allPermissions = Object.values(Permission);
|
|
|
7
8
|
|
|
8
9
|
export const revokeAllUserPlatformPermissions = async (
|
|
9
10
|
{
|
|
10
|
-
|
|
11
|
+
platformAdminId,
|
|
11
12
|
}: {
|
|
12
|
-
|
|
13
|
+
platformAdminId: string;
|
|
13
14
|
},
|
|
14
15
|
db: CommonQueryMethods,
|
|
15
16
|
): Promise<void> => {
|
|
16
17
|
await db.query(
|
|
17
|
-
sql.type(
|
|
18
|
+
sql.type(
|
|
19
|
+
voidSelectSchema,
|
|
20
|
+
)`delete from "PlatformAdminPermissions" where "id" = ${platformAdminId}`,
|
|
18
21
|
);
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
export const grantAllUserPlatformPermissions = async (
|
|
22
25
|
{
|
|
23
|
-
|
|
24
|
-
platformId,
|
|
26
|
+
platformAdminId,
|
|
25
27
|
}: {
|
|
26
|
-
|
|
27
|
-
platformId: string;
|
|
28
|
+
platformAdminId: string;
|
|
28
29
|
},
|
|
29
30
|
db: CommonQueryMethods,
|
|
30
31
|
): Promise<void> => {
|
|
31
32
|
await db.query(
|
|
32
33
|
sql.type(
|
|
33
34
|
voidSelectSchema,
|
|
34
|
-
)`insert into "
|
|
35
|
-
allPermissions.map(
|
|
35
|
+
)`insert into "PlatformAdminPermissions" ("id", "platformAdminId", "permission") values ${sql.join(
|
|
36
|
+
allPermissions.map(
|
|
37
|
+
(permission) => sql.fragment`(${cleanSmallId()}, ${platformAdminId}, ${permission})`,
|
|
38
|
+
),
|
|
36
39
|
sql.fragment`,`,
|
|
37
|
-
)} on conflict
|
|
40
|
+
)} on conflict ("platformAdminId", "permission") do nothing`,
|
|
38
41
|
);
|
|
39
42
|
};
|