harden admin api authorization and add authz regression tests

This commit is contained in:
dimitar committed 2026-03-29 11:23:49 +02:00
1 parent 272c9b36dd
commit 10b58245f5
28 files changed
+1336 -240

No files matched your search

+59 -9
View File
@@ -580,26 +580,76 @@ export async function PUT(request: NextRequest) {
export async function DELETE(request: NextRequest) {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return unauthorizedResponse();
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(clerkUserId, db);
if (!currentUser) {
return forbiddenResponse("Current user not found");
}
const canDeleteUsers =
currentUser.role === "admin" || currentUser.role === "superAdmin";
if (!canDeleteUsers) {
return forbiddenResponse("Only admins can delete users");
}
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");
const body = await request.json().catch(() => ({}));
const { ids } = body;
const targetIds: string[] = Array.isArray(ids)
? ids.filter(
(userId: unknown): userId is string => typeof userId === "string",
)
: id
? [id]
: [];
if (targetIds.length === 0) {
return badRequestResponse("User ID or IDs array required");
}
if (targetIds.includes(clerkUserId)) {
return forbiddenResponse("Cannot delete your own account");
}
const targetUsers = await Promise.all(
targetIds.map((targetId) => db.getUserById(targetId)),
);
const missingTargets = targetUsers.filter((user) => !user).length;
if (missingTargets > 0) {
return notFoundResponse("One or more users were not found");
}
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId) {
return forbiddenResponse("No gym assigned to current user");
}
const hasCrossGymTarget = targetUsers.some(
(targetUser) => targetUser && targetUser.gymId !== currentUser.gymId,
);
if (hasCrossGymTarget) {
return forbiddenResponse("Cannot delete users from other gyms");
}
}
if (ids && Array.isArray(ids)) {
// Bulk delete
await Promise.all(ids.map((userId: string) => db.deleteUser(userId)));
return successResponse({ deleted: ids.length });
} else if (id) {
// Single delete
const user = await db.getUserById(id);
if (!user) {
return notFoundResponse("User not found");
}
await db.deleteUser(id);
return successResponse({ deleted: 1 });
} else {
return badRequestResponse("User ID or IDs array required");
await db.deleteUser(id as string);
return successResponse({ deleted: 1 });
}
} catch (error) {
log.error("Failed to delete user(s)", error);