diff --git a/src/app.d.ts b/src/app.d.ts
index da08e6d..3c856c0 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -1,9 +1,14 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
+
+import type { Guard } from "$lib/server/guard";
+
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
- // interface Locals {}
+ interface Locals {
+ guard: Guard;
+ }
// interface PageData {}
// interface PageState {}
// interface Platform {}
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
index 7aef4d9..2dd97ee 100644
--- a/src/hooks.server.ts
+++ b/src/hooks.server.ts
@@ -3,6 +3,8 @@ import type { ServerInit } from "@sveltejs/kit";
import bcrypt from "bcryptjs";
import { nanoid } from "nanoid";
import { writeFileSync } from "fs";
+import { Guard } from "$lib/server/guard";
+import { getUserFromSession } from "$lib/server/sessions";
export const init: ServerInit = async () => {
const anyUser = db.data.users.at(0);
@@ -26,4 +28,12 @@ export const init: ServerInit = async () => {
writeFileSync("./data/default_admin_pass.txt", pass);
}
+};
+
+export async function handle({ event, resolve }) {
+ const { cookies, locals } = event;
+
+ locals.guard = new Guard(await getUserFromSession(cookies.get("session")));
+
+ return await resolve(event);
};
\ No newline at end of file
diff --git a/src/lib/server/db/types/user.ts b/src/lib/server/db/types/user.ts
index 18ee6dd..e738a2d 100644
--- a/src/lib/server/db/types/user.ts
+++ b/src/lib/server/db/types/user.ts
@@ -6,6 +6,6 @@ export type User = {
name: string,
password: string,
admin: boolean
- groups: Group[],
+ groups: string[],
permissions: { [key: string]: Permission }
}
\ No newline at end of file
diff --git a/src/lib/server/guard.ts b/src/lib/server/guard.ts
new file mode 100644
index 0000000..44fe3ae
--- /dev/null
+++ b/src/lib/server/guard.ts
@@ -0,0 +1,51 @@
+import { fail, redirect } from "@sveltejs/kit";
+import type { User } from "./db/types/user";
+
+export class Guard {
+ private readonly user?: User;
+
+ private authRequired = false;
+ private adminRequired = false;
+
+ constructor(user?: User, options?: { authRequired?: boolean, adminRequired?: boolean }) {
+ this.user = user;
+ this.authRequired = options?.authRequired ?? false;
+ this.adminRequired = options?.adminRequired ?? false;
+ }
+
+ public requiresAuth() {
+ return new Guard(this.user, { authRequired: true });
+ }
+
+ public requiresAdmin() {
+ return new Guard(this.user, { authRequired: true, adminRequired: true });
+ }
+
+ public orRedirects() {
+ if (this.authRequired && !this.user) {
+ redirect(302, "/login");
+ }
+
+ if (this.adminRequired && !this.user?.admin) {
+ redirect(302, "/dashboard");
+ }
+
+ return this;
+ }
+
+ public isFailed() {
+ if (this.authRequired && !this.user) {
+ return true;
+ }
+
+ if (this.adminRequired && !this.user?.admin) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public getUser(): User {
+ return this.user!;
+ }
+}
\ No newline at end of file
diff --git a/src/routes/dashboard/+layout.server.ts b/src/routes/dashboard/+layout.server.ts
index 278c7e5..3446805 100644
--- a/src/routes/dashboard/+layout.server.ts
+++ b/src/routes/dashboard/+layout.server.ts
@@ -1,13 +1,7 @@
-import { getUserFromSession } from "$lib/server/sessions";
-import { redirect } from "@sveltejs/kit";
import type { LayoutServerLoad } from "./$types";
-export const load: LayoutServerLoad = async ({ cookies }) => {
- const user = await getUserFromSession(cookies.get("session"));
-
- if (!user) {
- redirect(302, "/login");
- }
+export const load: LayoutServerLoad = async ({ locals: { guard } }) => {
+ const user = guard.requiresAuth().orRedirects().getUser();
return {
user,
diff --git a/src/routes/dashboard/+layout.svelte b/src/routes/dashboard/+layout.svelte
index 6af5864..adddfdb 100644
--- a/src/routes/dashboard/+layout.svelte
+++ b/src/routes/dashboard/+layout.svelte
@@ -3,7 +3,7 @@
logged in as {data.user.name}
-home
+devices
{#if data.user.admin}
users
groups
diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts
index 4c12f78..328f836 100644
--- a/src/routes/dashboard/+page.server.ts
+++ b/src/routes/dashboard/+page.server.ts
@@ -1,15 +1,6 @@
-import { db } from "$lib/server/db/db";
-import type { User } from "$lib/server/db/types/user";
-import type { ServerLoad } from "@sveltejs/kit";
+import { redirect, type ServerLoad } from "@sveltejs/kit";
-export const load: ServerLoad = async ({ parent }) => {
- const { user } = await parent() as { user: User };
-
- return {
- devices: user.admin ? db.data.devices :
- db.data.devices.filter(device =>
- Object.keys(user.permissions).includes(device.id) ||
- user.groups.some(group => Object.keys(group.permissions).includes(device.id))
- ),
- }
+export const load: ServerLoad = async ({ locals: { guard }}) => {
+ guard.requiresAuth().orRedirects();
+ redirect(302, '/dashboard/devices');
};
\ No newline at end of file
diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte
deleted file mode 100644
index bbf4a0b..0000000
--- a/src/routes/dashboard/+page.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-aaaa
\ No newline at end of file
diff --git a/src/routes/dashboard/devices/+page.server.ts b/src/routes/dashboard/devices/+page.server.ts
new file mode 100644
index 0000000..477dd06
--- /dev/null
+++ b/src/routes/dashboard/devices/+page.server.ts
@@ -0,0 +1,17 @@
+import { db } from "$lib/server/db/db";
+import type { ServerLoad } from "@sveltejs/kit";
+
+export const load: ServerLoad = async ({ locals: { guard} }) => {
+ const user = guard.requiresAuth().orRedirects().getUser();
+
+ return {
+ devices: user.admin ? db.data.devices :
+ db.data.devices.filter(device =>
+ Object.keys(user.permissions).includes(device.id) ||
+ user.groups.some(groupId => {
+ const group = db.data.groups.find(group => group.id === groupId);
+ return group && Object.keys(group.permissions).includes(device.id)
+ })
+ ),
+ }
+};
\ No newline at end of file
diff --git a/src/routes/dashboard/devices/+page.svelte b/src/routes/dashboard/devices/+page.svelte
new file mode 100644
index 0000000..6637499
--- /dev/null
+++ b/src/routes/dashboard/devices/+page.svelte
@@ -0,0 +1,18 @@
+
+
+{#if data.user.admin}
+ new device
+{/if}
+
+{#if data.devices.length === 0}
+ No devices found
+{:else}
+ Devices:
+
+ {#each data.devices as device}
+ - {device.name} {#if data.user.admin}- edit{/if}
+ {/each}
+
+{/if}
\ No newline at end of file
diff --git a/src/routes/dashboard/devices/[slug]/+page.server.ts b/src/routes/dashboard/devices/[slug]/+page.server.ts
new file mode 100644
index 0000000..2fdf03d
--- /dev/null
+++ b/src/routes/dashboard/devices/[slug]/+page.server.ts
@@ -0,0 +1,86 @@
+import { db } from '$lib/server/db/db';
+import { getUserFromSession } from '$lib/server/sessions';
+import { fail, redirect, type ServerLoad } from '@sveltejs/kit';
+import { nanoid } from 'nanoid';
+
+export const load: ServerLoad = async ({ locals: { guard }, params }) => {
+ guard.requiresAdmin().orRedirects();
+
+ const device = db.data.devices.find(device => device.id === params.slug);
+
+ if (!device && params.slug !== "new") {
+ redirect(302, "/dashboard/devices");
+ }
+
+ return {
+ device,
+ }
+}
+
+export const actions = {
+ update: async ({ request, cookies, params, locals: { guard } }) => {
+ if (guard.requiresAdmin().isFailed()) {
+ return fail(403);
+ }
+
+ const form = await request.formData();
+ const name = form.get("name")?.toString();
+ const mac = form.get("mac")?.toString();
+ const ip = form.get("ip")?.toString();
+ const port = form.get("port")?.toString();
+ const packets = form.get("packets")?.toString();
+
+ if (!name || !mac || !ip || !port || !packets) {
+ // TODO better validation
+ return {
+ error: "MISSING_FIELDS"
+ }
+ }
+
+ if (params.slug === "new") {
+ await db.update(({ devices }) => {
+ devices.push({
+ id: nanoid(),
+ name,
+ mac,
+ ip,
+ port: parseInt(port),
+ packets: parseInt(packets)
+ });
+ });
+ } else {
+ let device = db.data.devices.find(device => device.id === params.slug);
+
+ if (!device) {
+ return;
+ }
+
+ device.name = name;
+ device.mac = mac;
+ device.ip = ip;
+ device.port = parseInt(port);
+ device.packets = parseInt(packets);
+
+ await db.write();
+ }
+
+ redirect(302, "/dashboard/devices");
+ },
+ delete: async ({ locals: { guard }, params }) => {
+ if (guard.requiresAdmin().isFailed()) {
+ return fail(403);
+ }
+
+ db.data.devices = db.data.devices.filter(device => device.id !== params.slug);
+ db.data.users.forEach(user => {
+ delete user.permissions[params.slug];
+ });
+ db.data.groups.forEach(group => {
+ delete group.permissions[params.slug];
+ });
+
+ await db.write();
+
+ redirect(302, "/dashboard/devices");
+ }
+}
\ No newline at end of file
diff --git a/src/routes/dashboard/devices/[slug]/+page.svelte b/src/routes/dashboard/devices/[slug]/+page.svelte
new file mode 100644
index 0000000..c7ea642
--- /dev/null
+++ b/src/routes/dashboard/devices/[slug]/+page.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+{#if form?.error}
+ Could not create device: {form.error}
+{/if}
\ No newline at end of file
diff --git a/src/routes/dashboard/groups/+page.server.ts b/src/routes/dashboard/groups/+page.server.ts
new file mode 100644
index 0000000..ff3813e
--- /dev/null
+++ b/src/routes/dashboard/groups/+page.server.ts
@@ -0,0 +1,10 @@
+import { db } from "$lib/server/db/db";
+import { type ServerLoad } from "@sveltejs/kit";
+
+export const load: ServerLoad = async ({ locals: { guard } }) => {
+ guard.requiresAdmin().orRedirects();
+
+ return {
+ groups: db.data.groups,
+ }
+};
\ No newline at end of file
diff --git a/src/routes/dashboard/groups/+page.svelte b/src/routes/dashboard/groups/+page.svelte
new file mode 100644
index 0000000..7ad42ae
--- /dev/null
+++ b/src/routes/dashboard/groups/+page.svelte
@@ -0,0 +1,16 @@
+
+
+new group
+
+{#if data.groups.length === 0}
+ No groups found
+{:else}
+ Groups:
+
+ {#each data.groups as group}
+ - {group.name} - edit
+ {/each}
+
+{/if}
\ No newline at end of file
diff --git a/src/routes/dashboard/groups/[slug]/+page.server.ts b/src/routes/dashboard/groups/[slug]/+page.server.ts
new file mode 100644
index 0000000..6b0fe4d
--- /dev/null
+++ b/src/routes/dashboard/groups/[slug]/+page.server.ts
@@ -0,0 +1,92 @@
+import { db } from "$lib/server/db/db";
+import type { Permission } from "$lib/server/db/types/permission";
+import type { User } from "$lib/server/db/types/user";
+import { getUserFromSession } from "$lib/server/sessions";
+import { fail, redirect, type Actions, type ServerLoad } from "@sveltejs/kit";
+import { nanoid } from "nanoid";
+
+export const load: ServerLoad = async ({ locals: { guard },params }) => {
+ guard.requiresAdmin().orRedirects();
+
+ const group = db.data.groups.find(group => group.id === params.slug);
+
+ if (!group && params.slug !== "new") {
+ redirect(302, "/dashboard/groups");
+ }
+
+ return {
+ devices: db.data.devices,
+ group,
+ }
+};
+
+export const actions: Actions = {
+ update: async ({ request, locals: { guard }, params }) => {
+ if (guard.requiresAdmin().isFailed()) {
+ return fail(403);
+ }
+
+ const form = await request.formData();
+ const name = form.get("name")?.toString();
+
+ let permissions: { [key: string]: Permission } = {};
+
+ for (let deviceId of form.getAll("canSee")) {
+ if (db.data.devices.find(device => device.id === deviceId)) {
+ permissions[deviceId.toString()] = { wake: false };
+ }
+ }
+
+ for (let deviceId of form.getAll("canWake")) {
+ if (db.data.devices.find(device => device.id === deviceId)) {
+ permissions[deviceId.toString()] = { wake: true };
+ }
+ }
+
+ if (!name) {
+ // TODO better validation
+ return {
+ error: "MISSING_FIELDS"
+ }
+ }
+
+ if (params.slug === "new") {
+ await db.update(({ groups }) => {
+ groups.push({
+ id: nanoid(),
+ name,
+ permissions,
+ });
+ });
+ } else {
+ await db.update(({ groups }) => {
+ const group = groups.find(group => group.id === params.slug);
+
+ if (!group) {
+ return;
+ }
+
+ group.name = name;
+ group.permissions = permissions;
+ });
+ }
+
+ redirect(302, "/dashboard/groups");
+ },
+
+ delete: async ({ locals: { guard }, params }) => {
+ if (guard.requiresAdmin().isFailed()) {
+ return fail(403);
+ }
+
+ db.data.groups = db.data.groups.filter(group => group.id !== params.slug);
+ db.data.users = db.data.users.map(user => {
+ user.groups = user.groups.filter(groupId => groupId !== params.slug);
+ return user;
+ });
+
+ await db.write();
+
+ redirect(302, "/dashboard/groups");
+ }
+};
\ No newline at end of file
diff --git a/src/routes/dashboard/groups/[slug]/+page.svelte b/src/routes/dashboard/groups/[slug]/+page.svelte
new file mode 100644
index 0000000..d4b2eef
--- /dev/null
+++ b/src/routes/dashboard/groups/[slug]/+page.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+{#if form?.error}
+ Could not {data.group ? "update" : "create"} group: {form.error}
+{/if}
\ No newline at end of file
diff --git a/src/routes/dashboard/users/+page.server.ts b/src/routes/dashboard/users/+page.server.ts
new file mode 100644
index 0000000..f8b5abb
--- /dev/null
+++ b/src/routes/dashboard/users/+page.server.ts
@@ -0,0 +1,10 @@
+import { db } from "$lib/server/db/db";
+import type { ServerLoad } from "@sveltejs/kit";
+
+export const load: ServerLoad = async ({ locals: { guard } }) => {
+ guard.requiresAdmin().orRedirects();
+
+ return {
+ users: db.data.users,
+ }
+};
\ No newline at end of file
diff --git a/src/routes/dashboard/users/+page.svelte b/src/routes/dashboard/users/+page.svelte
new file mode 100644
index 0000000..ea48b7f
--- /dev/null
+++ b/src/routes/dashboard/users/+page.svelte
@@ -0,0 +1,16 @@
+
+
+new user
+
+{#if data.users.length === 0}
+ No users found
+{:else}
+ Users:
+
+ {#each data.users as user}
+ - {user.name} - edit
+ {/each}
+
+{/if}
\ No newline at end of file
diff --git a/src/routes/dashboard/users/[slug]/+page.server.ts b/src/routes/dashboard/users/[slug]/+page.server.ts
new file mode 100644
index 0000000..196415e
--- /dev/null
+++ b/src/routes/dashboard/users/[slug]/+page.server.ts
@@ -0,0 +1,90 @@
+import { db } from "$lib/server/db/db";
+import type { Permission } from "$lib/server/db/types/permission";
+import { fail, redirect, type Actions } from "@sveltejs/kit";
+import bcrypt from "bcryptjs";
+import { nanoid } from "nanoid";
+
+export const load = async ({ locals: { guard }, params }) => {
+ guard.requiresAdmin().orRedirects();
+
+ const user = db.data.users.find(user => user.id === params.slug);
+
+ if (!user && params.slug !== "new") {
+ redirect(302, "/dashboard/users");
+ }
+
+ return {
+ user,
+ groups: db.data.groups,
+ devices: db.data.devices,
+ }
+};
+
+export const actions: Actions = {
+ update: async ({ request, locals: { guard }, params }) => {
+ if (guard.requiresAdmin().isFailed()) {
+ return fail(403);
+ }
+
+ const form = await request.formData();
+ const name = form.get("name")?.toString();
+ const admin = form.get("admin")?.toString() === "on";
+ const password = form.get("password")?.toString() ?? "";
+ const groups = form.getAll("groups")
+ .map(groupId => groupId.toString())
+ .filter(groupId => db.data.groups.find(group => group.id === groupId));
+
+ let permissions: { [key: string]: Permission } = {};
+
+ for (let deviceId of form.getAll("canSee")) {
+ if (db.data.devices.find(device => device.id === deviceId)) {
+ permissions[deviceId.toString()] = { wake: false };
+ }
+ }
+
+ for (let deviceId of form.getAll("canWake")) {
+ if (db.data.devices.find(device => device.id === deviceId)) {
+ permissions[deviceId.toString()] = { wake: true };
+ }
+ }
+
+ if (!name) {
+ // TODO better validation
+ return {
+ error: "MISSING_FIELDS"
+ }
+ }
+
+ if (params.slug === "new") {
+ if (password.length < 8) {
+ return {
+ error: "PASSWORD_TOO_WEAK"
+ }
+ }
+
+ await db.update(({ users }) => {
+ users.push({
+ id: nanoid(),
+ name,
+ password: bcrypt.hashSync(password, 10),
+ admin,
+ groups,
+ permissions
+ });
+ });
+ } else {
+ await db.update(({ users }) => {
+ const user = users.find(user => user.id === params.slug);
+ if (user) {
+ user.name = name;
+ user.admin = admin;
+ if (password !== "") {
+ user.password = bcrypt.hashSync(password, 10);
+ }
+ user.groups = groups;
+ user.permissions = permissions;
+ }
+ });
+ }
+ }
+};
\ No newline at end of file
diff --git a/src/routes/dashboard/users/[slug]/+page.svelte b/src/routes/dashboard/users/[slug]/+page.svelte
new file mode 100644
index 0000000..2da0a2c
--- /dev/null
+++ b/src/routes/dashboard/users/[slug]/+page.svelte
@@ -0,0 +1,50 @@
+
+
+
+
+{#if form?.error}
+ Could not update user: {form.error}
+{/if}
\ No newline at end of file