diff --git a/package-lock.json b/package-lock.json index c3e8588..9e00d04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@sveltejs/adapter-node": "^5.2.12", "@tailwindcss/forms": "^0.5.10", "autoprefixer": "^10.4.21", + "bcrypt": "^6.0.0", "better-sqlite3": "^12.2.0", "jsonwebtoken": "^9.0.2", - "jssha": "^3.3.1", "minio": "^8.0.5", "postcss": "^8.5.4", "sqlite3": "^5.1.7", @@ -2483,6 +2483,29 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/better-sqlite3": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.2.0.tgz", @@ -4617,15 +4640,6 @@ "npm": ">=6" } }, - "node_modules/jssha": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", - "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -5309,6 +5323,17 @@ "node": ">= 10.12.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", diff --git a/package.json b/package.json index 9d7f33c..4742a0c 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,9 @@ "@sveltejs/adapter-node": "^5.2.12", "@tailwindcss/forms": "^0.5.10", "autoprefixer": "^10.4.21", + "bcrypt": "^6.0.0", "better-sqlite3": "^12.2.0", "jsonwebtoken": "^9.0.2", - "jssha": "^3.3.1", "minio": "^8.0.5", "postcss": "^8.5.4", "sqlite3": "^5.1.7", diff --git a/src/init/init_db.ts b/src/init/init_db.ts index 559708b..394bb58 100644 --- a/src/init/init_db.ts +++ b/src/init/init_db.ts @@ -1,17 +1,18 @@ import Database from 'better-sqlite3'; -import jsSHA from 'jssha'; +import bcrypt from 'bcrypt'; const db = new Database('./src/lib/data/tatort.db'); let createSQLStmt = `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, pw TEXT NOT NULL)`; db.exec(createSQLStmt); // check if there are any users; if not add one default admin one const userPassword = 'A-InnoHUB_2025!'; -const hashedUserPassword = new jsSHA('SHA-512', 'TEXT').update(userPassword).getHash('HEX'); +const saltRounds = 12; +const hashedUserPassword = bcrypt.hashSync(userPassword, saltRounds); const checkInsertSQLStmt = `INSERT INTO users (name, pw) SELECT 'admin', '${hashedUserPassword}' WHERE NOT EXISTS (SELECT * FROM users);`; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index c31d5ec..8895111 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,5 +1,5 @@ import jwt from 'jsonwebtoken'; -import jsSHA from 'jssha'; +import bcrypt from 'bcrypt'; import { db } from '$lib/server/dbService'; import config from '$lib/config'; @@ -7,7 +7,6 @@ import config from '$lib/config'; const SECRET = config.jwt.secret; const EXPIRES_IN = config.jwt.expiresIn; - export function createToken(userData) { return jwt.sign(userData, SECRET, { expiresIn: EXPIRES_IN }); } @@ -19,14 +18,16 @@ export function decryptToken(token: string) { export function authenticate(user, password) { let JWTToken; - // hash user password - const hashedPW = new jsSHA('SHA-512', 'TEXT').update(password).getHash('HEX'); - const getUserSQLStmt = 'SELECT name, pw FROM users WHERE name = ?'; const row = db.prepare(getUserSQLStmt).get(user); + + if (!row) { + return null; + } const storedPW = row.pw; - if (hashedPW && hashedPW === storedPW) { + const isValid = bcrypt.compareSync(password, storedPW) + if (isValid) { JWTToken = createToken({ id: user, admin: true }); } diff --git a/src/lib/server/userService.ts b/src/lib/server/userService.ts new file mode 100644 index 0000000..a8fb0b7 --- /dev/null +++ b/src/lib/server/userService.ts @@ -0,0 +1,51 @@ +import { db } from '$lib/server/dbService'; + +export const getUsers = (): { userId: string; userName: string }[] => { + const getUsersSQLStmt = `SELECT id, name + FROM users;`; + const statement = db.prepare(getUsersSQLStmt); + const result = statement.all() as { id: string; name: string }[]; + const userList: { userId: string; userName: string }[] = []; + + for (const resultItem of result) { + const user = { userId: resultItem.id, userName: resultItem.name }; + userList.push(user); + } + + return userList; +}; + +export const addUser = (userName: string, userPassword: string) => { + const addUserSQLStmt = `INSERT into users(name, pw) + values (?, ?)`; + const statement = db.prepare(addUserSQLStmt); + + let rowInfo; + try { + rowInfo = statement.run(userName, userPassword); + return rowInfo; + } catch (error) { + console.error('ERROR: ', error); + } +}; + +export const deleteUser = (userId: string) => { + // make sure to not delete the last entry + const deleteUserSQLStmt = `DELETE + FROM users + WHERE id = ? + AND (SELECT COUNT(*) FROM users) > 1;`; + + const statement = db.prepare(deleteUserSQLStmt); + + let rowCount; + try { + const info = statement.run(userId); + rowCount = info.changes; + } catch (error) { + console.log(error); + rowCount = 0; + } + + return rowCount; +}; diff --git a/src/routes/(angemeldet)/+page.svelte b/src/routes/(angemeldet)/+page.svelte index 6287a40..16f5d0a 100644 --- a/src/routes/(angemeldet)/+page.svelte +++ b/src/routes/(angemeldet)/+page.svelte @@ -42,6 +42,18 @@

Fügen Sie einem Tatort Bilder hinzu.

{/if} +
+
+ +
+ + Benutzerverwaltung + + +

Füge neue Benutzer hinzu oder entferne welche.

+
diff --git a/src/routes/(angemeldet)/user-management/+page.svelte b/src/routes/(angemeldet)/user-management/+page.svelte new file mode 100644 index 0000000..d0d1874 --- /dev/null +++ b/src/routes/(angemeldet)/user-management/+page.svelte @@ -0,0 +1,212 @@ + + +

+ Benutzerverwaltung +

+ +

+ Benutzerliste +

+ +
+ + + + + + + + + {#each userList as userItem} + + + + + {/each} + +
BenutzernameEntfernen
{userItem.userName} + +
+
+ +

+ Neuer Nutzer +

+ +
+
+
+ + +
+ +
+ + +
+
+
+ +
+ {#if addUserError} + + {/if} + {#if addUserSuccess} + + {/if} + + +
+ + \ No newline at end of file diff --git a/src/routes/api/users/+server.ts b/src/routes/api/users/+server.ts new file mode 100644 index 0000000..7bf820c --- /dev/null +++ b/src/routes/api/users/+server.ts @@ -0,0 +1,38 @@ +import { json } from '@sveltejs/kit'; +import { addUser, getUsers } from '$lib/server/userService'; +import bcrypt from 'bcrypt'; + +const saltRounds = 12; + +export function GET({ locals }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + const userList = getUsers(); + + return new Response(JSON.stringify(userList)); +} + +export async function POST({ request, locals }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + const data = await request.json(); + const userName = data.userName; + const userPassword = data.userPassword; + + if (!userName || !userPassword) { + return json({ error: 'Missing input' }, { status: 400 }); + } + + const hashedPassword = bcrypt.hashSync(userPassword, saltRounds); + const rowInfo = addUser(userName, hashedPassword); + + if (rowInfo?.changes == 1) { + return json({ userId: rowInfo.lastInsertRowid, userName: userName }, { status: 201 }); + } else { + return new Response(null, { status: 400 }); + } +} diff --git a/src/routes/api/users/[user]/+server.ts b/src/routes/api/users/[user]/+server.ts new file mode 100644 index 0000000..704ae19 --- /dev/null +++ b/src/routes/api/users/[user]/+server.ts @@ -0,0 +1,13 @@ +import { json } from '@sveltejs/kit'; +import { deleteUser } from '$lib/server/userService'; + +export async function DELETE({ params, locals }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + const userId = params.user; + const rowCount = deleteUser(userId); + + return new Response(null, { status: rowCount == 1 ? 204 : 400 }); +}