aboutsummaryrefslogtreecommitdiff
path: root/src/routes/api/badges
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-06-01 15:11:27 +0000
committerFuwn <[email protected]>2026-06-01 15:11:27 +0000
commit7c49986a5646e892a115725abb43cf97b5d7c1d2 (patch)
treeed842f6009cdd07f0ff890bfde3be90c49759970 /src/routes/api/badges
parentfix(badges): drop content-visibility that clipped tooltip and tilt (diff)
downloaddue.moe-7c49986a5646e892a115725abb43cf97b5d7c1d2.tar.xz
due.moe-7c49986a5646e892a115725abb43cf97b5d7c1d2.zip
fix(security): authorize shadowHide target in badges endpoint (IDOR)
PUT /api/badges?shadowHide=<userId> called setShadowHidden on an arbitrary user_id with no ownership/privilege check, so any logged-in user could flip shadow_hidden on another user's badges (e.g. un-hide moderator-hidden ones). The GraphQL path already guarded this; the REST twin didn't. Extract the owner-or-privileged check into a shared isOwnerOrPrivileged helper, use it in both the REST endpoint and the GraphQL resolver, and add a regression test.
Diffstat (limited to 'src/routes/api/badges')
-rw-r--r--src/routes/api/badges/+server.ts14
1 files changed, 9 insertions, 5 deletions
diff --git a/src/routes/api/badges/+server.ts b/src/routes/api/badges/+server.ts
index 2673273c..10b63125 100644
--- a/src/routes/api/badges/+server.ts
+++ b/src/routes/api/badges/+server.ts
@@ -16,6 +16,7 @@ import {
import { decodeAuthCookieOrNull } from "$lib/Effect/authCookie";
import { decodeRequestJsonOrThrow } from "$lib/Effect/requestBody";
import { appOrigin, appOriginHeaders } from "$lib/Utility/appOrigin";
+import { isOwnerOrPrivileged } from "$lib/Utility/authorisation";
import privilegedUser from "$lib/Utility/privilegedUser";
const unauthorised = () => new Response("Unauthorised", { status: 401 });
@@ -76,11 +77,14 @@ export const PUT = async ({ cookies, url, request }) => {
if (!identity) return unauthorised();
const authorised = privilegedUser(identity.id);
- if (url.searchParams.get("shadowHide"))
- await setShadowHidden(
- Number(url.searchParams.get("shadowHide")),
- authorised,
- );
+ if (url.searchParams.get("shadowHide")) {
+ const targetUserId = Number(url.searchParams.get("shadowHide"));
+
+ if (!isOwnerOrPrivileged(identity.id, targetUserId, authorised))
+ return unauthorised();
+
+ await setShadowHidden(targetUserId, authorised);
+ }
if (url.searchParams.get("import") || undefined) {
const importedBadges = await decodeRequestJsonOrThrow(