fix(housekeeping): close final authorization gaps
This commit is contained in:
1 parent
2b8f73a91d
commit
222535e116
9 files changed
+219
-44
No files matched your search
@@ -149,6 +149,8 @@ function operationCapability(operation: PeopleMutationOperation) {
|
||||
if (
|
||||
operation === "user.ban" ||
|
||||
operation === "user.unban" ||
|
||||
operation === "users.bulk-ban" ||
|
||||
operation === "users.bulk-unban" ||
|
||||
operation === "ban.create" ||
|
||||
operation === "ban.lift" ||
|
||||
operation === "help-ticket.unban"
|
||||
@@ -400,7 +402,7 @@ async function loadTarget(
|
||||
if (
|
||||
guardHierarchy &&
|
||||
target.rank >= context.capability.actor.rank &&
|
||||
context.capability.actor.rank < 7
|
||||
!context.capability.isSuperAdmin
|
||||
) {
|
||||
throw new PeopleMutationFailure(
|
||||
"FORBIDDEN",
|
||||
@@ -410,6 +412,43 @@ async function loadTarget(
|
||||
return target;
|
||||
}
|
||||
|
||||
async function assertBulkTargetHierarchy(
|
||||
userIds: readonly number[],
|
||||
context: PeopleMutationContext,
|
||||
): Promise<void> {
|
||||
const uniqueIds = [...new Set(userIds)];
|
||||
const targets = await db
|
||||
.select({ id: User.id, rank: User.rank })
|
||||
.from(User)
|
||||
.where(inArray(User.id, uniqueIds));
|
||||
if (targets.length !== uniqueIds.length) {
|
||||
throw new PeopleMutationFailure(
|
||||
"NOT_FOUND",
|
||||
"errors.housekeeping.notFound",
|
||||
);
|
||||
}
|
||||
if (
|
||||
!context.capability.isSuperAdmin &&
|
||||
targets.some((target) => target.rank >= context.capability.actor.rank)
|
||||
) {
|
||||
throw new PeopleMutationFailure(
|
||||
"FORBIDDEN",
|
||||
"errors.housekeeping.forbidden",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertConfiguredRankExists(rank: number): Promise<void> {
|
||||
const [rows] = (await db.execute(
|
||||
sql`SELECT id FROM permission_ranks WHERE id = ${rank} LIMIT 1`,
|
||||
)) as unknown as [unknown[], unknown];
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
throw new PeopleMutationFailure("VALIDATION", "errors.validation.invalid", {
|
||||
rank: ["errors.validation.invalid"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function targetSnapshot(target: TargetUser) {
|
||||
return {
|
||||
id: target.id,
|
||||
@@ -592,15 +631,18 @@ async function executeUserMutation(
|
||||
if (operation === "user.update") {
|
||||
const fields = record(data.fields);
|
||||
const nextRank = fields.rank;
|
||||
if (
|
||||
nextRank !== undefined &&
|
||||
positiveInteger(nextRank) >= context.capability.actor.rank &&
|
||||
context.capability.actor.rank < 7
|
||||
) {
|
||||
throw new PeopleMutationFailure(
|
||||
"FORBIDDEN",
|
||||
"errors.housekeeping.forbidden",
|
||||
);
|
||||
if (nextRank !== undefined) {
|
||||
const rank = positiveInteger(nextRank);
|
||||
if (
|
||||
rank >= context.capability.actor.rank &&
|
||||
!context.capability.isSuperAdmin
|
||||
) {
|
||||
throw new PeopleMutationFailure(
|
||||
"FORBIDDEN",
|
||||
"errors.housekeeping.forbidden",
|
||||
);
|
||||
}
|
||||
await assertConfiguredRankExists(rank);
|
||||
}
|
||||
const userPatch = Object.fromEntries(
|
||||
["username", "mail", "rank", "motto", "credits", "pixels"].flatMap(
|
||||
@@ -1072,6 +1114,7 @@ async function executeBulkMutation(
|
||||
): Promise<PeopleMutationSnapshot> {
|
||||
const data = record(input);
|
||||
const ids = userIds(data.userIds, context.legacy);
|
||||
await assertBulkTargetHierarchy(ids, context);
|
||||
const failures: Array<{ userId: number; reason: string }> = [];
|
||||
const externalSyncFailures: Array<{ userId: number; reason: string }> = [];
|
||||
const externalSyncFailureReason =
|
||||
|
||||
Reference in new issue
Block a user