From 53c023ca4d3d7118242e7605e10229c64b45bcc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Thu, 26 Jun 2025 00:52:10 +0200 Subject: [PATCH] add membership and permissions --- src/api/apiService.ts | 2 +- src/api/authService.ts | 30 +++++++++--- src/app.d.ts | 4 +- src/models/user.ts | 17 +++++++ src/routes/+layout.server.ts | 7 +++ src/routes/+layout.svelte | 10 +++- src/routes/dashboard/+page.svelte | 14 ++++-- .../dashboard/membership/+page.server.ts | 18 ++++++++ src/routes/dashboard/membership/+page.svelte | 46 +++++++++++++++++++ src/stores/user.ts | 15 ++++++ vite.config.ts | 10 +++- 11 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 src/models/user.ts create mode 100644 src/routes/+layout.server.ts create mode 100644 src/routes/dashboard/membership/+page.server.ts create mode 100644 src/routes/dashboard/membership/+page.svelte create mode 100644 src/stores/user.ts diff --git a/src/api/apiService.ts b/src/api/apiService.ts index 6116c1a..0ab9874 100644 --- a/src/api/apiService.ts +++ b/src/api/apiService.ts @@ -44,7 +44,7 @@ export async function fetchAPIEvent( data: { token } } = await redisSessionManager.getSession(event.cookies); - return fetchAPI(endpoint, method, body, { Authorization: `Basic ${token}` }); + return fetchAPI(endpoint, method, body, { Authorization: `Bearer ${token}` }); } export default fetchAPI; diff --git a/src/api/authService.ts b/src/api/authService.ts index 4ca9b46..c9aa6b3 100644 --- a/src/api/authService.ts +++ b/src/api/authService.ts @@ -1,19 +1,35 @@ -import fetchAPI, { fetchAPIEvent } from '$api/apiService'; +import { fetchAPIEvent } from '$api/apiService'; +import { env } from '$env/dynamic/private'; import { authStore } from '$stores/authStore'; import { redisSessionManager } from '$stores/redisSessionManager'; import type { RequestEvent } from '@sveltejs/kit'; import { v4 as uuidv4 } from 'uuid'; export const login = async (event: RequestEvent, username: string, password: string) => { - const token = btoa(`${username}:${password}`); - await redisSessionManager.createSession(event.cookies, { token }, uuidv4()); - if (!(await checkAuth(event))) { - { - authStore.set({ token: undefined, error: 'Invalid username or password.' }); + try { + const response = await fetch(`${env.API_BASE_URL}/auth/login`, { + method: 'POST', + body: JSON.stringify({ username, password }), + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Invalid username or password.' })); + authStore.set({ token: undefined, error: errorData.error || 'Invalid username or password.' }); return false; } + + const { token } = await response.json(); + + await redisSessionManager.createSession(event.cookies, { token }, uuidv4()); + + return true; + } catch (err) { + authStore.set({ token: undefined, error: 'Login failed. Please try again.' }); + return false; } - return true; }; export const logout = (event: RequestEvent) => { diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..ce6e612 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -3,7 +3,9 @@ declare global { namespace App { // interface Error {} - // interface Locals {} + interface Locals { + user: import('$models/user').User | null; + } // interface PageData {} // interface PageState {} // interface Platform {} diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..b2810d3 --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,17 @@ +export interface Permission { + id: string; + name: string; +} + +export interface Role { + id: string; + name: string; + permissions: Permission[]; +} + +export interface User { + id: string; + username: string; + role_id: string; + role: Role; +} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..ec238fd --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,7 @@ +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async ({ locals }) => { + return { + user: locals.user + }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 19926df..bfed911 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,12 @@ -
diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 188709b..22d002d 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -2,6 +2,7 @@ import ServerCard from '$components/ServerCard.svelte'; import Toast from '$components/Toast.svelte'; import type { Server } from '$models/server'; + import { user, hasPermission } from '$stores/user'; const { data, form } = $props(); let servers: Server[] = data.servers; @@ -16,6 +17,14 @@

ACC Server Manager

+{/if} + +
+ + + + + + {#if hasPermission($user, 'membership.edit')} + + {/if} + + + + {#each users as user (user.id)} + + + + {#if hasPermission($user, 'membership.edit')} + + {/if} + + {/each} + +
UsernameRoleActions
{user.username}{user.role.name} + + +
+
+
diff --git a/src/stores/user.ts b/src/stores/user.ts new file mode 100644 index 0000000..e6f61f6 --- /dev/null +++ b/src/stores/user.ts @@ -0,0 +1,15 @@ +import { writable, type Writable } from 'svelte/store'; +import type { User } from '$models/user'; + +export const user: Writable = writable(null); + +export function hasPermission(user: User | null, permission: string): boolean { + if (!user || !user.role || !user.role.permissions) { + return false; + } + // Super Admins have all permissions + if (user.role.name === 'Super Admin') { + return true; + } + return user.role.permissions.some((p) => p.name === permission); +} diff --git a/vite.config.ts b/vite.config.ts index a69fa55..d44e38f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,5 +3,13 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit(), tailwindcss()] + plugins: [sveltekit(), tailwindcss()], + server: { + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true + } + } + } });