6 Commits

26 changed files with 2090 additions and 251 deletions

View File

@@ -1,2 +1,3 @@
node_modules node_modules
build build
config.json

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
test-results test-results
node_modules node_modules
config.json
# Output # Output
.output .output

View File

@@ -56,4 +56,4 @@ Cases
- id - id
- token - token
- name - name
- pw - pin

1859
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"test:unit": "vitest", "test:unit": "vitest",
"test": "npm run test:unit -- --run && npm run test:e2e", "test": "npm run test:unit -- --run && npm run test:e2e",
"init_db": "npx vite-node src/init/init_db.ts" "init-db": "tsx ./src/init/init_db.ts"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.9", "@eslint/compat": "^1.2.9",
@@ -25,6 +25,7 @@
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/svelte": "^5.2.8", "@testing-library/svelte": "^5.2.8",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"@types/better-sqlite3": "^7.6.13",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
"eslint": "^9.28.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
@@ -35,6 +36,7 @@
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.33.18", "svelte": "^5.33.18",
"svelte-check": "^4.2.1", "svelte-check": "^4.2.1",
"tsx": "^4.20.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.34.0", "typescript-eslint": "^8.34.0",
"vite": "^6.3.5", "vite": "^6.3.5",

View File

@@ -10,15 +10,14 @@ let createSQLStmt = `CREATE TABLE IF NOT EXISTS users
db.exec(createSQLStmt); db.exec(createSQLStmt);
// check if there are any users; if not add one default admin one // check if there are any users; if not add one default admin one
let password = 'pass-123'; const userPassword = 'pass-123';
let hashedPassword = new jsSHA('SHA-512', 'TEXT').update(password).getHash('HEX'); const hashedUserPassword = new jsSHA('SHA-512', 'TEXT').update(userPassword).getHash('HEX');
const checkInsertSQLStmt = `INSERT INTO users (name, pw) SELECT 'admin', '${hashedUserPassword}'
let checkInsertSQLStmt = `INSERT INTO users (name, pw) SELECT 'admin', '${hashedPassword}'
WHERE NOT EXISTS (SELECT * FROM users);`; WHERE NOT EXISTS (SELECT * FROM users);`;
db.exec(checkInsertSQLStmt); db.exec(checkInsertSQLStmt);
let usersSQLStmt = `SELECT * FROM USERS`; const usersSQLStmt = `SELECT * FROM USERS`;
let SQLStatement = db.prepare(usersSQLStmt); let SQLStatement = db.prepare(usersSQLStmt);
// cases table // cases table
@@ -27,11 +26,11 @@ createSQLStmt = `CREATE TABLE IF NOT EXISTS cases
(id INTEGER PRIMARY KEY AUTOINCREMENT, (id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
pw TEXT NOT NULL)`; pin TEXT NOT NULL)`;
db.exec(createSQLStmt); db.exec(createSQLStmt);
let casesSQLStmt = `SELECT * FROM cases`; const vorgangSQLStmt = `SELECT * FROM cases`;
SQLStatement = db.prepare(casesSQLStmt); SQLStatement = db.prepare(vorgangSQLStmt);
db.close(); db.close();

View File

@@ -1,6 +1,5 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import jsSHA from 'jssha'; import jsSHA from 'jssha';
import process from 'process';
import { db } from '$lib/server/dbService'; import { db } from '$lib/server/dbService';
import config from '$lib/config'; import config from '$lib/config';
@@ -8,7 +7,6 @@ import config from '$lib/config';
const SECRET = config.jwt.secret; const SECRET = config.jwt.secret;
const EXPIRES_IN = config.jwt.expiresIn; const EXPIRES_IN = config.jwt.expiresIn;
const AUTH = config.auth;
export function createToken(userData) { export function createToken(userData) {
return jwt.sign(userData, SECRET, { expiresIn: EXPIRES_IN }); return jwt.sign(userData, SECRET, { expiresIn: EXPIRES_IN });
@@ -18,15 +16,15 @@ export function decryptToken(token: string) {
return jwt.verify(token, SECRET); return jwt.verify(token, SECRET);
} }
export function authenticate(user, pass) { export function authenticate(user, password) {
let JWTToken; let JWTToken;
// hash user password // hash user password
let hashedPW = new jsSHA('SHA-512', 'TEXT').update(pass).getHash('HEX'); const hashedPW = new jsSHA('SHA-512', 'TEXT').update(password).getHash('HEX');
let getUserSQLStmt = 'SELECT name, pw FROM users WHERE name = ?'; const getUserSQLStmt = 'SELECT name, pw FROM users WHERE name = ?';
const row = db.prepare(getUserSQLStmt).get(user); const row = db.prepare(getUserSQLStmt).get(user);
let storedPW = row.pw; const storedPW = row.pw;
if (hashedPW && hashedPW === storedPW) { if (hashedPW && hashedPW === storedPW) {
JWTToken = createToken({ id: user, admin: true }); JWTToken = createToken({ id: user, admin: true });

Binary file not shown.

View File

@@ -1,10 +0,0 @@
export default async function get_code(case_no) {
let url = `/api/list/${case_no}/casepw`;
const response = await fetch(url);
if (response.status == 200) {
return response.text();
} else {
return -1;
}
}

View File

@@ -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 vorgangNumberOccupied (vorgangNumber: string): Promise<boolean> {
const prefix = `${caseNumber}`; const prefix = `${vorgangNumber}`;
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', () => {

View File

@@ -6,11 +6,11 @@ import { db } from './dbService';
/** /**
* Get Vorgang and corresponend list of tatorte * Get Vorgang and corresponend list of tatorte
* @param caseToken * @param vorgangToken
* @returns * @returns
*/ */
export const getCrimesListByToken = async (caseToken: string) => { export const getCrimesListByToken = async (vorgangToken: string) => {
const prefix = `${caseToken}/`; const prefix = `${vorgangToken}/`;
const stream = client.listObjectsV2(BUCKET, prefix, false, ''); const stream = client.listObjectsV2(BUCKET, prefix, false, '');
@@ -28,46 +28,46 @@ export const getCrimesListByToken = async (caseToken: string) => {
/** /**
* Get Vorgang * Get Vorgang
* @param caseToken * @param vorgangToken
* @returns caseObj with keys `token`, `name`, `pw` || undefined * @returns vorgangObj with keys `token`, `name`, `pin` || undefined
*/ */
export const getVorgangByToken = function (caseToken: string) { export const getVorgangByToken = (vorgangToken: string): {token: string, name:string, pin: string} | undefined => {
let getVorgangSQLStmt = `SELECT token, name, pw FROM cases WHERE token = ?`; const getVorgangSQLStmt = `SELECT token, name, pin FROM cases WHERE token = ?`;
const statement = db.prepare(getVorgangSQLStmt); const statement = db.prepare(getVorgangSQLStmt);
const result = statement.get(caseToken); const result = statement.get(vorgangToken) as {token: string, name:string, pin: string} | undefined;
return result; return result;
}; };
/** /**
* Get Vorgang * Get Vorgang
* @param caseName * @param vorgangName
* @returns caseObj with keys `token`, `name`, `pw` || undefined * @returns vorgangObj with keys `token`, `name`, `pin` || undefined
*/ */
export const getVorgangByName = function (caseName: string) { export const getVorgangByName = (vorgangName: string): {token: string, name: string, pin: string} | undefined => {
let getVorgangByNameSQLStmt = `SELECT token, name, pw FROM cases WHERE name = ?`; const getVorgangByNameSQLStmt = `SELECT token, name, pin FROM cases WHERE name = ?`;
const statement = db.prepare(getVorgangByNameSQLStmt); const statement = db.prepare(getVorgangByNameSQLStmt);
const result = statement.get(caseName); const result = statement.get(vorgangName) as {token: string, name: string, pin: string} | undefined;
return result; return result;
}; };
/** /**
* Delete Vorgang * Delete Vorgang
* @param caseToken * @param vorgangToken
* @returns int: number of changes * @returns int: number of changes
*/ */
export const deleteVorgangByToken = function (caseToken: string) { export const deleteVorgangByToken = function (vorgangToken: string) {
let deleteSQLStmt = 'DELETE FROM cases WHERE token = ?'; const deleteSQLStmt = 'DELETE FROM cases WHERE token = ?';
const statement = db.prepare(deleteSQLStmt); const statement = db.prepare(deleteSQLStmt);
const info = statement.run(caseToken); const info = statement.run(vorgangToken);
return info.changes; return info.changes;
}; };
/** /**
* Fetches list of vorgänge from s3 bucket * Fetches list of vorgänge from s3 bucket
* @returns list of available cases * @returns list of available vorgaenge
*/ */
export const getListOfVorgänge = async () => { export const getListOfVorgänge = async () => {
const stream = client.listObjectsV2(BUCKET, '', false, ''); const stream = client.listObjectsV2(BUCKET, '', false, '');
@@ -86,15 +86,15 @@ export const getListOfVorgänge = async () => {
/** /**
* Fetches list of vorgänge from database * Fetches list of vorgänge from database
* @returns list with of available cases * @returns list with of available vorgaenge
*/ */
export const getVorgaenge = function () { export const getVorgaenge = (): {vorgangToken: string, vorgangName: string, vorgangPIN: string}[] => {
let getVorgaengeSQLStmt = `SELECT token, name, pw from cases`; const getVorgaengeSQLStmt = `SELECT token, name, pin from cases`;
const statement = db.prepare(getVorgaengeSQLStmt); const statement = db.prepare(getVorgaengeSQLStmt);
const result = statement.all(); const result = statement.all() as { token: string; name: string; pin: string }[];
const vorgaenge_list = []; const vorgaenge_list: {vorgangToken: string, vorgangName: string, vorgangPIN: string}[] = [];
for (const r of result) { for (const resultItem of result) {
const vorg = { token: r.token, name: r.name, pw: r.pw }; const vorg = { vorgangToken: resultItem.token, vorgangName: resultItem.name, vorgangPIN: resultItem.pin };
vorgaenge_list.push(vorg); vorgaenge_list.push(vorg);
} }
@@ -106,19 +106,19 @@ export const getVorgaenge = function () {
* @param request * @param request
* @returns fail or true * @returns fail or true
*/ */
export const checkIfVorgangExists = async (caseId: string | null) => { export const checkIfVorgangExists = async (vorgangId: string | null) => {
if (!caseId) { if (!vorgangId) {
return fail(400, { return fail(400, {
success: false, success: false,
caseId, vorgangId,
error: { message: 'Die Vorgangsnummer darf nicht leer sein.' } error: { message: 'Die Vorgangsnummer darf nicht leer sein.' }
}); });
} }
if (typeof caseId === 'string' && !(await checkIfExactDirectoryExists(caseId))) { if (typeof vorgangId === 'string' && !(await checkIfExactDirectoryExists(vorgangId))) {
return fail(400, { return fail(400, {
success: false, success: false,
caseId, vorgangId,
error: { message: 'Die Vorgangsnummer existiert in dieser Anwendung nicht.' } error: { message: 'Die Vorgangsnummer existiert in dieser Anwendung nicht.' }
}); });
} }
@@ -126,42 +126,42 @@ export const checkIfVorgangExists = async (caseId: string | null) => {
return true; return true;
}; };
export const vorgangExists = function (caseToken: string | null) { export const vorgangExists = function (vorgangToken: string | null) {
if (!caseToken) { if (!vorgangToken) {
return fail(400, { return fail(400, {
success: false, success: false,
caseId: caseToken, vorgangId: vorgangToken,
error: { message: 'Die Vorgangsnummer darf nicht leer sein.' } error: { message: 'Die Vorgangsnummer darf nicht leer sein.' }
}); });
} }
let vorgaenge = getVorgaenge(); const vorgaenge = getVorgaenge();
const vorgaenge_tokens = vorgaenge.map((vorg) => vorg.token); const vorgaengeTokens = vorgaenge.map((vorgang) => vorgang.vorgangToken);
const found = vorgaenge_tokens.indexOf(caseToken) != -1; const found = vorgaengeTokens.indexOf(vorgangToken) != -1;
return found; return found;
}; };
export const vorgangNameExists = function (caseName: string) { export const vorgangNameExists = (vorgangName: string) => {
let vorgaenge = getVorgaenge(); const vorgaenge = getVorgaenge();
const vorgaengeNames = vorgaenge.map((vorg) => vorg.name); const vorgaengeNames = vorgaenge.map((vorgang) => vorgang.vorgangName);
const found = vorgaengeNames.indexOf(caseName) != -1; const found = vorgaengeNames.indexOf(vorgangName) != -1;
return found; return found;
}; };
export const hasValidToken = async (caseId: string, caseToken: string) => { export const hasValidToken = async (vorgangId: string, vorgangToken: string) => {
const objPath = `${caseId}/${TOKENFILENAME}`; const objPath = `${vorgangId}/${TOKENFILENAME}`;
try { try {
if (!caseToken) { if (!vorgangToken) {
return false; return false;
} }
const token = await getContentOfTextObject(BUCKET, objPath); const token = await getContentOfTextObject(BUCKET, objPath);
if (!token || token !== caseToken) { if (!token || token !== vorgangToken) {
return false; return false;
} }
@@ -174,14 +174,14 @@ export const hasValidToken = async (caseId: string, caseToken: string) => {
} }
}; };
export const passwordValid = function (caseToken, casePassword) { export const vorgangPINValidation = function (vorgangToken: string, vorgangPIN: string) {
if (!casePassword) { if (!vorgangPIN) {
return false; return false;
} }
const vorg = getVorgangByToken(caseToken); const vorgang = getVorgangByToken(vorgangToken);
if (!vorg || vorg.pw !== casePassword) { if (!vorgang || vorgang.pin !== vorgangPIN) {
return false; return false;
} }

View File

@@ -1,11 +1,10 @@
import { getListOfVorgänge, getVorgaenge } from '$lib/server/vorgangService'; import { getVorgaenge } from '$lib/server/vorgangService';
import type { PageServerLoad } from '../../(token-based)/view/$types'; import type { PageServerLoad } from '../../(token-based)/view/$types';
export const load: PageServerLoad = async () => { export const load: PageServerLoad = async () => {
// const caseList = await getListOfVorgänge(); const vorgangList = await getVorgaenge();
const caseList = getVorgaenge();
return { return {
caseList vorgangList
}; };
}; };

View File

@@ -5,7 +5,7 @@
export let data: PageData; export let data: PageData;
const caseList = data.caseList; const vorgangList = data.vorgangList;
async function delete_item(ev: Event) { async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?'); let delete_item = window.confirm('Bist du sicher?');
@@ -44,20 +44,20 @@
</div> </div>
<div class="mx-auto flex justify-center max-w-7xl h-full"> <div class="mx-auto flex justify-center max-w-7xl h-full">
<ul role="list" class="divide-y divide-gray-100"> <ul role="list" class="divide-y divide-gray-100">
{#each caseList as item} {#each vorgangList as vorgangItem}
<li> <li>
<a href="/list/{item.token}?pw={item.pw}" class="flex justify-between gap-x-6 py-5"> <a href="/list/{vorgangItem.vorgangToken}?pin={vorgangItem.vorgangPIN}" class="flex justify-between gap-x-6 py-5">
<div class="flex gap-x-4"> <div class="flex gap-x-4">
<!-- Ordner --> <!-- Ordner -->
<Folder /> <Folder />
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<span class="text-sm font-semibold leading-6 text-gray-900">{item.name}</span> <span class="text-sm font-semibold leading-6 text-gray-900">{vorgangItem.vorgangName}</span>
<!-- Delete button --> <!-- Delete button -->
<button <button
style="padding: 2px" style="padding: 2px"
id="del__{item.token}" id="del__{vorgangItem.vorgangToken}"
on:click|preventDefault={delete_item} on:click|preventDefault={delete_item}
aria-label="Vorgang {item.name} löschen" aria-label="Vorgang {vorgangItem.name} löschen"
> >
<Trash /> <Trash />
</button> </button>

View File

@@ -1,34 +1,34 @@
import { client } from '$lib/minio'; import { client } from '$lib/minio';
import { fail } from '@sveltejs/kit'; import { fail } from '@sveltejs/kit';
import caseNumberOccupied from '$lib/helper/caseNumberOccupied'; import vorgangNumberOccupied from '$lib/helper/vorgangNumberOccupied.js';
/** @type {import('./$types').Actions} */ /** @type {import('./$types').Actions} */
export const actions = { export const actions = {
default: async ({ request }: {request: Request}) => { default: async ({ request }: {request: Request}) => {
const data = await request.formData(); const data = await request.formData();
const caseNumber = data.get('caseNumber'); const vorgangNumber = data.get('vorgangNumber');
const description = data.get('description'); const description = data.get('description');
if (!caseNumber) { if (!vorgangNumber) {
return fail(400, { return fail(400, {
caseNumber, vorgangNumber,
description, description,
error: { caseNumber: 'Es muss eine Vorgangsnummer vorhanden sein.' } error: { vorgangNumber: 'Es muss eine Vorgangsnummer vorhanden sein.' }
}); });
} }
if (await caseNumberOccupied(`${caseNumber}`)) { if (await vorgangNumberOccupied(`${vorgangNumber}`)) {
return fail(400, { return fail(400, {
caseNumber, vorgangNumber,
description, description,
error: { caseNumber: 'Die Vorgangsnummer wurde im System bereits angelegt.' } error: { vorgangNumber: 'Die Vorgangsnummer wurde im System bereits angelegt.' }
}); });
} }
const config = `${JSON.stringify({ caseNumber, description, version: 1 })}\n`; const config = `${JSON.stringify({ vorgangNumber, description, version: 1 })}\n`;
await client.putObject('tatort', `${caseNumber}/config.json`, config, undefined, { await client.putObject('tatort', `${vorgangNumber}/config.json`, config, undefined, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });

View File

@@ -27,9 +27,9 @@
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8"> <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8">
<div> <div>
<label for="caseNumber" class="block text-sm font-medium leading-6 text-gray-900" <label for="vorgangNumber" class="block text-sm font-medium leading-6 text-gray-900"
><span class="flex" ><span class="flex"
>{#if form?.error?.caseNumber} >{#if form?.error?.vorgangNumber}
<span class="inline-block mr-1"><Exclamation /></span> <span class="inline-block mr-1"><Exclamation /></span>
{/if} Vorgangs-Nr.</span {/if} Vorgangs-Nr.</span
></label ></label
@@ -39,16 +39,16 @@
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" 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 <input
value={form?.caseNumber ?? ''} value={form?.vorgangNumber ?? ''}
type="text" type="text"
name="caseNumber" name="vorgangNumber"
id="caseNumber" id="vorgangNumber"
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" 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"
/> />
</div> </div>
</div> </div>
{#if form?.error?.caseNumber} {#if form?.error?.vorgangNumber}
<p class="block text-sm leading-6 text-red-900 mt-2">{form.error.caseNumber}</p> <p class="block text-sm leading-6 text-red-900 mt-2">{form.error.vorgangNumber}</p>
{/if} {/if}
</div> </div>
@@ -74,8 +74,8 @@
{/if} {/if}
</div> </div>
<label for="code"> <label for="vorgang-token">
<span >Zugangscode (optional) </span> <span >Zugangstoken (optional) </span>
</label> </label>
<div class="mt-2"> <div class="mt-2">
@@ -83,7 +83,7 @@
> >
<input <input
type="text" type="text"
id="code" id="vorgang-token"
/> />
</div> </div>
</div> </div>

View File

@@ -17,36 +17,31 @@ const isRequiredFieldValid = (value: unknown) => {
export const actions = { export const actions = {
url: async ({ request }: { request: Request }) => { url: async ({ request }: { request: Request }) => {
const data = await request.formData(); const data = await request.formData();
const caseName = data.get('vorgang'); const vorgangName = data.get('vorgang');
const crimeName = data.get('name'); const crimeName = data.get('name');
const type = data.get('type'); const type = data.get('type');
const password = data.get('password'); const vorgangPIN = data.get('vorgangPIN');
const fileName = data.get('fileName'); const fileName = data.get('fileName');
// store case in database const vorgangExists = vorgangNameExists(vorgangName);
// skip if Vorgang exists and token not changed let vorgangToken;
const vorgangExists = vorgangNameExists(caseName);
let token;
if (!vorgangExists) { if (!vorgangExists) {
token = uuidv4(); vorgangToken = uuidv4();
let insertSQLStatement = `INSERT INTO cases (token, name, pw) VALUES (?, ?, ?)`; const insertSQLStatement = `INSERT INTO cases (token, name, pin) VALUES (?, ?, ?)`;
const statement = db.prepare(insertSQLStatement); const statement = db.prepare(insertSQLStatement);
statement.run(token, caseName, password); statement.run(vorgangToken, vorgangName, vorgangPIN);
} else { } else {
// vorgang exists const vorgang = getVorgangByName(vorgangName);
// check if PW was changed, and update DB if it was vorgangToken = vorgang.token;
const vorg = getVorgangByName(caseName); if (vorgang && vorgang.pin != vorgangPIN) {
token = vorg.token; const updateSQLStmt = `UPDATE cases SET pin = ? WHERE name = ?`;
if (vorg.pw != password) {
let updateSQLStmt = `UPDATE cases SET pw = ? WHERE name = ?`;
const statement = db.prepare(updateSQLStmt); const statement = db.prepare(updateSQLStmt);
statement.run(password, vorg); statement.run(vorgangPIN, vorgang);
} }
} }
let objectName = `${token}/${crimeName}`; let objectName = `${vorgangToken}/${crimeName}`;
switch (type) { switch (type) {
case 'image/png': case 'image/png':
if (!objectName.endsWith('.png')) objectName += '.png'; if (!objectName.endsWith('.png')) objectName += '.png';
@@ -65,24 +60,27 @@ export const actions = {
const data = Object.fromEntries(requestData); const data = Object.fromEntries(requestData);
const vorgang = data.vorgang; const vorgang = data.vorgang;
const name = data.name; const name = data.name;
const password = data.password; const vorgangPIN = data.vorgangPIN;
let success = true; let success = true;
const err = {}; const err = {};
if (isRequiredFieldValid(vorgang)) err.vorgang = null; if (isRequiredFieldValid(vorgang)) {
else { err.vorgang = null;
} else {
err.vorgang = 'Das Feld Vorgang darf nicht leer bleiben.'; err.vorgang = 'Das Feld Vorgang darf nicht leer bleiben.';
success = false; success = false;
} }
if (isRequiredFieldValid(name)) err.name = null; if (isRequiredFieldValid(name)) {
else { err.name = null;
} else {
err.name = 'Das Feld Name darf nicht leer bleiben.'; err.name = 'Das Feld Name darf nicht leer bleiben.';
success = false; success = false;
} }
if (isRequiredFieldValid(password)) err.password = null; if (isRequiredFieldValid(vorgangPIN)) {
else { err.vorgangPIN = null;
err.password = 'Das Feld Zugangspasswort darf nicht leer bleiben.'; } else {
err.vorgangPIN = 'Das Feld Zugangspasswort darf nicht leer bleiben.';
success = false; success = false;
} }

View File

@@ -15,20 +15,20 @@
let open = false; let open = false;
let inProgress = false; let inProgress = false;
let vorgang = ''; let vorgang = '';
const code_len = 8; const PINLength = 8;
function generatePassword() { function generatePIN() {
return Math.random() return Math.random()
.toString(36) .toString(36)
.slice(2, 2 + code_len); .slice(2, 2 + PINLength);
} }
let zugangspasswort = '' let vorgangPIN = '';
let zugangspasswordOld = '' let vorgangPINOld = '';
$: zugangspasswordOld = generatePassword(); $: vorgangPINOld = generatePIN();
$: zugangspasswort = zugangspasswordOld $: vorgangPIN = vorgangPINOld;
let caseExisting = undefined; let vorgangExists = undefined;
$: caseExisting = false; $: vorgangExists = false;
let name = ''; let name = '';
let etag: string | null = null; let etag: string | null = null;
@@ -36,13 +36,13 @@
$: inProgress = form === null; $: inProgress = form === null;
let formErrors: Record<string,any> | null; let formErrors: Record<string, any> | null;
async function validateForm() { async function validateForm() {
let data = new FormData(); let data = new FormData();
data.append('vorgang', vorgang); data.append('vorgang', vorgang);
data.append('name', name); data.append('name', name);
data.append('password', zugangspasswort); data.append('vorgangPIN', vorgangPIN);
const response = await fetch('?/validate', { method: 'POST', body: data }); const response = await fetch('?/validate', { method: 'POST', body: data });
/** @type {import('@sveltejs/kit').ActionResult} */ /** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text()); const result = deserialize(await response.text());
@@ -71,7 +71,7 @@
let data = new FormData(); let data = new FormData();
data.append('vorgang', vorgang); data.append('vorgang', vorgang);
data.append('name', name); data.append('name', name);
data.append('password', zugangspasswort); data.append('vorgangPIN', vorgangPIN);
if (files?.length === 1) { if (files?.length === 1) {
data.append('type', files[0].type); data.append('type', files[0].type);
data.append('fileName', files[0].name); data.append('fileName', files[0].name);
@@ -139,6 +139,7 @@
// big endian! // big endian!
let file = files[0]; let file = files[0];
let file_header = file.slice(0, 4); let file_header = file.slice(0, 4);
console.log(file_header);
let header_bytes = await file_header.bytes(); let header_bytes = await file_header.bytes();
let file_header_hex = '0x' + header_bytes.toHex().toString(); let file_header_hex = '0x' + header_bytes.toHex().toString();
@@ -151,37 +152,40 @@
} }
// `/(angemeldet)/view` return true or false // `/(angemeldet)/view` return true or false
async function caseExists(caseName: string) { async function checkVorgangExists(vorgangName: string) {
if (vorgangName == '') {
if (caseName == '') { vorgangPIN = vorgangPINOld;
zugangspasswort = zugangspasswordOld;
return; return;
} }
let url = `/api/list/${caseName}` try {
const url = `/api/list/${vorgangName}`;
const response = await fetch(url, { method: 'HEAD' });
const response = await fetch(url, { method: 'HEAD'}); if (response.status === 200) {
const status = response.status; console.log('Vorgang existiert:', vorgangName);
vorgangExists = true;
if (status == 200) { const token = await getVorgangPIN(vorgangName);
caseExisting = true; vorgangPIN = token;
const passwort = await getPassword(caseName); return true;
zugangspasswort = passwort; } else {
console.log('Vorgang existiert nicht!');
return true vorgangExists = false;
vorgangPIN = vorgangPINOld;
} else { return false;
caseExisting = false; }
zugangspasswort = zugangspasswordOld; } catch (err) {
return false console.error('Fehler bei checkVorgangExists:', err);
vorgangExists = false;
vorgangPIN = vorgangPINOld;
return false;
} }
} }
async function getPassword(caseName: string) { async function getVorgangPIN(vorgangName: string) {
if (vorgangName == '') return;
if (caseName == '') return; let url = `/api/list/${vorgangName}/vorgangPIN`;
let url = `/api/list/${caseName}/casepw`;
const response = await fetch(url); const response = await fetch(url);
if (response.status == 200) { if (response.status == 200) {
@@ -190,7 +194,6 @@
return -1; return -1;
} }
} }
</script> </script>
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">
@@ -211,7 +214,7 @@
><span class="flex" ><span class="flex"
>{#if formErrors?.vorgang} >{#if formErrors?.vorgang}
<span class="inline-block mr-1"><Exclamation /></span> <span class="inline-block mr-1"><Exclamation /></span>
{/if} Vorgang</span {/if} Vorgangsname</span
></label ></label
> >
<div class="mt-2"> <div class="mt-2">
@@ -225,14 +228,14 @@
id="vorgang" id="vorgang"
autocomplete={vorgang} autocomplete={vorgang}
class="block 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" class="block 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"
on:input={() => caseExists(vorgang)} on:input={() => checkVorgangExists(vorgang)}
/> />
</div> </div>
</div> </div>
{#if formErrors?.vorgang} {#if formErrors?.vorgang}
<p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.vorgang}</p> <p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.vorgang}</p>
{/if} {/if}
{#if caseExisting && vorgang.length > 0} {#if vorgangExists && vorgang.length > 0}
<span>Datei wird zum existierenden Vorgang hinzugefügt.</span> <span>Datei wird zum existierenden Vorgang hinzugefügt.</span>
{:else if vorgang.length > 0} {:else if vorgang.length > 0}
<span>Neuer Vorgang wird angelegt.</span> <span>Neuer Vorgang wird angelegt.</span>
@@ -241,10 +244,10 @@
<div> <div>
<label for="name" class="block text-sm font-medium leading-6 text-gray-900" <label for="name" class="block text-sm font-medium leading-6 text-gray-900"
><span class="flex" ><span class="flex"
>{#if formErrors?.name} >{#if formErrors?.name}
<span class="inline-block mr-1"><Exclamation /></span> <span class="inline-block mr-1"><Exclamation /></span>
{/if} Name</span {/if} Modellname</span
></label ></label
> >
<div class="mt-2"> <div class="mt-2">
@@ -267,11 +270,11 @@
</div> </div>
<div> <div>
<label for="zugangscode" class="block text-sm font-medium leading-6 text-gray-900" <label for="vorgang-pin" class="block text-sm font-medium leading-6 text-gray-900"
><span class="flex" ><span class="flex"
>{#if formErrors?.zugangscode} >{#if formErrors?.vorgangPIN}
<span class="inline-block mr-1"><Exclamation /></span> <span class="inline-block mr-1"><Exclamation /></span>
{/if} Zugangscode</span {/if} Zugangs-PIN</span
></label ></label
> >
<div class="mt-2"> <div class="mt-2">
@@ -279,25 +282,28 @@
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" 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 <input
bind:value={zugangspasswort} bind:value={vorgangPIN}
type="text" type="text"
name="zugangscode" name="vorgang-pin"
id="zugangscode" id="vorgang-pin"
on:input="{ (ev) => { zugangspasswordOld = ev.target.value }}" on:input={(ev) => {
vorgangPINOld = ev.target.value;
}}
class="block 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" class="block 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>
<button <button
class="rounded-md bg-blue-500 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" class="rounded-md bg-blue-500 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"
on:click="{() => { on:click={() => {
zugangspasswort = zugangspasswordOld = generatePassword(); }}" vorgangPIN = vorgangPINOld = generatePIN();
type="button"> }}
Generiere Zugangscode type="button"
>
Generiere Zugangs-PIN
</button> </button>
</div> </div>
{#if formErrors?.code} {#if formErrors?.vorgangPIN}
<p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.code}</p> <p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.vorgangPIN}</p>
{/if} {/if}
</div> </div>

View File

@@ -1,7 +1,5 @@
import { import {
checkIfVorgangExists, vorgangPINValidation,
hasValidToken,
passwordValid,
vorgangExists vorgangExists
} from '$lib/server/vorgangService'; } from '$lib/server/vorgangService';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
@@ -14,11 +12,11 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
}; };
} }
const caseToken = params.vorgang; const vorgangToken = params.vorgang;
const casePassword = url.searchParams.get('pw'); const vorgangPIN = url.searchParams.get('pin');
const isVorgangValid = vorgangExists(caseToken); const isVorgangValid = vorgangExists(vorgangToken);
const isPasswordValid = passwordValid(caseToken, casePassword); const isVorgangPINValid = vorgangPINValidation(vorgangToken, vorgangPIN);
if (!isVorgangValid || !isPasswordValid) throw redirect(303, `/anmeldung?vorgang=${caseToken}`); if (!isVorgangValid || !isVorgangPINValid) throw redirect(303, `/anmeldung?vorgang=${vorgangToken}`);
}; };

View File

@@ -2,15 +2,15 @@ import { getVorgangByToken, getCrimesListByToken } from '$lib/server/vorgangServ
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, url }) => { export const load: PageServerLoad = async ({ params, url }) => {
const caseToken = params.vorgang; const vorgangToken = params.vorgang;
const casePassword = url.searchParams.get('pw'); const vorgangPIN = url.searchParams.get('pin');
const crimesList = await getCrimesListByToken(caseToken); const crimesList = await getCrimesListByToken(vorgangToken);
const vorgang = getVorgangByToken(caseToken); const vorgang = getVorgangByToken(vorgangToken);
return { return {
crimesList, crimesList,
casePassword, vorgangPIN,
vorgang vorgang
}; };
}; };

View File

@@ -28,7 +28,7 @@
const vorgang = data.vorgang; const vorgang = data.vorgang;
const crimesList: ListItem[] = data.crimesList; const crimesList: ListItem[] = data.crimesList;
const password: string = data.casePassword; const vorgangPIN: string = data.vorgangPIN;
let open = false; let open = false;
$: open; $: open;
@@ -143,7 +143,7 @@
<div class="flex flex-col items-center justify-center w-full"> <div class="flex flex-col items-center justify-center w-full">
<h1 class="text-xl">Vorgang {vorgang.name}</h1> <h1 class="text-xl">Vorgang {vorgang.name}</h1>
{#if data?.user?.admin} {#if data?.user?.admin}
Zugangspasswort: {vorgang.pw} Zugangs-PIN: {vorgang.pin}
<Button on:click={() => setClipboard($page.url.toString().split('?')[0])}>Copy Link</Button> <Button on:click={() => setClipboard($page.url.toString().split('?')[0])}>Copy Link</Button>
{/if} {/if}
</div> </div>
@@ -152,7 +152,7 @@
{#each crimesList as item, i} {#each crimesList as item, i}
<li> <li>
<a <a
href="/view/{$page.params.vorgang}/{item.name}?pw={password}" href="/view/{$page.params.vorgang}/{item.name}?pin={vorgangPIN}"
class=" flex justify-between gap-x-6 py-5" class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell" aria-label="zum 3D-modell"
> >

View File

@@ -3,9 +3,9 @@ import { redirect } from '@sveltejs/kit';
export const actions = { export const actions = {
default: async ({request}: {request: Request}) => { default: async ({request}: {request: Request}) => {
const data = await request.formData(); const data = await request.formData();
const caseId = data.get('case-id'); const vorgangId = data.get('vorgang-id');
const caseToken = data.get('case-token'); const vorgangToken = data.get('vorgang-token');
if( caseId && caseToken) throw redirect(303, `/list/${caseId}?token=${caseToken}`); if( vorgangId && vorgangToken) throw redirect(303, `/list/${vorgangId}?token=${vorgangToken}`);
} }
} }

View File

@@ -16,19 +16,19 @@
</p> </p>
<form method="POST"> <form method="POST">
<BaseInputField <BaseInputField
id="case-id" id="vorgang-id"
name="case-id" name="vorgang-id"
label="Vorgangskennung" label="Vorgangskennung"
type="text" type="text"
value={form?.caseId} value={form?.vorgangId}
/> />
<div class="mt-5"> <div class="mt-5">
<BaseInputField <BaseInputField
id="case-token" id="vorgang-token"
name="case-token" name="vorgang-token"
label="Zugangscode" label="Zugangstoken"
type="text" type="text"
value={form?.token} value={form?.vorgangToken}
error={form?.error?.message} error={form?.error?.message}
/> />
</div> </div>

View File

@@ -6,13 +6,11 @@ export const actions = {
logout: (event) => logoutUser(event), logout: (event) => logoutUser(event),
getVorgangByToken: async ({ request }) => { getVorgangByToken: async ({ request }) => {
const data = await request.formData(); const data = await request.formData();
const caseToken = data.get('case-token'); const vorgangToken = data.get('vorgang-token');
const casePassword = data.get('case-password'); const vorgangPIN = data.get('vorgang-pin');
console.log(`+++ ${caseToken} + ${casePassword}`); if (!vorgangToken || !vorgangPIN) return;
if (!caseToken || !casePassword) return; throw redirect(303, `/list/${vorgangToken}?pin=${vorgangPIN}`);
throw redirect(303, `/list/${caseToken}?pw=${casePassword}`);
} }
} as const; } as const;

View File

@@ -29,19 +29,19 @@
<div class="mt-10"> <div class="mt-10">
<form action="?/getVorgangByToken" method="POST"> <form action="?/getVorgangByToken" method="POST">
<BaseInputField <BaseInputField
id="case-token" id="vorgang-token"
name="case-token" name="vorgang-token"
label="Vorgangskennung" label="Vorgangskennung"
type="text" type="text"
value={vorgangToken} value={vorgangToken}
/> />
<div class="mt-5"> <div class="mt-5">
<BaseInputField <BaseInputField
id="case-password" id="vorgang-pin"
name="case-password" name="vorgang-pin"
label="Zugangspasswort" label="Zugangs-PIN"
type="text" type="text"
value={form?.password} value={form?.vorgangPIN}
error={form?.error?.message} error={form?.error?.message}
/> />
</div> </div>

View File

@@ -1,11 +1,5 @@
import { client } from '$lib/minio'; import { client } from '$lib/minio';
import { db } from '$lib/server/dbService'; import { deleteVorgangByToken, vorgangNameExists } from '$lib/server/vorgangService';
import {
deleteVorgangByToken,
getVorgangByToken,
getVorgangByName,
vorgangNameExists
} from '$lib/server/vorgangService';
export async function DELETE({ params }) { export async function DELETE({ params }) {
const vorgangToken = params.vorgang; const vorgangToken = params.vorgang;
@@ -32,13 +26,15 @@ export async function DELETE({ params }) {
} }
export async function HEAD({ params }) { export async function HEAD({ params }) {
const vorgangName = params.vorgang; try {
const vorgangName = params.vorgang;
const existing = vorgangNameExists(vorgangName);
const existing = vorgangNameExists(vorgangName); return new Response(null, {
status: existing ? 200 : 404
if (existing) { });
return new Response(null, { status: 200 }); } catch (err) {
} else { console.error('Fehler im HEAD-Handler:', err);
return new Response(null, { status: 404 }); return new Response(null, { status: 500 });
} }
} }

View File

@@ -4,12 +4,12 @@ import { db } from '$lib/server/dbService';
export async function GET({ params }) { export async function GET({ params }) {
const vorgangName = params.vorgang; const vorgangName = params.vorgang;
let getCodeSQLStatement = `SELECT pw FROM cases WHERE name = ?;`; const getPINSQLStatement = `SELECT pin FROM cases WHERE name = ?;`;
const row = db.prepare(getCodeSQLStatement).get(vorgangName); const row = db.prepare(getPINSQLStatement).get(vorgangName);
let password = row.pw; const vorgangPIN = row?.pin;
if (password) { if (vorgangPIN) {
return new Response(password, { status: 200 }); return new Response(vorgangPIN, { status: 200 });
} else { } else {
return new Response(null, { status: 404 }); return new Response(null, { status: 404 });
} }