aboutsummaryrefslogtreecommitdiff
path: root/src/lib
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/lib
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/lib')
-rw-r--r--src/lib/Utility/authorisation.test.ts20
-rw-r--r--src/lib/Utility/authorisation.ts9
2 files changed, 29 insertions, 0 deletions
diff --git a/src/lib/Utility/authorisation.test.ts b/src/lib/Utility/authorisation.test.ts
new file mode 100644
index 00000000..0027782b
--- /dev/null
+++ b/src/lib/Utility/authorisation.test.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from "vitest";
+import { isOwnerOrPrivileged } from "./authorisation";
+
+describe("isOwnerOrPrivileged", () => {
+ it("allows the owner to act on their own resources", () => {
+ expect(isOwnerOrPrivileged(7, 7, false)).toBe(true);
+ });
+
+ it("allows a privileged user to act on anyone", () => {
+ expect(isOwnerOrPrivileged(7, 999, true)).toBe(true);
+ });
+
+ it("blocks a non-privileged user acting on someone else (the IDOR case)", () => {
+ expect(isOwnerOrPrivileged(7, 999, false)).toBe(false);
+ });
+
+ it("allows a privileged owner (both conditions)", () => {
+ expect(isOwnerOrPrivileged(7, 7, true)).toBe(true);
+ });
+});
diff --git a/src/lib/Utility/authorisation.ts b/src/lib/Utility/authorisation.ts
new file mode 100644
index 00000000..c6b64414
--- /dev/null
+++ b/src/lib/Utility/authorisation.ts
@@ -0,0 +1,9 @@
+/**
+ * Whether a caller may act on resources belonging to `targetUserId`: either the
+ * caller owns them, or the caller is a privileged (allow-listed) user.
+ */
+export const isOwnerOrPrivileged = (
+ callerUserId: number,
+ targetUserId: number,
+ privileged: boolean,
+) => privileged || callerUserId === targetUserId;