add admin admin settings
This commit is contained in:
parent
4b84c475b8
commit
0958ff21b6
@ -1,7 +1,7 @@
|
|||||||
import { sequelize } from '$lib/server/database';
|
import { sequelize } from '$lib/server/database';
|
||||||
import type { Handle } from '@sveltejs/kit';
|
import type { Handle } from '@sveltejs/kit';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { hasSession } from '$lib/server/session';
|
import { getSession } from '$lib/server/session';
|
||||||
|
|
||||||
// make sure that the database and tables exist
|
// make sure that the database and tables exist
|
||||||
await sequelize.sync();
|
await sequelize.sync();
|
||||||
@ -11,7 +11,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
event.url.pathname.startsWith(`${env.PUBLIC_BASE_PATH}/admin`) &&
|
event.url.pathname.startsWith(`${env.PUBLIC_BASE_PATH}/admin`) &&
|
||||||
event.url.pathname != `${env.PUBLIC_BASE_PATH}/admin/login`
|
event.url.pathname != `${env.PUBLIC_BASE_PATH}/admin/login`
|
||||||
) {
|
) {
|
||||||
if (!hasSession(event.cookies.get('session') || '')) {
|
if (getSession(event.cookies.get('session') || '') == null) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: {
|
headers: {
|
||||||
|
53
src/lib/components/Input/Badges.svelte
Normal file
53
src/lib/components/Input/Badges.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
type T = $$Generic;
|
||||||
|
|
||||||
|
export let id: string | null = null;
|
||||||
|
export let name: string | null = null;
|
||||||
|
export let disabled = true;
|
||||||
|
export let available: string[] | { [key: string]: T } = {};
|
||||||
|
export let value: T[] = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<select
|
||||||
|
{id}
|
||||||
|
{name}
|
||||||
|
class="select select-bordered select-xs"
|
||||||
|
disabled={disabled || available.length === 0}
|
||||||
|
on:change={(e) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
value.push(Object.values(available)[Object.keys(available).indexOf(e.target.value)]);
|
||||||
|
value = value;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
e.target.value = '-';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option selected hidden>-</option>
|
||||||
|
{#each Object.keys(available) as badge}
|
||||||
|
<option
|
||||||
|
hidden={value.find(
|
||||||
|
(v) => v === Object.values(available)[Object.keys(available).indexOf(badge)]
|
||||||
|
) !== undefined}>{badge}</option
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
<div class="flex flow flex-wrap gap-2">
|
||||||
|
{#each value as badge, i}
|
||||||
|
{#if Object.values(available).indexOf(badge) !== -1}
|
||||||
|
<div class="badge badge-outline gap-1">
|
||||||
|
<button
|
||||||
|
{disabled}
|
||||||
|
on:click={() => {
|
||||||
|
value.splice(i, 1);
|
||||||
|
value = value;
|
||||||
|
}}>✕</button
|
||||||
|
>
|
||||||
|
{Object.keys(available)[Object.values(available).indexOf(badge)]}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -3,10 +3,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IconSolid } from 'svelte-heros-v2';
|
import { IconSolid } from 'svelte-heros-v2';
|
||||||
|
|
||||||
export let id: string;
|
export let id: string | null = null;
|
||||||
export let name: string | null = null;
|
export let name: string | null = null;
|
||||||
export let type: string;
|
export let type: string;
|
||||||
export let value: string | null = null;
|
export let value: string | null = null;
|
||||||
|
export let placeholder: string | null = null;
|
||||||
export let required = false;
|
export let required = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@
|
|||||||
<!-- the cursor-not-allowed class must be set here because a disabled button does not respect the 'cursor' css property -->
|
<!-- the cursor-not-allowed class must be set here because a disabled button does not respect the 'cursor' css property -->
|
||||||
<div class={type === 'submit' && disabled ? 'cursor-not-allowed' : ''}>
|
<div class={type === 'submit' && disabled ? 'cursor-not-allowed' : ''}>
|
||||||
{#if type === 'submit'}
|
{#if type === 'submit'}
|
||||||
<input class="btn" {id} type="submit" {value} {disabled} bind:this={inputElement} />
|
<input class="btn" {id} type="submit" {disabled} bind:value bind:this={inputElement} />
|
||||||
{:else}
|
{:else}
|
||||||
<div>
|
<div>
|
||||||
{#if $$slots.label}
|
{#if $$slots.label}
|
||||||
@ -31,24 +32,29 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}>
|
<div class="relative flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}>
|
||||||
<input
|
<input
|
||||||
class:checkbox={type === 'checkbox'}
|
class:checkbox={type === 'checkbox'}
|
||||||
class:input,input-bordered={type !== 'checkbox'}
|
class:input,input-bordered,w-[100%]={type !== 'checkbox'}
|
||||||
class:w-[100%]={initialType !== 'password' && initialType !== 'checkbox'}
|
|
||||||
class:pr-11={initialType === 'password'}
|
class:pr-11={initialType === 'password'}
|
||||||
{id}
|
{id}
|
||||||
{name}
|
{name}
|
||||||
{type}
|
{type}
|
||||||
{value}
|
{value}
|
||||||
|
{placeholder}
|
||||||
{required}
|
{required}
|
||||||
{disabled}
|
{disabled}
|
||||||
autocomplete="off"
|
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
|
autocomplete="off"
|
||||||
|
on:input={(e) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
value = e.target?.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{#if initialType === 'password'}
|
{#if initialType === 'password'}
|
||||||
<button
|
<button
|
||||||
class="relative right-9"
|
class="absolute right-3"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
type = type === 'password' ? 'text' : 'password';
|
type = type === 'password' ? 'text' : 'password';
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
<div class="alert alert-error border-none relative text-gray-900 overflow-hidden">
|
<div class="alert alert-error border-none relative text-gray-900 overflow-hidden">
|
||||||
<div class="flex gap-2 z-10">
|
<div class="flex gap-2 z-10">
|
||||||
<IconOutline name="exclamation-circle-outline" />
|
<IconOutline name="exclamation-circle-outline" />
|
||||||
<span>Nutzername oder Passwort falsch</span>
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<progress
|
<progress
|
||||||
class="progress progress-error absolute bottom-0 h-[3px] w-full bg-[rgba(0,0,0,0.6)]"
|
class="progress progress-error absolute bottom-0 h-[3px] w-full bg-[rgba(0,0,0,0.6)]"
|
||||||
|
56
src/lib/permissions.ts
Normal file
56
src/lib/permissions.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
export class Permissions {
|
||||||
|
static readonly AdminRead = 2;
|
||||||
|
static readonly AdminWrite = 4;
|
||||||
|
static readonly UserRead = 8;
|
||||||
|
static readonly UserWrite = 16;
|
||||||
|
|
||||||
|
readonly value: number;
|
||||||
|
|
||||||
|
constructor(value: number | number[]) {
|
||||||
|
if (typeof value == 'number') {
|
||||||
|
this.value = value;
|
||||||
|
} else {
|
||||||
|
let finalValue = 0;
|
||||||
|
for (const v of Object.values(value)) {
|
||||||
|
finalValue |= v;
|
||||||
|
}
|
||||||
|
this.value = finalValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static allPermissions(): number[] {
|
||||||
|
return [
|
||||||
|
Permissions.AdminRead,
|
||||||
|
Permissions.AdminWrite,
|
||||||
|
Permissions.UserRead,
|
||||||
|
Permissions.UserWrite
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
adminRead(): boolean {
|
||||||
|
return (this.value & Permissions.AdminRead) != 0;
|
||||||
|
}
|
||||||
|
adminWrite(): boolean {
|
||||||
|
return (this.value & Permissions.AdminWrite) != 0;
|
||||||
|
}
|
||||||
|
userRead(): boolean {
|
||||||
|
return (this.value & Permissions.UserRead) != 0;
|
||||||
|
}
|
||||||
|
userWrite(): boolean {
|
||||||
|
return (this.value & Permissions.UserWrite) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
asArray(): number[] {
|
||||||
|
const array = [];
|
||||||
|
for (const perm of Permissions.allPermissions()) {
|
||||||
|
if ((this.value & perm) != 0) {
|
||||||
|
array.push(perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,16 @@ import { DataTypes } from 'sequelize';
|
|||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import { building, dev } from '$app/environment';
|
import { building, dev } from '$app/environment';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { BeforeCreate, BeforeUpdate, Column, Model, Sequelize, Table } from 'sequelize-typescript';
|
import {
|
||||||
|
BeforeCreate,
|
||||||
|
BeforeUpdate,
|
||||||
|
Column,
|
||||||
|
Model,
|
||||||
|
Sequelize,
|
||||||
|
Table,
|
||||||
|
Unique
|
||||||
|
} from 'sequelize-typescript';
|
||||||
|
import { Permissions } from '$lib/permissions';
|
||||||
|
|
||||||
@Table({ modelName: 'user' })
|
@Table({ modelName: 'user' })
|
||||||
export class User extends Model {
|
export class User extends Model {
|
||||||
@ -26,18 +35,28 @@ export class User extends Model {
|
|||||||
|
|
||||||
@Table({ modelName: 'admin' })
|
@Table({ modelName: 'admin' })
|
||||||
export class Admin extends Model {
|
export class Admin extends Model {
|
||||||
@Column({ type: DataTypes.STRING, allowNull: false })
|
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
|
||||||
declare username: string;
|
declare username: string;
|
||||||
@Column({ type: DataTypes.STRING, allowNull: false })
|
@Column({ type: DataTypes.STRING, allowNull: false })
|
||||||
declare password: string;
|
declare password: string;
|
||||||
@Column({ type: DataTypes.BIGINT, allowNull: false })
|
@Column({
|
||||||
declare permissions: number;
|
type: DataTypes.BIGINT,
|
||||||
|
allowNull: false,
|
||||||
|
get(this: Admin): Permissions | null {
|
||||||
|
const permissions = this.getDataValue('permissions');
|
||||||
|
return permissions != null ? new Permissions(permissions) : null;
|
||||||
|
},
|
||||||
|
set(this: Admin, value: Permissions) {
|
||||||
|
this.setDataValue('permissions', value.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
declare permissions: Permissions;
|
||||||
|
|
||||||
@BeforeCreate
|
@BeforeCreate
|
||||||
@BeforeUpdate
|
@BeforeUpdate
|
||||||
static hashPassword(instance: Admin) {
|
static hashPassword(instance: Admin) {
|
||||||
if (instance.password != null) {
|
if (instance.password != null) {
|
||||||
instance.username = bcrypt.hashSync(instance.password, 10);
|
instance.password = bcrypt.hashSync(instance.password, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,30 @@
|
|||||||
const sessions: string[] = [];
|
import type { Permissions } from '$lib/permissions';
|
||||||
|
import type { Cookies } from '@sveltejs/kit';
|
||||||
|
|
||||||
export function addSession(): string {
|
const sessions: Map<string, Permissions> = new Map();
|
||||||
|
|
||||||
|
export function addSession(permissions: Permissions): string {
|
||||||
const session = 'AAA';
|
const session = 'AAA';
|
||||||
sessions.push(session);
|
sessions.set(session, permissions);
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSession(session: string): boolean {
|
export function getSession(session: string | Cookies, permissions?: number[]): Permissions | null {
|
||||||
return sessions.find((v) => v == session) != undefined;
|
let sess: Permissions | null;
|
||||||
|
if (typeof session == 'string') {
|
||||||
|
sess = sessions.get(session) || null;
|
||||||
|
} else {
|
||||||
|
const sessionId = session.get('session');
|
||||||
|
sess = sessionId ? sessions.get(sessionId) || null : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sess) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (const perm of permissions || []) {
|
||||||
|
if ((sess.value & perm) == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sess;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,27 @@
|
|||||||
<div class="h-full">
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
import { IconOutline } from 'svelte-heros-v2';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`}
|
||||||
|
<div class="flex h-screen">
|
||||||
|
<div class="h-full">
|
||||||
|
<ul class="menu p-4 w-fit h-full bg-base-200 text-base-content">
|
||||||
|
<li>
|
||||||
|
<a href="{env.PUBLIC_BASE_PATH}/admin/admin">
|
||||||
|
<IconOutline name="users-outline" />
|
||||||
|
<span class="ml-1">Website Admins</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="h-full w-full">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="h-full w-full">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
0
src/routes/admin/+page.svelte
Normal file
0
src/routes/admin/+page.svelte
Normal file
3
src/routes/admin/admin/+layout.svelte
Normal file
3
src/routes/admin/admin/+layout.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="flex justify-center items-center w-full">
|
||||||
|
<slot />
|
||||||
|
</div>
|
11
src/routes/admin/admin/+page.server.ts
Normal file
11
src/routes/admin/admin/+page.server.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { Admin } from '$lib/server/database';
|
||||||
|
import { getSession } from '$lib/server/session';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ cookies }) => {
|
||||||
|
const admins = await Admin.findAll({ attributes: { exclude: ['password'] } });
|
||||||
|
return {
|
||||||
|
admins: JSON.parse(JSON.stringify(admins)),
|
||||||
|
permissions: getSession(cookies.get('session') || '')!.value
|
||||||
|
};
|
||||||
|
};
|
222
src/routes/admin/admin/+page.svelte
Normal file
222
src/routes/admin/admin/+page.svelte
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
import Badges from '$lib/components/Input/Badges.svelte';
|
||||||
|
import { IconOutline } from 'svelte-heros-v2';
|
||||||
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
|
import { Permissions } from '$lib/permissions';
|
||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
||||||
|
|
||||||
|
let allPermissionBadges = {
|
||||||
|
'Admin Read': Permissions.AdminRead,
|
||||||
|
'Admin Write': Permissions.AdminWrite,
|
||||||
|
'User Read': Permissions.UserRead,
|
||||||
|
'User Write': Permissions.UserWrite
|
||||||
|
};
|
||||||
|
|
||||||
|
let newAdminUsername: string;
|
||||||
|
let newAdminPassword: string;
|
||||||
|
let newAdminPermissions: number[];
|
||||||
|
|
||||||
|
async function buttonTriggeredRequest<T>(e: MouseEvent, promise: Promise<T>) {
|
||||||
|
(e.target as HTMLButtonElement).disabled = true;
|
||||||
|
await promise;
|
||||||
|
(e.target as HTMLButtonElement).disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAdmin(username: string, password: string, permissions: Permissions) {
|
||||||
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
permissions: permissions.value
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
data.admins.push(await response.json());
|
||||||
|
data.admins = data.admins;
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAdmin(
|
||||||
|
id: number,
|
||||||
|
username: string | null,
|
||||||
|
password: string | null,
|
||||||
|
permissions: Permissions | null
|
||||||
|
) {
|
||||||
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: id,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
permissions: permissions?.value
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAdmin(id: number) {
|
||||||
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
data.admins.splice(
|
||||||
|
data.admins.find((v: typeof data.admins) => v.id == id),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
data.admins = data.admins;
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorMessage = '';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
let permissions = new Permissions(data.permissions);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class="table table-zebra">
|
||||||
|
<colgroup>
|
||||||
|
<col span="1" style="width: 5%" />
|
||||||
|
<col span="1" style="width: 25%" />
|
||||||
|
<col span="1" style="width: 25%" />
|
||||||
|
<col span="1" style="width: 30%" />
|
||||||
|
<col span="1" style="width: 15%" />
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th />
|
||||||
|
<th>Benutzername</th>
|
||||||
|
<th>Passwort</th>
|
||||||
|
<th>Berechtigungen</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each data.admins as admin, i}
|
||||||
|
<tr>
|
||||||
|
<td>{i}</td>
|
||||||
|
<td
|
||||||
|
><Input
|
||||||
|
type="text"
|
||||||
|
value={admin.username}
|
||||||
|
disabled={!permissions.adminWrite() || !admin.edit}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Neues Passwort..."
|
||||||
|
disabled={!permissions.adminWrite() || !admin.edit}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><Badges
|
||||||
|
value={new Permissions(admin.permissions).asArray()}
|
||||||
|
available={allPermissionBadges}
|
||||||
|
disabled={!permissions.adminWrite() || !admin.edit}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<div>
|
||||||
|
{#if admin.edit}
|
||||||
|
<button
|
||||||
|
class="btn btn-square"
|
||||||
|
disabled={!permissions.adminWrite()}
|
||||||
|
on:click={async (e) => {
|
||||||
|
await buttonTriggeredRequest(
|
||||||
|
e,
|
||||||
|
updateAdmin(
|
||||||
|
admin.id,
|
||||||
|
admin.username,
|
||||||
|
admin.password,
|
||||||
|
new Permissions(admin.permissions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
admin.edit = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconOutline name="check-outline" width="24" height="24" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-square"
|
||||||
|
disabled={!permissions.adminWrite()}
|
||||||
|
on:click={() => {
|
||||||
|
admin.username = admin.before.username;
|
||||||
|
admin.permissions = admin.before.permissions;
|
||||||
|
admin.edit = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconOutline name="no-symbol-outline" width="24" height="24" />
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="btn btn-square"
|
||||||
|
disabled={!permissions.adminWrite()}
|
||||||
|
on:click={() => {
|
||||||
|
admin.edit = true;
|
||||||
|
admin.before = {
|
||||||
|
username: admin.username,
|
||||||
|
permissions: admin.permissions
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconOutline name="pencil-square-outline" width="24" height="24" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-square"
|
||||||
|
disabled={!permissions.adminWrite()}
|
||||||
|
on:click={(e) => buttonTriggeredRequest(e, deleteAdmin(admin.id))}
|
||||||
|
>
|
||||||
|
<IconOutline name="trash-outline" width="24" height="24" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
<tr>
|
||||||
|
<td>{data.admins.length}</td>
|
||||||
|
<td><Input type="text" bind:value={newAdminUsername} /></td>
|
||||||
|
<td><Input type="password" bind:value={newAdminPassword} /></td>
|
||||||
|
<td
|
||||||
|
><Badges
|
||||||
|
bind:value={newAdminPermissions}
|
||||||
|
available={allPermissionBadges}
|
||||||
|
disabled={!permissions.adminWrite()}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
class="btn btn-square"
|
||||||
|
disabled={!permissions.adminWrite() || !newAdminUsername || !newAdminPassword}
|
||||||
|
on:click={async (e) => {
|
||||||
|
await buttonTriggeredRequest(
|
||||||
|
e,
|
||||||
|
addAdmin(newAdminUsername, newAdminPassword, new Permissions(newAdminPermissions))
|
||||||
|
);
|
||||||
|
newAdminUsername = '';
|
||||||
|
newAdminPassword = '';
|
||||||
|
newAdminPermissions = [];
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconOutline name="user-plus-outline" width="24" height="24" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<ErrorToast show={errorMessage !== ''}>
|
||||||
|
<span />
|
||||||
|
</ErrorToast>
|
80
src/routes/admin/admin/+server.ts
Normal file
80
src/routes/admin/admin/+server.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { Permissions } from '$lib/permissions';
|
||||||
|
import { getSession } from '$lib/server/session';
|
||||||
|
import { Admin } from '$lib/server/database';
|
||||||
|
|
||||||
|
export const POST = (async ({ request, cookies }) => {
|
||||||
|
if (getSession(cookies, [Permissions.AdminWrite]) == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 401
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.json();
|
||||||
|
const username = data['username'] as string | null;
|
||||||
|
const password = data['password'] as string | null;
|
||||||
|
const permissions = data['permissions'] as number | null;
|
||||||
|
|
||||||
|
if (username == null || password == null || permissions == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 400
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = await Admin.create({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
permissions: new Permissions(permissions)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(admin), {
|
||||||
|
status: 201
|
||||||
|
});
|
||||||
|
}) satisfies RequestHandler;
|
||||||
|
|
||||||
|
export const PATCH = (async ({ request, cookies }) => {
|
||||||
|
if (getSession(cookies, [Permissions.AdminWrite]) == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 401
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.json();
|
||||||
|
const id = data['id'] as string | null;
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 400
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePayload: { [key: string]: any } = {};
|
||||||
|
if (data['username']) updatePayload.username = data['username'];
|
||||||
|
if (data['password']) updatePayload.password = data['password'];
|
||||||
|
if (data['permissions']) updatePayload.permissions = data['permissions'];
|
||||||
|
|
||||||
|
await Admin.update(updatePayload, { where: { id: id } });
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}) satisfies RequestHandler;
|
||||||
|
|
||||||
|
export const DELETE = (async ({ request, cookies }) => {
|
||||||
|
if (getSession(cookies, [Permissions.AdminWrite]) == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 401
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.json();
|
||||||
|
const id = data['id'] as string | null;
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 400
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Admin.destroy({ where: { id: id } });
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}) satisfies RequestHandler;
|
@ -83,4 +83,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ErrorToast timeout={2000} bind:show={showError} bind:this={errorToastElement} />
|
<ErrorToast timeout={2000} bind:show={showError} bind:this={errorToastElement}>
|
||||||
|
<span>Nutzername oder Passwort falsch</span>
|
||||||
|
</ErrorToast>
|
||||||
|
@ -3,6 +3,7 @@ import { Admin } from '$lib/server/database';
|
|||||||
import { env as publicEnv } from '$env/dynamic/public';
|
import { env as publicEnv } from '$env/dynamic/public';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import { addSession } from '$lib/server/session';
|
import { addSession } from '$lib/server/session';
|
||||||
|
import { Permissions } from '$lib/permissions';
|
||||||
|
|
||||||
export const POST = (async ({ request, cookies }) => {
|
export const POST = (async ({ request, cookies }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
@ -11,7 +12,7 @@ export const POST = (async ({ request, cookies }) => {
|
|||||||
|
|
||||||
if (username == null || password == null) {
|
if (username == null || password == null) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 403
|
status: 401
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ export const POST = (async ({ request, cookies }) => {
|
|||||||
username == env.ADMIN_USER &&
|
username == env.ADMIN_USER &&
|
||||||
password == env.ADMIN_PASSWORD
|
password == env.ADMIN_PASSWORD
|
||||||
) {
|
) {
|
||||||
cookies.set('session', addSession(), {
|
cookies.set('session', addSession(new Permissions(Permissions.allPermissions())), {
|
||||||
path: `${publicEnv.PUBLIC_BASE_PATH}/admin`,
|
path: `${publicEnv.PUBLIC_BASE_PATH}/admin`,
|
||||||
maxAge: 60 * 60 * 24 * 90,
|
maxAge: 60 * 60 * 24 * 90,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@ -32,7 +33,7 @@ export const POST = (async ({ request, cookies }) => {
|
|||||||
|
|
||||||
const user = await Admin.findOne({ where: { username: username } });
|
const user = await Admin.findOne({ where: { username: username } });
|
||||||
if (user && user.validatePassword(password)) {
|
if (user && user.validatePassword(password)) {
|
||||||
cookies.set('session', addSession(), {
|
cookies.set('session', addSession(user.permissions), {
|
||||||
path: `${publicEnv.PUBLIC_BASE_PATH}/admin`,
|
path: `${publicEnv.PUBLIC_BASE_PATH}/admin`,
|
||||||
maxAge: 60 * 60 * 24 * 90,
|
maxAge: 60 * 60 * 24 * 90,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@ -41,7 +42,7 @@ export const POST = (async ({ request, cookies }) => {
|
|||||||
return new Response();
|
return new Response();
|
||||||
} else {
|
} else {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 403
|
status: 401
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}) satisfies RequestHandler;
|
}) satisfies RequestHandler;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user