added most features required + better auth
This commit is contained in:
parent
bf114411ff
commit
c753171846
7
src/app.d.ts
vendored
7
src/app.d.ts
vendored
@ -1,9 +1,14 @@
|
|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
|
||||||
|
import type { Guard } from "$lib/server/guard";
|
||||||
|
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Locals {}
|
interface Locals {
|
||||||
|
guard: Guard;
|
||||||
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import type { ServerInit } from "@sveltejs/kit";
|
|||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { writeFileSync } from "fs";
|
import { writeFileSync } from "fs";
|
||||||
|
import { Guard } from "$lib/server/guard";
|
||||||
|
import { getUserFromSession } from "$lib/server/sessions";
|
||||||
|
|
||||||
export const init: ServerInit = async () => {
|
export const init: ServerInit = async () => {
|
||||||
const anyUser = db.data.users.at(0);
|
const anyUser = db.data.users.at(0);
|
||||||
@ -27,3 +29,11 @@ export const init: ServerInit = async () => {
|
|||||||
writeFileSync("./data/default_admin_pass.txt", pass);
|
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);
|
||||||
|
};
|
||||||
@ -6,6 +6,6 @@ export type User = {
|
|||||||
name: string,
|
name: string,
|
||||||
password: string,
|
password: string,
|
||||||
admin: boolean
|
admin: boolean
|
||||||
groups: Group[],
|
groups: string[],
|
||||||
permissions: { [key: string]: Permission }
|
permissions: { [key: string]: Permission }
|
||||||
}
|
}
|
||||||
51
src/lib/server/guard.ts
Normal file
51
src/lib/server/guard.ts
Normal file
@ -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!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,7 @@
|
|||||||
import { getUserFromSession } from "$lib/server/sessions";
|
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
import type { LayoutServerLoad } from "./$types";
|
import type { LayoutServerLoad } from "./$types";
|
||||||
|
|
||||||
export const load: LayoutServerLoad = async ({ cookies }) => {
|
export const load: LayoutServerLoad = async ({ locals: { guard } }) => {
|
||||||
const user = await getUserFromSession(cookies.get("session"));
|
const user = guard.requiresAuth().orRedirects().getUser();
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
redirect(302, "/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p>logged in as {data.user.name}</p>
|
<p>logged in as {data.user.name}</p>
|
||||||
<a href="/dashboard">home</a>
|
<a href="/dashboard/devices">devices</a>
|
||||||
{#if data.user.admin}
|
{#if data.user.admin}
|
||||||
<a href="/dashboard/users">users</a>
|
<a href="/dashboard/users">users</a>
|
||||||
<a href="/dashboard/groups">groups</a>
|
<a href="/dashboard/groups">groups</a>
|
||||||
|
|||||||
@ -1,15 +1,6 @@
|
|||||||
import { db } from "$lib/server/db/db";
|
import { redirect, type ServerLoad } from "@sveltejs/kit";
|
||||||
import type { User } from "$lib/server/db/types/user";
|
|
||||||
import type { ServerLoad } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ parent }) => {
|
export const load: ServerLoad = async ({ locals: { guard }}) => {
|
||||||
const { user } = await parent() as { user: User };
|
guard.requiresAuth().orRedirects();
|
||||||
|
redirect(302, '/dashboard/devices');
|
||||||
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))
|
|
||||||
),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
let { data } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
aaaa
|
|
||||||
17
src/routes/dashboard/devices/+page.server.ts
Normal file
17
src/routes/dashboard/devices/+page.server.ts
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
18
src/routes/dashboard/devices/+page.svelte
Normal file
18
src/routes/dashboard/devices/+page.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if data.user.admin}
|
||||||
|
<a href="/dashboard/devices/new">new device</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if data.devices.length === 0}
|
||||||
|
<p>No devices found</p>
|
||||||
|
{:else}
|
||||||
|
<p>Devices:</p>
|
||||||
|
<ul>
|
||||||
|
{#each data.devices as device}
|
||||||
|
<li>{device.name} {#if data.user.admin}- <a href="/dashboard/devices/{device.id}">edit</a>{/if}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
86
src/routes/dashboard/devices/[slug]/+page.server.ts
Normal file
86
src/routes/dashboard/devices/[slug]/+page.server.ts
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/routes/dashboard/devices/[slug]/+page.svelte
Normal file
35
src/routes/dashboard/devices/[slug]/+page.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { form, data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form method="POST" action="?/update">
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input name="name" type="text" defaultValue={data.device ? data.device.name : "New Device"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Mac Address
|
||||||
|
<input name="mac" type="text" defaultValue={data.device ? data.device.mac : "00:00:00:00:00:00"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Broadcast Ip Address
|
||||||
|
<input name="ip" type="text" defaultValue={data.device ? data.device.ip : "255.255.255.255"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Port
|
||||||
|
<input name="port" type="text" defaultValue={data.device ? data.device.port : "9"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Amount of packets
|
||||||
|
<input name="packets" type="text" defaultValue={data.device ? data.device.packets : "3"}>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button>{data.device ? "Update" : "Create"}</button>
|
||||||
|
{#if data.device}
|
||||||
|
<button formaction="?/delete">Delete</button>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if form?.error}
|
||||||
|
<p>Could not create device: {form.error}</p>
|
||||||
|
{/if}
|
||||||
10
src/routes/dashboard/groups/+page.server.ts
Normal file
10
src/routes/dashboard/groups/+page.server.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
};
|
||||||
16
src/routes/dashboard/groups/+page.svelte
Normal file
16
src/routes/dashboard/groups/+page.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a href="/dashboard/groups/new">new group</a>
|
||||||
|
|
||||||
|
{#if data.groups.length === 0}
|
||||||
|
<p>No groups found</p>
|
||||||
|
{:else}
|
||||||
|
<p>Groups:</p>
|
||||||
|
<ul>
|
||||||
|
{#each data.groups as group}
|
||||||
|
<li>{group.name} - <a href="/dashboard/groups/{group.id}">edit</a></li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
92
src/routes/dashboard/groups/[slug]/+page.server.ts
Normal file
92
src/routes/dashboard/groups/[slug]/+page.server.ts
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
34
src/routes/dashboard/groups/[slug]/+page.svelte
Normal file
34
src/routes/dashboard/groups/[slug]/+page.svelte
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { form, data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form method="POST" action="?/update">
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input name="name" type="text" defaultValue={data.group ? data.group.name : "New Group"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Can see
|
||||||
|
<select multiple name="canSee">
|
||||||
|
{#each data.devices as device}
|
||||||
|
<option value={device.id} selected={data.group?.permissions[device.id] ? true : false}>{device.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Can wake
|
||||||
|
<select multiple name="canWake">
|
||||||
|
{#each data.devices as device}
|
||||||
|
<option value={device.id} selected={data.group?.permissions[device.id]?.wake ? true : false}>{device.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button>{data.group ? "Update" : "Create"}</button>
|
||||||
|
{#if data.group}
|
||||||
|
<button formaction="?/delete">Delete</button>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if form?.error}
|
||||||
|
<p>Could not {data.group ? "update" : "create"} group: {form.error}</p>
|
||||||
|
{/if}
|
||||||
10
src/routes/dashboard/users/+page.server.ts
Normal file
10
src/routes/dashboard/users/+page.server.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
};
|
||||||
16
src/routes/dashboard/users/+page.svelte
Normal file
16
src/routes/dashboard/users/+page.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a href="/dashboard/users/new">new user</a>
|
||||||
|
|
||||||
|
{#if data.users.length === 0}
|
||||||
|
<p>No users found</p>
|
||||||
|
{:else}
|
||||||
|
<p>Users:</p>
|
||||||
|
<ul>
|
||||||
|
{#each data.users as user}
|
||||||
|
<li>{user.name} - <a href="/dashboard/users/{user.id}">edit</a></li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
90
src/routes/dashboard/users/[slug]/+page.server.ts
Normal file
90
src/routes/dashboard/users/[slug]/+page.server.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
50
src/routes/dashboard/users/[slug]/+page.svelte
Normal file
50
src/routes/dashboard/users/[slug]/+page.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { data, form } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form method="POST" action="?/update">
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input name="name" type="text" defaultValue={data.user ? data.user.name : "New User"}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password (leave empty for no change)
|
||||||
|
<input name="password" type="password">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Is admin
|
||||||
|
<input name="admin" type="checkbox" checked={data.user ? data.user.admin : false}>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Groups
|
||||||
|
<select multiple name="groups">
|
||||||
|
{#each data.groups as group}
|
||||||
|
<option value={group.id} selected={data.user?.groups.includes(group.id) ? true : false}>{group.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Can see
|
||||||
|
<select multiple name="canSee">
|
||||||
|
{#each data.devices as device}
|
||||||
|
<option value={device.id} selected={data.user?.permissions[device.id] ? true : false}>{device.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Can wake
|
||||||
|
<select multiple name="canWake">
|
||||||
|
{#each data.devices as device}
|
||||||
|
<option value={device.id} selected={data.user?.permissions[device.id]?.wake ? true : false}>{device.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button>{data.user ? "Update" : "Create"}</button>
|
||||||
|
{#if data.user}
|
||||||
|
<button formaction="?/delete">Delete</button>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if form?.error}
|
||||||
|
<p>Could not update user: {form.error}</p>
|
||||||
|
{/if}
|
||||||
Loading…
x
Reference in New Issue
Block a user