diff --git a/src/lib/components/ui/Alert.svelte b/src/lib/components/Alert.svelte similarity index 100% rename from src/lib/components/ui/Alert.svelte rename to src/lib/components/Alert.svelte diff --git a/src/lib/components/BaseInputField.svelte b/src/lib/components/BaseInputField.svelte new file mode 100644 index 0000000..d4d7c90 --- /dev/null +++ b/src/lib/components/BaseInputField.svelte @@ -0,0 +1,32 @@ + + +
+ +
+
+ +
+
+ {#if error} +

{error}

+ {/if} +
diff --git a/src/lib/components/ui/Button.svelte b/src/lib/components/Button.svelte similarity index 100% rename from src/lib/components/ui/Button.svelte rename to src/lib/components/Button.svelte diff --git a/src/lib/components/ui/DeleteIconButton.svelte b/src/lib/components/DeleteIconButton.svelte similarity index 92% rename from src/lib/components/ui/DeleteIconButton.svelte rename to src/lib/components/DeleteIconButton.svelte index f0d329f..9efab9e 100644 --- a/src/lib/components/ui/DeleteIconButton.svelte +++ b/src/lib/components/DeleteIconButton.svelte @@ -14,8 +14,8 @@ + +
+ +
\ No newline at end of file diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte new file mode 100644 index 0000000..4e13909 --- /dev/null +++ b/src/lib/components/Header.svelte @@ -0,0 +1,36 @@ + + +
+
+ +
+
diff --git a/src/lib/components/ui/Modal/Modal.svelte b/src/lib/components/Modal/Modal.svelte similarity index 100% rename from src/lib/components/ui/Modal/Modal.svelte rename to src/lib/components/Modal/Modal.svelte diff --git a/src/lib/components/ui/Modal/ModalContent.svelte b/src/lib/components/Modal/ModalContent.svelte similarity index 100% rename from src/lib/components/ui/Modal/ModalContent.svelte rename to src/lib/components/Modal/ModalContent.svelte diff --git a/src/lib/components/ui/Modal/ModalFooter.svelte b/src/lib/components/Modal/ModalFooter.svelte similarity index 100% rename from src/lib/components/ui/Modal/ModalFooter.svelte rename to src/lib/components/Modal/ModalFooter.svelte diff --git a/src/lib/components/ui/Modal/ModalTitle.svelte b/src/lib/components/Modal/ModalTitle.svelte similarity index 100% rename from src/lib/components/ui/Modal/ModalTitle.svelte rename to src/lib/components/Modal/ModalTitle.svelte diff --git a/src/lib/components/ui/Notification.svelte b/src/lib/components/Notification.svelte similarity index 100% rename from src/lib/components/ui/Notification.svelte rename to src/lib/components/Notification.svelte diff --git a/src/lib/components/ui/Panel.svelte b/src/lib/components/Panel.svelte similarity index 100% rename from src/lib/components/ui/Panel.svelte rename to src/lib/components/Panel.svelte diff --git a/src/lib/components/ui/Select.svelte b/src/lib/components/Select.svelte similarity index 100% rename from src/lib/components/ui/Select.svelte rename to src/lib/components/Select.svelte diff --git a/src/lib/helper/caseNumberOccupied.js b/src/lib/helper/caseNumberOccupied.js deleted file mode 100644 index e727abf..0000000 --- a/src/lib/helper/caseNumberOccupied.js +++ /dev/null @@ -1,22 +0,0 @@ -import { client } from '$lib/minio'; - -/** - * Check if caseNumber is used - * @param {string} caseNumber - * @returns {Promise} - */ -export default async function caseNumberOccupied(caseNumber) { - const prefix = `${caseNumber}`; - const promise = new Promise((resolve) => { - let stream = client.listObjectsV2('tatort', prefix, false, ''); - stream.on('data', () => { - stream.destroy(); - resolve(true); - }); - stream.on('end', () => { - resolve(false); - }); - }); - - return promise; -} diff --git a/src/lib/helper/caseNumberOccupied.ts b/src/lib/helper/caseNumberOccupied.ts index 0d95f73..3c6b08d 100644 --- a/src/lib/helper/caseNumberOccupied.ts +++ b/src/lib/helper/caseNumberOccupied.ts @@ -1,7 +1,7 @@ import { client } from '$lib/minio'; export default async function caseNumberOccupied (caseNumber: string): Promise { - const prefix = `${caseNumber}/config.json`; + const prefix = `${caseNumber}`; const promise: Promise = new Promise((resolve) => { const stream = client.listObjectsV2('tatort', prefix, false, ''); stream.on('data', () => { diff --git a/src/lib/helper/getCode.js b/src/lib/helper/getCode.ts similarity index 100% rename from src/lib/helper/getCode.js rename to src/lib/helper/getCode.ts diff --git a/src/lib/minio.ts b/src/lib/minio.ts index 07a763b..6824b78 100644 --- a/src/lib/minio.ts +++ b/src/lib/minio.ts @@ -6,3 +6,5 @@ import config from '$lib/config'; /** export const client = new Minio.Client(config.minio); */ export const client = new Client(config.minio); + +export const BUCKET = 'tatort'; diff --git a/src/lib/server/authService.ts b/src/lib/server/authService.ts new file mode 100644 index 0000000..98155c8 --- /dev/null +++ b/src/lib/server/authService.ts @@ -0,0 +1,29 @@ +import { dev } from '$app/environment'; +import { fail, redirect, type Cookies, type RequestEvent } from '@sveltejs/kit'; +import { authenticate } from '$lib/auth'; + +const COOKIE_NAME = 'session'; + +export const loginUser = async ({ request, cookies }: { request: Request; cookies: Cookies }) => { + const data = await request.formData(); + const user = data.get('user'); + const password = data.get('password'); + + const token = authenticate(user, password); + + if (!token) return fail(400, { user, incorrect: true }); + + cookies.set(COOKIE_NAME, token, { + path: '/', + httpOnly: true, + sameSite: 'strict', + secure: !dev + }); + return redirect(303, '/'); +}; + +export const logoutUser = async (event: RequestEvent) => { + event.cookies.delete(COOKIE_NAME, { path: '/' }); + event.locals.user = null; + return { success: true }; +}; diff --git a/src/lib/server/s3ClientService.ts b/src/lib/server/s3ClientService.ts new file mode 100644 index 0000000..2a46c87 --- /dev/null +++ b/src/lib/server/s3ClientService.ts @@ -0,0 +1,21 @@ +import { BUCKET, client } from '$lib/minio'; + + +export const checkIfExactDirectoryExists = (dir: string): Promise => { + return new Promise((resolve, reject) => { + const prefix = dir.endsWith('/') ? dir : `${dir}/`; + + const stream = client.listObjectsV2(BUCKET, prefix, false, ''); + + stream.on('data', (obj) => { + if (obj.prefix === undefined && obj.name.startsWith(prefix)) { + stream.destroy(); + resolve(true); + } + }); + + stream.on('error', (err) => reject(err)); + + stream.on('end', () => resolve(false)); + }); +} diff --git a/src/lib/server/vorgangService.ts b/src/lib/server/vorgangService.ts new file mode 100644 index 0000000..c24a37c --- /dev/null +++ b/src/lib/server/vorgangService.ts @@ -0,0 +1,97 @@ +import { fail, redirect } from '@sveltejs/kit'; +import { BUCKET, client } from '$lib/minio'; +import { checkIfExactDirectoryExists } from './s3ClientService'; + +/** + * Checks if Vorgang exists and token is valid. + * @param request + * @returns redirect to /list/caseId or error + */ +export const redirectIfVorgangExists = async (request: Request) => { + const data = await request.formData(); + const caseId = data.get('case-id'); + const caseToken = data.get('case-token'); + + if (!caseId) { + return fail(400, { + success: false, + caseId, + error: { message: 'Die Vorgangsnummer darf nicht leer sein.' } + }); + } + + if (typeof caseId === 'string' && !(await checkIfExactDirectoryExists(caseId))) { + return fail(400, { + success: false, + caseId, + error: { message: 'Die Vorgangsnummer existiert in dieser Anwendung nicht.' } + }); + } + + const isTokenValid = await hasValidToken(caseId, caseToken); + + if (!isTokenValid) { + return fail(400, { + success: false, + caseId, + error: { message: 'Der Token ist ungültig.' } + }); + } + + redirect(303, `/list/${caseId}`); +}; + +export const getVorgangByCaseId = ({ params }) => { + const prefix = params.vorgang ? `${params.vorgang}/` : ''; + const stream = client.listObjectsV2(BUCKET, prefix, false, ''); + const result = new ReadableStream({ + start(controller) { + stream.on('data', (data) => { + if (prefix === '') { + if (data.prefix) + controller.enqueue(`${JSON.stringify({ ...data, name: data.prefix.slice(0, -1) })}\n`); + return; + } + + const name = data.name.slice(prefix.length); + if (name === 'config.json') return; + // zugangscode datei + if (name === '__perm__') return; + + controller.enqueue(`${JSON.stringify({ ...data, name, prefix })}\n`); + }); + stream.on('end', () => { + controller.close(); + }); + }, + cancel() { + stream.destroy(); + } + }); + + return new Response(result, { + headers: { + 'content-type': 'text/event-stream' + } + }); +}; + +const hasValidToken = async (caseId: string, caseToken: string) => { + const tokenFileName = '__perm__'; + const objPath = `${caseId}/${tokenFileName}`; + + try { + if (!caseToken) return false; + + const res = await client.getObject('tatort', objPath); + + const savedToken = await new Response(res).text(); + + return savedToken === caseToken ? true : false; + } catch (error) { + if (error.name == 'S3Error') { + console.log(error); + return false; + } + } +}; diff --git a/src/routes/(angemeldet)/+layout.server.ts b/src/routes/(angemeldet)/+layout.server.ts index 0454232..5251779 100644 --- a/src/routes/(angemeldet)/+layout.server.ts +++ b/src/routes/(angemeldet)/+layout.server.ts @@ -1,5 +1,5 @@ import { redirect, type ServerLoadEvent } from '@sveltejs/kit'; -import type { PageServerLoad } from './view/[vorgang]/[tatort]/$types'; +import type { PageServerLoad } from '../anmeldung/$types'; export const load: PageServerLoad = (event: ServerLoadEvent) => { if (!event.locals.user && event.url.pathname !== '/anmeldung') throw redirect(303, '/anmeldung'); diff --git a/src/routes/(angemeldet)/+layout.svelte b/src/routes/(angemeldet)/+layout.svelte index 00beb40..a3a4cd5 100644 --- a/src/routes/(angemeldet)/+layout.svelte +++ b/src/routes/(angemeldet)/+layout.svelte @@ -1,73 +1,18 @@
-
- -
+
+
-
diff --git a/src/routes/(angemeldet)/tatorte/+page.svelte b/src/routes/(angemeldet)/tatorte/+page.svelte index 6798fe5..44acb50 100644 --- a/src/routes/(angemeldet)/tatorte/+page.svelte +++ b/src/routes/(angemeldet)/tatorte/+page.svelte @@ -1,10 +1,10 @@ - -
-
-

Vorgang ansehen

-
- -
-
-
- -

- Anhand der Vorgangsnummer werden Sie zu den Dateien des Vorgangs weitergeleitet und können - sich den Vorgang dann ansehen. -

- -
-
- -
-
- -
-
- {#if form?.error?.caseNumber} -

{form.error.caseNumber}

- {/if} -
- -
- -
-
- -
-
- {#if form?.error?.token} -

{form.error.token}

- {/if} -
-
-
-
- -
-
-
-
diff --git a/src/routes/(token-based)/+layout.server.ts b/src/routes/(token-based)/+layout.server.ts new file mode 100644 index 0000000..e7a1878 --- /dev/null +++ b/src/routes/(token-based)/+layout.server.ts @@ -0,0 +1,10 @@ +import { type ServerLoadEvent } from '@sveltejs/kit'; +import type { PageServerLoad } from '../anmeldung/$types'; + +export const load: PageServerLoad = (event: ServerLoadEvent) => { + if (event.locals.user) { + return { + user: event.locals.user + }; + } +}; diff --git a/src/routes/(token-based)/+layout.svelte b/src/routes/(token-based)/+layout.svelte new file mode 100644 index 0000000..7621ed9 --- /dev/null +++ b/src/routes/(token-based)/+layout.svelte @@ -0,0 +1,16 @@ + + +
+
+ +
+ +
+ +
+
diff --git a/src/routes/(angemeldet)/list/+page.svelte b/src/routes/(token-based)/list/+page.svelte similarity index 100% rename from src/routes/(angemeldet)/list/+page.svelte rename to src/routes/(token-based)/list/+page.svelte diff --git a/src/routes/(angemeldet)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte similarity index 94% rename from src/routes/(angemeldet)/list/[vorgang]/+page.svelte rename to src/routes/(token-based)/list/[vorgang]/+page.svelte index 8b18f66..3e96884 100644 --- a/src/routes/(angemeldet)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -5,12 +5,12 @@ import timeElapsed from '$lib/helper/timeElapsed'; - import Alert from '$lib/components/ui/Alert.svelte'; - import Button from '$lib/components/ui/Button.svelte'; - import Modal from '$lib/components/ui/Modal/Modal.svelte'; - import ModalTitle from '$lib/components/ui/Modal/ModalTitle.svelte'; - import ModalContent from '$lib/components/ui/Modal/ModalContent.svelte'; - import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte'; + import Alert from '$lib/components/Alert.svelte'; + import Button from '$lib/components/Button.svelte'; + import Modal from '$lib/components/Modal/Modal.svelte'; + import ModalTitle from '$lib/components/Modal/ModalTitle.svelte'; + import ModalContent from '$lib/components/Modal/ModalContent.svelte'; + import ModalFooter from '$lib/components/Modal/ModalFooter.svelte'; import Cube from '$lib/icons/Cube.svelte'; import Edit from '$lib/icons/Edit.svelte'; import Trash from '$lib/icons/Trash.svelte'; @@ -92,7 +92,6 @@ let text_field = document.getElementById(text_field_id); if (text_field) { - text_field.setAttribute('contenteditable', 'false'); text_field.setAttribute('contenteditable', 'false'); text_field.textContent = item.name; } @@ -173,9 +172,8 @@ >
-
- {#if data.user.admin} + {#if data?.user?.admin} redirectIfVorgangExists(request) +} \ No newline at end of file diff --git a/src/routes/(token-based)/view/+page.svelte b/src/routes/(token-based)/view/+page.svelte new file mode 100644 index 0000000..f2a6c49 --- /dev/null +++ b/src/routes/(token-based)/view/+page.svelte @@ -0,0 +1,40 @@ + + +
+
+

Vorgang ansehen

+
+

+ Anhand der Vorgangsnummer werden Sie zu den Dateien des Vorgangs weitergeleitet und können sich + den Vorgang dann ansehen. +

+
+ +
+ +
+
+ +
+ +
diff --git a/src/routes/(angemeldet)/view/[vorgang]/[tatort]/+page.server.ts b/src/routes/(token-based)/view/[vorgang]/[tatort]/+page.server.ts similarity index 100% rename from src/routes/(angemeldet)/view/[vorgang]/[tatort]/+page.server.ts rename to src/routes/(token-based)/view/[vorgang]/[tatort]/+page.server.ts diff --git a/src/routes/(angemeldet)/view/[vorgang]/[tatort]/+page.svelte b/src/routes/(token-based)/view/[vorgang]/[tatort]/+page.svelte similarity index 97% rename from src/routes/(angemeldet)/view/[vorgang]/[tatort]/+page.svelte rename to src/routes/(token-based)/view/[vorgang]/[tatort]/+page.svelte index 919b7e1..8e47533 100644 --- a/src/routes/(angemeldet)/view/[vorgang]/[tatort]/+page.svelte +++ b/src/routes/(token-based)/view/[vorgang]/[tatort]/+page.svelte @@ -1,7 +1,7 @@
@@ -12,14 +18,47 @@ Landeswappen Niedersachsen

- Anmeldung zum 3D Tatort + Willkommen beim 3D Tatort

- -
-
+
+
+
+ + +
+ +
+
+ +
+ +
+
+
+ +
+
+
+ + Anmelden + +
- +
-
- -
+
-
- +
+
-
-
+
+ +