Merge pull request 'refactor-login-page' (#8) from refactor-login-page into development
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
32
src/lib/components/BaseInputField.svelte
Normal file
32
src/lib/components/BaseInputField.svelte
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Exclamation from '$lib/icons/Exclamation.svelte';
|
||||||
|
|
||||||
|
let { label = '', error = null, value, name, id, type = 'text'} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for={name} class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
|
><span class="flex"
|
||||||
|
>{#if error}
|
||||||
|
<span class="inline-block mr-1"><Exclamation /></span>
|
||||||
|
{/if}
|
||||||
|
{label}</span
|
||||||
|
></label
|
||||||
|
>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div
|
||||||
|
class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 text-sm leading-6"
|
||||||
|
bind:value
|
||||||
|
{type}
|
||||||
|
{name}
|
||||||
|
{id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if error}
|
||||||
|
<p class="block text-sm leading-6 text-red-900 mt-2">{error}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Trash from '$lib/icons/Trash.svelte';
|
import Trash from '$lib/icons/Trash.svelte';
|
||||||
import Panel from '$lib/components/ui/Panel.svelte';
|
import Panel from '$lib/components/Panel.svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import { clickOutside } from '$lib/helpers/clickOutside.js';
|
import { clickOutside } from '$lib/helpers/clickOutside.js';
|
||||||
const { adminMode, prediction, predictionRemove } = $page.data;
|
const { adminMode, prediction, predictionRemove } = $page.data;
|
||||||
|
|
||||||
37
src/lib/components/Footer.svelte
Normal file
37
src/lib/components/Footer.svelte
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script>
|
||||||
|
import Profile from "$lib/icons/Profile.svelte";
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex-none">
|
||||||
|
<footer class="justify-end">
|
||||||
|
<div class="bg-gray-100">
|
||||||
|
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between divide-x divide-gray-900/5 border-x border-gray-900/5">
|
||||||
|
<a
|
||||||
|
href="/list"
|
||||||
|
class="px-4 py-1 -ml-4 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
© 2023 Innovation Hub Niedersachen
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="px-4 py-1 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
back
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="px-4 py-1 -mr-4 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<!--icon-->
|
||||||
|
<Profile />
|
||||||
|
|
||||||
|
{data?.user?.id}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
36
src/lib/components/Header.svelte
Normal file
36
src/lib/components/Header.svelte
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Chevron from '$lib/icons/Chevron-right.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<header class="flex-none relative isolate z-10 bg-white px-8">
|
||||||
|
<nav
|
||||||
|
class="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8"
|
||||||
|
aria-label="Global"
|
||||||
|
>
|
||||||
|
<div class="flex w-48">
|
||||||
|
<a href="/" class="-m-1.5 p-1.5 w-10">
|
||||||
|
<span class="sr-only">Tatort Niedersachen</span>
|
||||||
|
<img class="h-8 w-auto" src="/Landeswappen_NI.svg" alt="Landeswappen Niedersachsen" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl text-slate-400 font-bold">Tatort</h1>
|
||||||
|
<div class="lg:flex lg:justify-end w-48">
|
||||||
|
{#if data.user}
|
||||||
|
<form method="POST" action="/anmeldung?/logout">
|
||||||
|
<input type="hidden" />
|
||||||
|
<button type="submit" class="text-sm font-semibold leading-6 text-gray-900"
|
||||||
|
><span
|
||||||
|
><span class="align-middle inline-block">Abmelden</span><span
|
||||||
|
class="align-middle inline-block"><Chevron /></span
|
||||||
|
></span
|
||||||
|
></button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { client } from '$lib/minio';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if caseNumber is used
|
|
||||||
* @param {string} caseNumber
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { client } from '$lib/minio';
|
import { client } from '$lib/minio';
|
||||||
|
|
||||||
export default async function caseNumberOccupied (caseNumber: string): Promise<boolean> {
|
export default async function caseNumberOccupied (caseNumber: string): Promise<boolean> {
|
||||||
const prefix = `${caseNumber}/config.json`;
|
const prefix = `${caseNumber}`;
|
||||||
const promise: Promise<boolean> = new Promise((resolve) => {
|
const promise: Promise<boolean> = new Promise((resolve) => {
|
||||||
const stream = client.listObjectsV2('tatort', prefix, false, '');
|
const stream = client.listObjectsV2('tatort', prefix, false, '');
|
||||||
stream.on('data', () => {
|
stream.on('data', () => {
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ import config from '$lib/config';
|
|||||||
|
|
||||||
/** export const client = new Minio.Client(config.minio); */
|
/** export const client = new Minio.Client(config.minio); */
|
||||||
export const client = new Client(config.minio);
|
export const client = new Client(config.minio);
|
||||||
|
|
||||||
|
export const BUCKET = 'tatort';
|
||||||
|
|||||||
29
src/lib/server/authService.ts
Normal file
29
src/lib/server/authService.ts
Normal file
@@ -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 };
|
||||||
|
};
|
||||||
21
src/lib/server/s3ClientService.ts
Normal file
21
src/lib/server/s3ClientService.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { BUCKET, client } from '$lib/minio';
|
||||||
|
|
||||||
|
|
||||||
|
export const checkIfExactDirectoryExists = (dir: string): Promise<boolean> => {
|
||||||
|
return new Promise<boolean>((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));
|
||||||
|
});
|
||||||
|
}
|
||||||
97
src/lib/server/vorgangService.ts
Normal file
97
src/lib/server/vorgangService.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { redirect, type ServerLoadEvent } from '@sveltejs/kit';
|
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) => {
|
export const load: PageServerLoad = (event: ServerLoadEvent) => {
|
||||||
if (!event.locals.user && event.url.pathname !== '/anmeldung') throw redirect(303, '/anmeldung');
|
if (!event.locals.user && event.url.pathname !== '/anmeldung') throw redirect(303, '/anmeldung');
|
||||||
|
|||||||
@@ -1,73 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Chevron from '$lib/icons/Chevron-right.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
import Profile from '$lib/icons/Profile.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-screen v-screen flex flex-col">
|
<div class="h-screen v-screen flex flex-col">
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<header class="flex-none relative isolate z-10 bg-white px-8">
|
<Header {data}/>
|
||||||
<nav
|
|
||||||
class="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8"
|
|
||||||
aria-label="Global"
|
|
||||||
>
|
|
||||||
<div class="flex w-48">
|
|
||||||
<a href="/" class="-m-1.5 p-1.5 w-10">
|
|
||||||
<span class="sr-only">Tatort Niedersachen</span>
|
|
||||||
<img class="h-8 w-auto" src="/Landeswappen_NI.svg" alt="Landeswappen Niedersachsen" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<h1 class="text-3xl text-slate-400 font-bold">Tatort</h1>
|
|
||||||
<div class="lg:flex lg:justify-end w-48">
|
|
||||||
{#if data.user}
|
|
||||||
<form method="POST" action="/anmeldung?/logout">
|
|
||||||
<input type="hidden" />
|
|
||||||
<button type="submit" class="text-sm font-semibold leading-6 text-gray-900"
|
|
||||||
><span
|
|
||||||
><span class="align-middle inline-block">Abmelden</span><span
|
|
||||||
class="align-middle inline-block"><Chevron /></span
|
|
||||||
></span
|
|
||||||
></button
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="h-full grow overflow-scroll">
|
<div class="h-full grow overflow-scroll">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<Footer {data}/>
|
||||||
<footer class="justify-end">
|
|
||||||
<div class="bg-gray-100">
|
|
||||||
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
|
||||||
<div class="flex justify-between divide-x divide-gray-900/5 border-x border-gray-900/5">
|
|
||||||
<a
|
|
||||||
href="/list"
|
|
||||||
class="px-4 py-1 -ml-4 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
© 2023 Innovation Hub Niedersachen
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/"
|
|
||||||
class="px-4 py-1 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
back
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/"
|
|
||||||
class="px-4 py-1 -mr-4 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
<!--icon-->
|
|
||||||
<Profile />
|
|
||||||
|
|
||||||
{data.user.id}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Alert from '$lib/components/ui/Alert.svelte';
|
import Alert from '$lib/components/Alert.svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Modal from '$lib/components/ui/Modal/Modal.svelte';
|
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||||
import ModalTitle from '$lib/components/ui/Modal/ModalTitle.svelte';
|
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||||||
import ModalContent from '$lib/components/ui/Modal/ModalContent.svelte';
|
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||||
import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte';
|
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||||
import Exclamation from '$lib/icons/Exclamation.svelte';
|
import Exclamation from '$lib/icons/Exclamation.svelte';
|
||||||
|
|
||||||
export let form;
|
export let form;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { deserialize } from '$app/forms';
|
import { deserialize } from '$app/forms';
|
||||||
import Alert from '$lib/components/ui/Alert.svelte';
|
import Alert from '$lib/components/Alert.svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Modal from '$lib/components/ui/Modal/Modal.svelte';
|
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||||
import ModalTitle from '$lib/components/ui/Modal/ModalTitle.svelte';
|
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||||||
import ModalContent from '$lib/components/ui/Modal/ModalContent.svelte';
|
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||||
import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte';
|
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||||
import shortenFileSize from '$lib/helper/shortenFileSize.js';
|
import shortenFileSize from '$lib/helper/shortenFileSize.js';
|
||||||
import Exclamation from '$lib/icons/Exclamation.svelte';
|
import Exclamation from '$lib/icons/Exclamation.svelte';
|
||||||
import FileRect from '$lib/icons/File-rect.svelte';
|
import FileRect from '$lib/icons/File-rect.svelte';
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `/(angemeldet)/view` return true or false
|
// `/(angemeldet)/view` return true or false
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import caseNumberOccupied from '$lib/helper/caseNumberOccupied';
|
|
||||||
import { fail, redirect } from '@sveltejs/kit';
|
|
||||||
import { client } from '$lib/minio';
|
|
||||||
|
|
||||||
/** @type {import('./$types').Actions} */
|
|
||||||
export const actions = {
|
|
||||||
default: async ({ request }: {request: Request}) => {
|
|
||||||
const data = await request.formData();
|
|
||||||
const caseNumber = data.get('caseNumber');
|
|
||||||
const user_token = data.get('token');
|
|
||||||
|
|
||||||
if (!caseNumber) {
|
|
||||||
return fail(400, {
|
|
||||||
success: false,
|
|
||||||
caseNumber,
|
|
||||||
error: { caseNumber: 'Die Vorgangsnummer darf nicht leer sein.' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof caseNumber === 'string' && !(await caseNumberOccupied(caseNumber))) {
|
|
||||||
return fail(400, {
|
|
||||||
success: false,
|
|
||||||
caseNumber,
|
|
||||||
error: { caseNumber: 'Die Vorgangsnummer existiert in dieser Anwendung nicht.' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Ab hier ist Vorgang vorhanden
|
|
||||||
//
|
|
||||||
|
|
||||||
// Jetzt prüfen, ob Code vorhanden ist und
|
|
||||||
// dem eingegebenen Code entspricht
|
|
||||||
|
|
||||||
const token = await get_code_or_null(caseNumber);
|
|
||||||
console.log(`xxx ${token}, ${user_token}`);
|
|
||||||
|
|
||||||
// token vorhanden, check ob gleich sind
|
|
||||||
if (token && token != user_token) {
|
|
||||||
console.log(`ooo token check`);
|
|
||||||
return fail(400, {
|
|
||||||
success: false,
|
|
||||||
caseNumber,
|
|
||||||
error: { token: 'Der Token ist falsch.' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(303, `/list/${caseNumber}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns `code` oder `null`
|
|
||||||
|
|
||||||
async function get_code_or_null(vorg) {
|
|
||||||
const code_name = '__perm__';
|
|
||||||
const obj_path = `${vorg}/${code_name}`;
|
|
||||||
|
|
||||||
let resp = null;
|
|
||||||
let code_saved = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
resp = await client.getObject('tatort', obj_path);
|
|
||||||
code_saved = await new Response(resp).text();
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name == 'S3Error') {
|
|
||||||
resp = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp != null) {
|
|
||||||
return code_saved;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
|
||||||
import Exclamation from '$lib/icons/Exclamation.svelte';
|
|
||||||
|
|
||||||
export let form;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto max-w-2xl">
|
|
||||||
<div class="flex flex-col items-center justify-center w-full">
|
|
||||||
<h1 class="text-xl">Vorgang ansehen</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<div class="space-y-12">
|
|
||||||
<div class="border-b border-gray-900/10 pb-12">
|
|
||||||
<!-- <h2 class="text-base font-semibold leading-7 text-gray-900">Profile</h2> -->
|
|
||||||
<p class="mt-8 text-sm leading-6 text-gray-600">
|
|
||||||
Anhand der Vorgangsnummer werden Sie zu den Dateien des Vorgangs weitergeleitet und können
|
|
||||||
sich den Vorgang dann ansehen.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8">
|
|
||||||
<div>
|
|
||||||
<label for="caseNumber" class="block text-sm font-medium leading-6 text-gray-900"
|
|
||||||
><span class="flex"
|
|
||||||
>{#if form?.error?.caseNumber}
|
|
||||||
<span class="inline-block mr-1"><Exclamation /></span>
|
|
||||||
{/if} Vorgangs-Nr.</span
|
|
||||||
></label
|
|
||||||
>
|
|
||||||
<div class="mt-2 w-full">
|
|
||||||
<div
|
|
||||||
class="flex w-full rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
value={form?.caseNumber ?? ''}
|
|
||||||
type="text"
|
|
||||||
name="caseNumber"
|
|
||||||
id="caseNumber"
|
|
||||||
class="block w-full flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if form?.error?.caseNumber}
|
|
||||||
<p class="block text-sm leading-6 text-red-900 mt-2">{form.error.caseNumber}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="token" class="block text-sm font-medium leading-6 text-gray-900"
|
|
||||||
><span class="flex"> Zugangscode</span></label
|
|
||||||
>
|
|
||||||
<div class="mt-2 w-full">
|
|
||||||
<div
|
|
||||||
class="flex w-full rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
value={false || ''}
|
|
||||||
placeholder="optional"
|
|
||||||
type="text"
|
|
||||||
name="token"
|
|
||||||
id="token"
|
|
||||||
class="block w-full flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if form?.error?.token}
|
|
||||||
<p class="block text-sm leading-6 text-red-900 mt-2">{form.error.token}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6 flex items-center justify-end gap-x-6">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
||||||
>Weiter</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
10
src/routes/(token-based)/+layout.server.ts
Normal file
10
src/routes/(token-based)/+layout.server.ts
Normal file
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
16
src/routes/(token-based)/+layout.svelte
Normal file
16
src/routes/(token-based)/+layout.svelte
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
|
import Header from '$lib/components/Header.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="h-screen v-screen flex flex-col">
|
||||||
|
<Header {data} />
|
||||||
|
|
||||||
|
<div class="mt-10 flex-grow items-center justify-center">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer {data} />
|
||||||
|
</div>
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
import timeElapsed from '$lib/helper/timeElapsed';
|
import timeElapsed from '$lib/helper/timeElapsed';
|
||||||
|
|
||||||
import Alert from '$lib/components/ui/Alert.svelte';
|
import Alert from '$lib/components/Alert.svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Modal from '$lib/components/ui/Modal/Modal.svelte';
|
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||||
import ModalTitle from '$lib/components/ui/Modal/ModalTitle.svelte';
|
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||||||
import ModalContent from '$lib/components/ui/Modal/ModalContent.svelte';
|
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||||
import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte';
|
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||||
import Cube from '$lib/icons/Cube.svelte';
|
import Cube from '$lib/icons/Cube.svelte';
|
||||||
import Edit from '$lib/icons/Edit.svelte';
|
import Edit from '$lib/icons/Edit.svelte';
|
||||||
import Trash from '$lib/icons/Trash.svelte';
|
import Trash from '$lib/icons/Trash.svelte';
|
||||||
@@ -92,7 +92,6 @@
|
|||||||
|
|
||||||
let text_field = document.getElementById(text_field_id);
|
let text_field = document.getElementById(text_field_id);
|
||||||
if (text_field) {
|
if (text_field) {
|
||||||
text_field.setAttribute('contenteditable', 'false');
|
|
||||||
text_field.setAttribute('contenteditable', 'false');
|
text_field.setAttribute('contenteditable', 'false');
|
||||||
text_field.textContent = item.name;
|
text_field.textContent = item.name;
|
||||||
}
|
}
|
||||||
@@ -173,9 +172,8 @@
|
|||||||
>
|
>
|
||||||
<div class=" flex gap-x-4">
|
<div class=" flex gap-x-4">
|
||||||
<Cube />
|
<Cube />
|
||||||
|
|
||||||
<div class="min-w-0 flex-auto">
|
<div class="min-w-0 flex-auto">
|
||||||
{#if data.user.admin}
|
{#if data?.user?.admin}
|
||||||
<span
|
<span
|
||||||
id="label__{item.name}"
|
id="label__{item.name}"
|
||||||
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
|
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { client } from '$lib/minio';
|
import { BUCKET, client } from '$lib/minio';
|
||||||
|
|
||||||
export async function DELETE({ request }: { request: Request }) {
|
export async function DELETE({ request }: { request: Request }) {
|
||||||
const url_fragments = request.url.split('/');
|
const url_fragments = request.url.split('/');
|
||||||
const item = url_fragments.at(-1);
|
const item = url_fragments.at(-1);
|
||||||
const vorgang = url_fragments.at(-2);
|
const vorgang = url_fragments.at(-2);
|
||||||
|
|
||||||
await client.removeObject('tatort', `${vorgang}/${item}`);
|
await client.removeObject(BUCKET, `${vorgang}/${item}`);
|
||||||
|
|
||||||
return new Response(null, { status: 204 });
|
return new Response(null, { status: 204 });
|
||||||
}
|
}
|
||||||
6
src/routes/(token-based)/view/+page.server.ts
Normal file
6
src/routes/(token-based)/view/+page.server.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { redirectIfVorgangExists } from '$lib/server/vorgangService';
|
||||||
|
|
||||||
|
/** @type {import('./$types').Actions} */
|
||||||
|
export const actions = {
|
||||||
|
default: async ({request}: {request: Request}) => redirectIfVorgangExists(request)
|
||||||
|
}
|
||||||
40
src/routes/(token-based)/view/+page.svelte
Normal file
40
src/routes/(token-based)/view/+page.svelte
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import BaseInputField from '$lib/components/BaseInputField.svelte';
|
||||||
|
import Button from '$lib/components/Button.svelte';
|
||||||
|
import ArrowRight from '$lib/icons/Arrow-right.svelte';
|
||||||
|
import Exclamation from '$lib/icons/Exclamation.svelte';
|
||||||
|
|
||||||
|
export let form;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<div class="flex flex-col items-center justify-center w-full">
|
||||||
|
<h1 class="text-xl">Vorgang ansehen</h1>
|
||||||
|
</div>
|
||||||
|
<p class="mt-8 mb-8 text-sm leading-6 text-gray-600">
|
||||||
|
Anhand der Vorgangsnummer werden Sie zu den Dateien des Vorgangs weitergeleitet und können sich
|
||||||
|
den Vorgang dann ansehen.
|
||||||
|
</p>
|
||||||
|
<form method="POST">
|
||||||
|
<BaseInputField
|
||||||
|
id="case-id"
|
||||||
|
name="case-id"
|
||||||
|
label="Vorgangskennung"
|
||||||
|
type="text"
|
||||||
|
value={form?.caseId}
|
||||||
|
/>
|
||||||
|
<div class="mt-5">
|
||||||
|
<BaseInputField
|
||||||
|
id="case-token"
|
||||||
|
name="case-token"
|
||||||
|
label="Zugangscode"
|
||||||
|
type="text"
|
||||||
|
value={form?.token}
|
||||||
|
error={form?.error?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end pt-4">
|
||||||
|
<Button type="submit"><ArrowRight /></Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Panel from '$lib/components/ui/Panel.svelte';
|
import Panel from '$lib/components/Panel.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -1,32 +1,9 @@
|
|||||||
import { dev } from '$app/environment';
|
import { loginUser, logoutUser } from '$lib/server/authService';
|
||||||
import { fail, redirect, type Cookies } from '@sveltejs/kit';
|
import { redirectIfVorgangExists } from '$lib/server/vorgangService.js';
|
||||||
import { authenticate } from '$lib/auth';
|
|
||||||
import type { RequestEvent } from '../(angemeldet)/$types';
|
|
||||||
|
|
||||||
const COOKIE_NAME = 'session';
|
|
||||||
|
|
||||||
/** @type {import('./$types').Actions} */
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
login: async ({ request, cookies }: {request: Request, cookies: Cookies}) => {
|
login: ({ request, cookies }) => loginUser({request, cookies}),
|
||||||
const data = await request.formData();
|
logout: (event) => logoutUser(event),
|
||||||
const user = data.get('user');
|
redirectToVorgang: ({request}) => redirectIfVorgangExists(request)
|
||||||
const password = data.get('password');
|
} as const;
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
throw redirect(303, '/');
|
|
||||||
},
|
|
||||||
logout: async (event: RequestEvent) => {
|
|
||||||
event.cookies.delete(COOKIE_NAME, {path: '/'});
|
|
||||||
event.locals.user = null;
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionData } from "./$types";
|
import BaseInputField from '$lib/components/BaseInputField.svelte';
|
||||||
|
import Button from '$lib/components/Button.svelte';
|
||||||
|
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||||
|
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||||
|
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||||
|
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||||||
|
import ArrowRight from '$lib/icons/Arrow-right.svelte';
|
||||||
|
import Login from '$lib/icons/Login.svelte';
|
||||||
|
|
||||||
export let form: ActionData;
|
export let form;
|
||||||
|
|
||||||
let user = form?.user ?? '';
|
|
||||||
|
|
||||||
|
export let open = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
@@ -12,14 +18,47 @@
|
|||||||
<img class="mx-auto h-10 w-auto" src="/Landeswappen_NI.svg" alt="Landeswappen Niedersachsen" />
|
<img class="mx-auto h-10 w-auto" src="/Landeswappen_NI.svg" alt="Landeswappen Niedersachsen" />
|
||||||
|
|
||||||
<h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
|
<h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
|
||||||
Anmeldung zum 3D Tatort
|
Willkommen beim 3D Tatort
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full max-w-sm mx-auto">
|
||||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
<div class="relative mt-5 bg-gray-50 rounded-xl shadow-xl p-3 pt-1">
|
||||||
<form class="space-y-6" action="?/login" method="POST">
|
<div class="mt-10">
|
||||||
|
<form action="?/redirectToVorgang" method="POST">
|
||||||
|
<BaseInputField
|
||||||
|
id="case-id"
|
||||||
|
name="case-id"
|
||||||
|
label="Vorgangskennung"
|
||||||
|
type="text"
|
||||||
|
value={form?.caseId}
|
||||||
|
/>
|
||||||
|
<div class="mt-5">
|
||||||
|
<BaseInputField
|
||||||
|
id="case-token"
|
||||||
|
name="case-token"
|
||||||
|
label="Zugangscode"
|
||||||
|
type="text"
|
||||||
|
value={form?.token}
|
||||||
|
error={form?.error?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end pt-4">
|
||||||
|
<Button type="submit"><ArrowRight /></Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end mt-10 px-3">
|
||||||
|
<Button on:click={() => (open = true)}><Login /></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal {open}>
|
||||||
|
<ModalTitle>Anmelden</ModalTitle>
|
||||||
|
<ModalContent class="flex justify-center">
|
||||||
|
<form action="?/login" method="POST">
|
||||||
<div>
|
<div>
|
||||||
<label for="user" class="block text-sm font-medium leading-6 text-gray-900">Kennung</label>
|
<label for="user" class="text-sm font-medium leading-6 text-gray-900">Kennung</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="user"
|
id="user"
|
||||||
@@ -27,17 +66,15 @@
|
|||||||
type="text"
|
type="text"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between">
|
<label for="password" class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
<label for="password" class="block text-sm font-medium leading-6 text-gray-900"
|
>Passwort</label
|
||||||
>Passwort</label
|
>
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
@@ -50,13 +87,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="flex justify-end">
|
||||||
<button
|
<Button type="submit" class="mt-5">Anmelden</Button>
|
||||||
type="submit"
|
|
||||||
class="mt-10 flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
||||||
>Anmelden</button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</ModalContent>
|
||||||
</div>
|
<ModalFooter><Button on:click={() => (open = false)}>Ok</Button></ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user