fix: idor checks on all exposed endpoints

This commit is contained in:
2026-03-26 02:13:01 +01:00
parent cd050f9974
commit d76e8922cc
8 changed files with 81 additions and 18 deletions

View File

@@ -2,11 +2,12 @@ import { json, error } from '@sveltejs/kit';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { mappool, mappoolSlot } from '$lib/server/db/schema'; import { mappool, mappoolSlot } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { requireAuth, requireOwnerOrAdmin } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
// Get a mappool with its slots // Get a mappool with its slots
export const GET: RequestHandler = async ({ params, locals }) => { export const GET: RequestHandler = async ({ params, locals }) => {
if (!locals.user) error(401, 'Not logged in'); requireAuth(locals);
const pool = await db.query.mappool.findFirst({ const pool = await db.query.mappool.findFirst({
where: eq(mappool.id, params.id) where: eq(mappool.id, params.id)
@@ -24,7 +25,17 @@ export const GET: RequestHandler = async ({ params, locals }) => {
// Delete a mappool // Delete a mappool
export const DELETE: RequestHandler = async ({ params, locals }) => { export const DELETE: RequestHandler = async ({ params, locals }) => {
if (!locals.user) error(401, 'Not logged in'); const pool = await db.query.mappool.findFirst({
where: eq(mappool.id, params.id)
});
if (!pool) error(404, 'Mappool not found');
requireOwnerOrAdmin(
locals,
pool.createdBy,
'Only the mappool creator or an admin can delete this'
);
await db.delete(mappool).where(eq(mappool.id, params.id)); await db.delete(mappool).where(eq(mappool.id, params.id));
return json({ ok: true }); return json({ ok: true });

View File

@@ -1,13 +1,19 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { mappoolSlot } from '$lib/server/db/schema'; import { mappool, mappoolSlot } from '$lib/server/db/schema';
import { eq, and } from 'drizzle-orm'; import { eq, and } from 'drizzle-orm';
import { getBeatmap } from '$lib/server/osu/api'; import { requireOwnerOrAdmin } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
// Add a slot to a mappool // Add a slot to a mappool
export const POST: RequestHandler = async ({ params, request, locals }) => { export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.user) error(401, 'Not logged in'); const pool = await db.query.mappool.findFirst({
where: eq(mappool.id, params.id)
});
if (!pool) error(404, 'Mappool not found');
requireOwnerOrAdmin(locals, pool.createdBy, 'Only the mappool creator or an admin can add slots');
const { beatmapId, category, mods } = await request.json(); const { beatmapId, category, mods } = await request.json();

View File

@@ -1,11 +1,22 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { mappoolSlot } from '$lib/server/db/schema'; import { mappool, mappoolSlot } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { requireOwnerOrAdmin } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const DELETE: RequestHandler = async ({ params, locals }) => { export const DELETE: RequestHandler = async ({ params, locals }) => {
if (!locals.user) error(401, 'Not logged in'); const pool = await db.query.mappool.findFirst({
where: eq(mappool.id, params.id)
});
if (!pool) error(404, 'Mappool not found');
requireOwnerOrAdmin(
locals,
pool.createdBy,
'Only the mappool creator or an admin can delete slots'
);
await db.delete(mappoolSlot).where(eq(mappoolSlot.id, params.slotId)); await db.delete(mappoolSlot).where(eq(mappoolSlot.id, params.slotId));
return json({ ok: true }); return json({ ok: true });

View File

@@ -1,10 +1,11 @@
// src/routes/api/matches/[id]/lobby/+server.ts // src/routes/api/matches/[id]/lobby/+server.ts
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { moveToLobby } from '$lib/server/match/engine'; import { moveToLobby } from '$lib/server/match/engine';
import { requireRole } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ params, request, locals }) => { export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.user) error(401, 'Not logged in'); requireRole(locals, 'referee', 'Only referees or admins can move matches to lobby');
const body = await request.json().catch(() => ({})); const body = await request.json().catch(() => ({}));

View File

@@ -1,14 +1,30 @@
// src/routes/api/matches/[id]/pick/+server.ts // src/routes/api/matches/[id]/pick/+server.ts
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { pickMap } from '$lib/server/match/engine'; import { pickMap } from '$lib/server/match/engine';
import { requireAuth, hasRole } from '$lib/server/permissions';
import { getMatchFull } from '$lib/server/match/helpers';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ params, request, locals }) => { export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.user) error(401, 'Not logged in'); const user = requireAuth(locals);
const { participantId, mappoolSlotId } = await request.json(); const { mappoolSlotId } = await request.json();
if (!participantId || !mappoolSlotId) { if (!mappoolSlotId) {
error(400, 'participantId and mappoolSlotId required'); error(400, 'mappoolSlotId required');
}
const match = await getMatchFull(params.id);
// Find the participant linked to the authenticated user
const participant = match.participants.find((p) => p.players.some((pl) => pl.userId === user.id));
if (!participant && !hasRole(user.role, 'referee')) {
error(403, 'You are not a participant in this match');
}
const participantId = participant?.id;
if (!participantId) {
error(400, 'Could not resolve participant');
} }
try { try {

View File

@@ -1,14 +1,30 @@
// src/routes/api/matches/[id]/roll/+server.ts // src/routes/api/matches/[id]/roll/+server.ts
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { submitRoll } from '$lib/server/match/engine'; import { submitRoll } from '$lib/server/match/engine';
import { requireAuth, hasRole } from '$lib/server/permissions';
import { getMatchFull } from '$lib/server/match/helpers';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ params, request, locals }) => { export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.user) error(401, 'Not logged in'); const user = requireAuth(locals);
const { participantId, value } = await request.json(); const { value } = await request.json();
if (!participantId || value == null) { if (value == null) {
error(400, 'participantId and value required'); error(400, 'value required');
}
const match = await getMatchFull(params.id);
// Find the participant linked to the authenticated user
const participant = match.participants.find((p) => p.players.some((pl) => pl.userId === user.id));
if (!participant && !hasRole(user.role, 'referee')) {
error(403, 'You are not a participant in this match');
}
const participantId = participant?.id;
if (!participantId) {
error(400, 'Could not resolve participant');
} }
try { try {

View File

@@ -1,10 +1,11 @@
// src/routes/api/matches/[id]/rolling/+server.ts // src/routes/api/matches/[id]/rolling/+server.ts
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { moveToRolling } from '$lib/server/match/engine'; import { moveToRolling } from '$lib/server/match/engine';
import { requireRole } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ params, locals }) => { export const POST: RequestHandler = async ({ params, locals }) => {
if (!locals.user) error(401, 'Not logged in'); requireRole(locals, 'referee', 'Only referees or admins can move matches to rolling');
try { try {
const m = await moveToRolling(params.id); const m = await moveToRolling(params.id);

View File

@@ -1,10 +1,11 @@
// src/routes/api/matches/[id]/score/+server.ts // src/routes/api/matches/[id]/score/+server.ts
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { submitGameScores } from '$lib/server/match/engine'; import { submitGameScores } from '$lib/server/match/engine';
import { requireRole } from '$lib/server/permissions';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user) error(401, 'Not logged in'); requireRole(locals, 'referee', 'Only referees or admins can submit scores');
const { matchGameId, scores } = await request.json(); const { matchGameId, scores } = await request.json();
if (!matchGameId || !scores?.length) { if (!matchGameId || !scores?.length) {