From 3c16bc89e57073e98781c5073fe22871e187c8c7 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Mon, 3 Nov 2025 14:21:08 +0100 Subject: [PATCH 1/3] undo skipped tests, only allow API calls for admin-views, refactor viewer-page to use page.server --- .../list/[vorgang]/+page.server.ts | 23 +++++++++++++++++ .../(token-based)/list/[vorgang]/+page.ts | 25 ------------------- src/routes/api/list/+server.ts | 5 +++- src/routes/api/list/[vorgang]/+server.ts | 15 ++++++++--- .../api/list/[vorgang]/[tatort]/+server.ts | 10 ++++++-- tests/api/List.test.ts | 2 +- tests/api/ListVorgang.test.ts | 2 +- tests/api/ListVorgangTatort.test.ts | 13 +++++++--- tests/api/VorgangVorgangPIN.test.ts | 4 ++- 9 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 src/routes/(token-based)/list/[vorgang]/+page.server.ts delete mode 100644 src/routes/(token-based)/list/[vorgang]/+page.ts diff --git a/src/routes/(token-based)/list/[vorgang]/+page.server.ts b/src/routes/(token-based)/list/[vorgang]/+page.server.ts new file mode 100644 index 0000000..72c55fe --- /dev/null +++ b/src/routes/(token-based)/list/[vorgang]/+page.server.ts @@ -0,0 +1,23 @@ +import { getCrimesListByToken, getVorgaenge } from '$lib/server/vorgangService.js'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params, url }) => { + const vorgangList = getVorgaenge(); + const vorgangToken = params.vorgang; + const crimesList = await getCrimesListByToken(vorgangToken); + const vorgang = vorgangList.find((v) => v.vorgangToken === vorgangToken); //vorgang sollte ein eigener Typ werden, und dann kann man es hier vernünftig typisieren + if (!vorgang || !crimesList) { + throw new Error(`Fehlgeschlagen, es wurden keine Daten zum token gefunden`); + } + + //Variabeln für NameItemEditor + const crimeNames: string[] = crimesList.map((l) => l.name); + + return { + vorgang, + vorgangList, + crimesList, + url, + crimeNames + }; +} diff --git a/src/routes/(token-based)/list/[vorgang]/+page.ts b/src/routes/(token-based)/list/[vorgang]/+page.ts deleted file mode 100644 index fdc38b8..0000000 --- a/src/routes/(token-based)/list/[vorgang]/+page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { API_ROUTES } from '../../../index.js'; - -export async function load({fetch, params, url}){ - const vorgangResponse = await fetch(API_ROUTES.LIST); - const vorgangList = await vorgangResponse.json() - const vorgangToken = params.vorgang; - const crimesListResponse = await fetch(API_ROUTES.VORGANG(vorgangToken)) - const crimesList = await crimesListResponse.json(); - const vorgang = vorgangList.find(v => v.vorgangToken === vorgangToken); //vorgang sollte ein eigener Typ werden, und dann kann man es hier vernünftig typisieren - if(!vorgang || !crimesList){ - throw new Error(`Fehlgeschlagen, es wurden keine Daten zum token gefunden`); - } - - //Variabeln für NameItemEditor - const crimeNames: string[] = crimesList.map((l) => l.name); - - - return { - vorgang, - vorgangList, - crimesList, - url, - crimeNames - } -} diff --git a/src/routes/api/list/+server.ts b/src/routes/api/list/+server.ts index b2c542d..b8fa28a 100644 --- a/src/routes/api/list/+server.ts +++ b/src/routes/api/list/+server.ts @@ -1,7 +1,10 @@ import { getVorgaenge } from '$lib/server/vorgangService'; +import { json } from '@sveltejs/kit'; export async function GET({ locals }) { - + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const vorgaenge = getVorgaenge(); return new Response(JSON.stringify(vorgaenge), { diff --git a/src/routes/api/list/[vorgang]/+server.ts b/src/routes/api/list/[vorgang]/+server.ts index d3c0534..4f4155a 100644 --- a/src/routes/api/list/[vorgang]/+server.ts +++ b/src/routes/api/list/[vorgang]/+server.ts @@ -1,11 +1,15 @@ import { BUCKET, client } from '$lib/minio'; +import { json } from '@sveltejs/kit'; import { deleteVorgangByToken, getCrimesListByToken, vorgangNameExists } from '$lib/server/vorgangService'; -export async function DELETE({ params }) { +export async function DELETE({ locals, params }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const vorgangToken = params.vorgang; const object_list = await new Promise((resolve, reject) => { @@ -29,7 +33,10 @@ export async function DELETE({ params }) { return new Response(null, { status: 204 }); } -export async function HEAD({ params }) { +export async function HEAD({ locals, params }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } try { const vorgangName = params.vorgang; const existing = vorgangNameExists(vorgangName); @@ -44,7 +51,9 @@ export async function HEAD({ params }) { } export async function GET({ params, locals }) { - + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } try { const vorgangToken = params.vorgang; const crimesList = await getCrimesListByToken(vorgangToken); diff --git a/src/routes/api/list/[vorgang]/[tatort]/+server.ts b/src/routes/api/list/[vorgang]/[tatort]/+server.ts index eb9473f..c6d7036 100644 --- a/src/routes/api/list/[vorgang]/[tatort]/+server.ts +++ b/src/routes/api/list/[vorgang]/[tatort]/+server.ts @@ -1,7 +1,10 @@ import { BUCKET, client } from '$lib/minio'; import { json } from '@sveltejs/kit'; -export async function GET() { +export async function GET({ locals }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const stream = client.listObjectsV2(BUCKET, '', true); const result = new ReadableStream({ start(controller) { @@ -24,7 +27,10 @@ export async function GET() { }); } -export async function DELETE({ request }: { request: Request }) { +export async function DELETE({ locals, request }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const url_fragments = request.url.split('/'); const item = url_fragments.at(-1); const vorgang = url_fragments.at(-2); diff --git a/tests/api/List.test.ts b/tests/api/List.test.ts index 39c868c..d7b5ce4 100644 --- a/tests/api/List.test.ts +++ b/tests/api/List.test.ts @@ -14,7 +14,7 @@ const event = { }; describe('API-Endpoints: list', () => { - test.skip('Unerlaubter Zugriff', async () => { + test('Unerlaubter Zugriff', async () => { const event = { locals: { user: null diff --git a/tests/api/ListVorgang.test.ts b/tests/api/ListVorgang.test.ts index 812ee36..ae25cfc 100644 --- a/tests/api/ListVorgang.test.ts +++ b/tests/api/ListVorgang.test.ts @@ -31,7 +31,7 @@ const MockEvent = { }; describe('API-Endpoints: list/[vorgang]', () => { - test.skip('Unerlaubter Zugriff', async () => { + test('Unerlaubter Zugriff', async () => { const event = { locals: { user: null diff --git a/tests/api/ListVorgangTatort.test.ts b/tests/api/ListVorgangTatort.test.ts index a870aa2..b49b0e1 100644 --- a/tests/api/ListVorgangTatort.test.ts +++ b/tests/api/ListVorgangTatort.test.ts @@ -1,6 +1,7 @@ import { describe, test, expect, vi } from 'vitest'; import { DELETE, PUT } from '$root/routes/api/list/[vorgang]/[tatort]/+server'; import { BUCKET, client } from '$lib/minio'; +import { baseData } from '../fixtures'; // Mock data and methods const fakeVorgangToken = `c399423a-ba37-4fe1-bbdf-80e5881168ff`; @@ -22,7 +23,8 @@ vi.mock('$lib/minio', () => ({ describe('API-Endpoints: list/[vorgang]/[tatort]', () => { test('Löschen von Tatorten', async () => { const request = new Request(fakeCrimeAPIURL); - const response = await DELETE({ request }); + const locals = { user: baseData.user } + const response = await DELETE({ locals, request }); expect(client.removeObject).toHaveBeenCalledWith(BUCKET, fakeCrimePath); @@ -40,11 +42,12 @@ describe('API-Endpoints: list/[vorgang]/[tatort]', () => { }) }); const params = { vorgang: fakeVorgangToken }; + const locals = { user: baseData.user } // Mock Datei nicht gefunden client.statObject.mockRejectedValueOnce(new Error('NotFound')); - const response = await PUT({ params, request }); + const response = await PUT({ locals, params, request }); const fakeCrimeNewPath = `${fakeVorgangToken}/${fakeCrimeNewName}`; expect(client.statObject).toHaveBeenCalledWith(BUCKET, fakeCrimeNewPath); @@ -62,9 +65,10 @@ describe('API-Endpoints: list/[vorgang]/[tatort]', () => { newName: '' }) }); + const locals = { user: baseData.user } const params = { vorgang: fakeVorgangToken }; - const response = await PUT({ params, request }); + const response = await PUT({ locals, params, request }); expect(response.status).toBe(400); }); @@ -77,11 +81,12 @@ describe('API-Endpoints: list/[vorgang]/[tatort]', () => { }) }); const params = { vorgang: fakeVorgangToken }; + const locals = { user: baseData.user } // Datei existiert bereits client.statObject.mockResolvedValueOnce({}); - const response = await PUT({ params, request }); + const response = await PUT({ locals, params, request }); expect(response.status).toBe(400); diff --git a/tests/api/VorgangVorgangPIN.test.ts b/tests/api/VorgangVorgangPIN.test.ts index 8d39143..4c11d3a 100644 --- a/tests/api/VorgangVorgangPIN.test.ts +++ b/tests/api/VorgangVorgangPIN.test.ts @@ -1,9 +1,11 @@ import { describe, test, expect, vi } from 'vitest'; import { GET } from '$root/routes/api/vorgang/[vorgang]/vorgangPIN/+server'; import { db } from '$lib/server/dbService'; +import { baseData } from '../fixtures'; const mockEvent = { - params: { vorgang: '123' } + params: { vorgang: '123' }, + locals: { user: baseData.user } }; vi.mock('$lib/server/dbService', () => ({ -- 2.43.0 From fd907c985112e7f0867e1533b270659fb9d94bc4 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 4 Nov 2025 09:22:53 +0100 Subject: [PATCH 2/3] move API protection check into hooks, adjusting corresponding tests --- src/hooks.server.ts | 10 +++++ src/routes/api/list/+server.ts | 3 -- src/routes/api/list/[vorgang]/+server.ts | 9 ----- .../api/list/[vorgang]/[tatort]/+server.ts | 6 --- src/routes/api/users/+server.ts | 7 ---- src/routes/api/users/[user]/+server.ts | 4 -- tests/api/API_Protection.test.ts | 37 +++++++++++++++++++ tests/api/List.test.ts | 15 -------- tests/api/ListVorgang.test.ts | 15 -------- tests/api/Users.test.ts | 15 -------- 10 files changed, 47 insertions(+), 74 deletions(-) create mode 100644 tests/api/API_Protection.test.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 1eb3671..a7e170f 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -14,5 +14,15 @@ export const handle: Handle = async ({ event, resolve }) => { event.cookies.delete('session', {path: ROUTE_NAMES.ROOT}); event.locals.user = null; } + + if (event.url.pathname.startsWith('/api')) { + if (!event.locals.user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + } + return await resolve(event); } diff --git a/src/routes/api/list/+server.ts b/src/routes/api/list/+server.ts index b8fa28a..5ffc53d 100644 --- a/src/routes/api/list/+server.ts +++ b/src/routes/api/list/+server.ts @@ -2,9 +2,6 @@ import { getVorgaenge } from '$lib/server/vorgangService'; import { json } from '@sveltejs/kit'; export async function GET({ locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } const vorgaenge = getVorgaenge(); return new Response(JSON.stringify(vorgaenge), { diff --git a/src/routes/api/list/[vorgang]/+server.ts b/src/routes/api/list/[vorgang]/+server.ts index 4f4155a..c4d1f2f 100644 --- a/src/routes/api/list/[vorgang]/+server.ts +++ b/src/routes/api/list/[vorgang]/+server.ts @@ -7,9 +7,6 @@ import { } from '$lib/server/vorgangService'; export async function DELETE({ locals, params }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } const vorgangToken = params.vorgang; const object_list = await new Promise((resolve, reject) => { @@ -34,9 +31,6 @@ export async function DELETE({ locals, params }) { } export async function HEAD({ locals, params }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } try { const vorgangName = params.vorgang; const existing = vorgangNameExists(vorgangName); @@ -51,9 +45,6 @@ export async function HEAD({ locals, params }) { } export async function GET({ params, locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } try { const vorgangToken = params.vorgang; const crimesList = await getCrimesListByToken(vorgangToken); diff --git a/src/routes/api/list/[vorgang]/[tatort]/+server.ts b/src/routes/api/list/[vorgang]/[tatort]/+server.ts index c6d7036..8e0442f 100644 --- a/src/routes/api/list/[vorgang]/[tatort]/+server.ts +++ b/src/routes/api/list/[vorgang]/[tatort]/+server.ts @@ -2,9 +2,6 @@ import { BUCKET, client } from '$lib/minio'; import { json } from '@sveltejs/kit'; export async function GET({ locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } const stream = client.listObjectsV2(BUCKET, '', true); const result = new ReadableStream({ start(controller) { @@ -28,9 +25,6 @@ export async function GET({ locals }) { } export async function DELETE({ locals, request }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } const url_fragments = request.url.split('/'); const item = url_fragments.at(-1); const vorgang = url_fragments.at(-2); diff --git a/src/routes/api/users/+server.ts b/src/routes/api/users/+server.ts index 7bf820c..8a3cdd5 100644 --- a/src/routes/api/users/+server.ts +++ b/src/routes/api/users/+server.ts @@ -5,9 +5,6 @@ import bcrypt from 'bcrypt'; const saltRounds = 12; export function GET({ locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } const userList = getUsers(); @@ -15,10 +12,6 @@ export function GET({ locals }) { } export async function POST({ request, locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } - const data = await request.json(); const userName = data.userName; const userPassword = data.userPassword; diff --git a/src/routes/api/users/[user]/+server.ts b/src/routes/api/users/[user]/+server.ts index 704ae19..1339248 100644 --- a/src/routes/api/users/[user]/+server.ts +++ b/src/routes/api/users/[user]/+server.ts @@ -2,10 +2,6 @@ import { json } from '@sveltejs/kit'; import { deleteUser } from '$lib/server/userService'; export async function DELETE({ params, locals }) { - if (!locals.user) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } - const userId = params.user; const rowCount = deleteUser(userId); diff --git a/tests/api/API_Protection.test.ts b/tests/api/API_Protection.test.ts new file mode 100644 index 0000000..8dfa69b --- /dev/null +++ b/tests/api/API_Protection.test.ts @@ -0,0 +1,37 @@ +import { describe, test, expect, vi } from 'vitest'; +import { handle } from '../../src/hooks.server'; + +const event = { + url: new URL("http://localhost/api/list"), + cookies: { get: vi.fn(() => null) }, + locals: {user: null} +}; + +vi.mock('$lib/auth', () => ({ + decryptToken: vi.fn() +})); + +describe('API-Endpoints: Zugangs-Mechanismus', () => { + test('Unautorisierter Zugriff', async () => { + const resolve = vi.fn(); + + const response = await handle({ event, resolve }); + + expect(response.status).toBe(401); + const body = await response.json(); + expect(body.error).toBe('Unauthorized'); + expect(resolve).not.toHaveBeenCalled(); + }); + + test('Authentifizierter Zugriff', async () => { + event.locals = {user: { id: 'admin', admin: true }} + + const resolve = vi.fn(() => new Response('ok', { status: 200 })); + + const response = await handle({ event, resolve }); + + expect(response.status).toBe(200); + expect(await response.text()).toBe('ok'); + expect(resolve).toHaveBeenCalled(); + }); +}) \ No newline at end of file diff --git a/tests/api/List.test.ts b/tests/api/List.test.ts index d7b5ce4..ae54ad8 100644 --- a/tests/api/List.test.ts +++ b/tests/api/List.test.ts @@ -14,21 +14,6 @@ const event = { }; describe('API-Endpoints: list', () => { - test('Unerlaubter Zugriff', async () => { - const event = { - locals: { - user: null - } - }; - - const response = await GET(event); - expect(response.status).toBe(401); - - const json = await response.json(); - const errorObj = { error: 'Unauthorized' }; - expect(json).toEqual(errorObj); - }); - test('Leere Liste wenn keine Vorgänge existieren', async () => { vi.mocked(getVorgaenge).mockReturnValueOnce([]); diff --git a/tests/api/ListVorgang.test.ts b/tests/api/ListVorgang.test.ts index ae25cfc..4429d53 100644 --- a/tests/api/ListVorgang.test.ts +++ b/tests/api/ListVorgang.test.ts @@ -31,21 +31,6 @@ const MockEvent = { }; describe('API-Endpoints: list/[vorgang]', () => { - test('Unerlaubter Zugriff', async () => { - const event = { - locals: { - user: null - } - }; - - const response = await GET(event); - expect(response.status).toBe(401); - - const json = await response.json(); - const errorObj = { error: 'Unauthorized' }; - expect(json).toEqual(errorObj); - }); - test('Vorgang ohne Tatorte', async () => { const testCrimesList = []; diff --git a/tests/api/Users.test.ts b/tests/api/Users.test.ts index a721b46..07e0b50 100644 --- a/tests/api/Users.test.ts +++ b/tests/api/Users.test.ts @@ -16,21 +16,6 @@ vi.mock('bcrypt', () => ({ })); describe('API-Endpoint: Users', () => { - test('Unerlaubter Zugriff', async () => { - const event = { - locals: { - user: null - } - }; - - const response = await GET(event); - expect(response.status).toBe(401); - - const errorMessage = { error: 'Unauthorized' }; - const json = await response.json(); - expect(json).toEqual(errorMessage); - }); - // [INFO] Test auf keine User nicht notwendig, da immer min. ein User vorhanden // Mock eingelogter User bzw. stelle locals.user zur Verfügung -- 2.43.0 From 808b56934c650cc7cd3c53bd45df893f612edfff Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 5 Nov 2025 09:18:05 +0100 Subject: [PATCH 3/3] remove unused locals parameter --- src/routes/api/list/+server.ts | 2 +- src/routes/api/list/[vorgang]/+server.ts | 6 +++--- src/routes/api/list/[vorgang]/[tatort]/+server.ts | 4 ++-- src/routes/api/users/+server.ts | 4 ++-- src/routes/api/users/[user]/+server.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/routes/api/list/+server.ts b/src/routes/api/list/+server.ts index 5ffc53d..21a483a 100644 --- a/src/routes/api/list/+server.ts +++ b/src/routes/api/list/+server.ts @@ -1,7 +1,7 @@ import { getVorgaenge } from '$lib/server/vorgangService'; import { json } from '@sveltejs/kit'; -export async function GET({ locals }) { +export async function GET() { const vorgaenge = getVorgaenge(); return new Response(JSON.stringify(vorgaenge), { diff --git a/src/routes/api/list/[vorgang]/+server.ts b/src/routes/api/list/[vorgang]/+server.ts index c4d1f2f..69fc6a6 100644 --- a/src/routes/api/list/[vorgang]/+server.ts +++ b/src/routes/api/list/[vorgang]/+server.ts @@ -6,7 +6,7 @@ import { vorgangNameExists } from '$lib/server/vorgangService'; -export async function DELETE({ locals, params }) { +export async function DELETE({ params }) { const vorgangToken = params.vorgang; const object_list = await new Promise((resolve, reject) => { @@ -30,7 +30,7 @@ export async function DELETE({ locals, params }) { return new Response(null, { status: 204 }); } -export async function HEAD({ locals, params }) { +export async function HEAD({ params }) { try { const vorgangName = params.vorgang; const existing = vorgangNameExists(vorgangName); @@ -44,7 +44,7 @@ export async function HEAD({ locals, params }) { } } -export async function GET({ params, locals }) { +export async function GET({ params }) { try { const vorgangToken = params.vorgang; const crimesList = await getCrimesListByToken(vorgangToken); diff --git a/src/routes/api/list/[vorgang]/[tatort]/+server.ts b/src/routes/api/list/[vorgang]/[tatort]/+server.ts index 8e0442f..2af6f3a 100644 --- a/src/routes/api/list/[vorgang]/[tatort]/+server.ts +++ b/src/routes/api/list/[vorgang]/[tatort]/+server.ts @@ -1,7 +1,7 @@ import { BUCKET, client } from '$lib/minio'; import { json } from '@sveltejs/kit'; -export async function GET({ locals }) { +export async function GET() { const stream = client.listObjectsV2(BUCKET, '', true); const result = new ReadableStream({ start(controller) { @@ -24,7 +24,7 @@ export async function GET({ locals }) { }); } -export async function DELETE({ locals, request }) { +export async function DELETE({ request }) { const url_fragments = request.url.split('/'); const item = url_fragments.at(-1); const vorgang = url_fragments.at(-2); diff --git a/src/routes/api/users/+server.ts b/src/routes/api/users/+server.ts index 8a3cdd5..9bf12d8 100644 --- a/src/routes/api/users/+server.ts +++ b/src/routes/api/users/+server.ts @@ -4,14 +4,14 @@ import bcrypt from 'bcrypt'; const saltRounds = 12; -export function GET({ locals }) { +export function GET() { const userList = getUsers(); return new Response(JSON.stringify(userList)); } -export async function POST({ request, locals }) { +export async function POST({ request }) { const data = await request.json(); const userName = data.userName; const userPassword = data.userPassword; diff --git a/src/routes/api/users/[user]/+server.ts b/src/routes/api/users/[user]/+server.ts index 1339248..19109e7 100644 --- a/src/routes/api/users/[user]/+server.ts +++ b/src/routes/api/users/[user]/+server.ts @@ -1,7 +1,7 @@ import { json } from '@sveltejs/kit'; import { deleteUser } from '$lib/server/userService'; -export async function DELETE({ params, locals }) { +export async function DELETE({ params }) { const userId = params.user; const rowCount = deleteUser(userId); -- 2.43.0