add membership and permissions
This commit is contained in:
@@ -44,7 +44,7 @@ export async function fetchAPIEvent(
|
|||||||
data: { token }
|
data: { token }
|
||||||
} = await redisSessionManager.getSession(event.cookies);
|
} = await redisSessionManager.getSession(event.cookies);
|
||||||
|
|
||||||
return fetchAPI(endpoint, method, body, { Authorization: `Basic ${token}` });
|
return fetchAPI(endpoint, method, body, { Authorization: `Bearer ${token}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchAPI;
|
export default fetchAPI;
|
||||||
|
|||||||
@@ -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 { authStore } from '$stores/authStore';
|
||||||
import { redisSessionManager } from '$stores/redisSessionManager';
|
import { redisSessionManager } from '$stores/redisSessionManager';
|
||||||
import type { RequestEvent } from '@sveltejs/kit';
|
import type { RequestEvent } from '@sveltejs/kit';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export const login = async (event: RequestEvent, username: string, password: string) => {
|
export const login = async (event: RequestEvent, username: string, password: string) => {
|
||||||
const token = btoa(`${username}:${password}`);
|
try {
|
||||||
await redisSessionManager.createSession(event.cookies, { token }, uuidv4());
|
const response = await fetch(`${env.API_BASE_URL}/auth/login`, {
|
||||||
if (!(await checkAuth(event))) {
|
method: 'POST',
|
||||||
{
|
body: JSON.stringify({ username, password }),
|
||||||
authStore.set({ token: undefined, error: 'Invalid username or 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const { token } = await response.json();
|
||||||
|
|
||||||
|
await redisSessionManager.createSession(event.cookies, { token }, uuidv4());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
authStore.set({ token: undefined, error: 'Login failed. Please try again.' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logout = (event: RequestEvent) => {
|
export const logout = (event: RequestEvent) => {
|
||||||
|
|||||||
4
src/app.d.ts
vendored
4
src/app.d.ts
vendored
@@ -3,7 +3,9 @@
|
|||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Locals {}
|
interface Locals {
|
||||||
|
user: import('$models/user').User | null;
|
||||||
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|||||||
17
src/models/user.ts
Normal file
17
src/models/user.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
7
src/routes/+layout.server.ts
Normal file
7
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
return {
|
||||||
|
user: locals.user
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
let { children } = $props();
|
import { user } from '$stores/user';
|
||||||
|
import type { LayoutData } from './$types';
|
||||||
|
|
||||||
|
let { data } = $props<LayoutData>();
|
||||||
|
|
||||||
|
// Set the user store with data from the server
|
||||||
|
$: $user = data.user;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import ServerCard from '$components/ServerCard.svelte';
|
import ServerCard from '$components/ServerCard.svelte';
|
||||||
import Toast from '$components/Toast.svelte';
|
import Toast from '$components/Toast.svelte';
|
||||||
import type { Server } from '$models/server';
|
import type { Server } from '$models/server';
|
||||||
|
import { user, hasPermission } from '$stores/user';
|
||||||
|
|
||||||
const { data, form } = $props();
|
const { data, form } = $props();
|
||||||
let servers: Server[] = data.servers;
|
let servers: Server[] = data.servers;
|
||||||
@@ -16,6 +17,14 @@
|
|||||||
<div class="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
|
<div class="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
|
||||||
<h1 class="text-2xl font-bold">ACC Server Manager</h1>
|
<h1 class="text-2xl font-bold">ACC Server Manager</h1>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
|
{#if hasPermission($user, 'membership.view')}
|
||||||
|
<a href="/dashboard/membership" class="flex items-center text-gray-300 hover:text-white">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M15 21a6 6 0 00-9-5.197m0 0A5.975 5.975 0 0112 13a5.975 5.975 0 01-3-1.197" />
|
||||||
|
</svg>
|
||||||
|
<span class="ml-1 hidden sm:inline">Users</span>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
<a href="/logout">
|
<a href="/logout">
|
||||||
<button class="flex items-center text-gray-300 hover:text-white">
|
<button class="flex items-center text-gray-300 hover:text-white">
|
||||||
<svg
|
<svg
|
||||||
@@ -67,9 +76,4 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.server-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
18
src/routes/dashboard/membership/+page.server.ts
Normal file
18
src/routes/dashboard/membership/+page.server.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load = async ({ locals, fetch }) => {
|
||||||
|
if (!locals.user) {
|
||||||
|
throw redirect(303, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/users');
|
||||||
|
if (!response.ok) {
|
||||||
|
return { users: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
users
|
||||||
|
};
|
||||||
|
};
|
||||||
46
src/routes/dashboard/membership/+page.svelte
Normal file
46
src/routes/dashboard/membership/+page.svelte
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { user, hasPermission } from '$stores/user';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
$: users = data.users || [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container mx-auto p-4">
|
||||||
|
<h1 class="text-2xl font-bold mb-4">User Management</h1>
|
||||||
|
|
||||||
|
{#if hasPermission($user, 'membership.create')}
|
||||||
|
<div class="flex justify-end mb-4">
|
||||||
|
<button class="btn btn-primary">Create User</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Role</th>
|
||||||
|
{#if hasPermission($user, 'membership.edit')}
|
||||||
|
<th>Actions</th>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each users as user (user.id)}
|
||||||
|
<tr>
|
||||||
|
<td>{user.username}</td>
|
||||||
|
<td>{user.role.name}</td>
|
||||||
|
{#if hasPermission($user, 'membership.edit')}
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm">Edit</button>
|
||||||
|
<button class="btn btn-sm btn-error">Delete</button>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
15
src/stores/user.ts
Normal file
15
src/stores/user.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
import type { User } from '$models/user';
|
||||||
|
|
||||||
|
export const user: Writable<User | null> = 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);
|
||||||
|
}
|
||||||
@@ -3,5 +3,13 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit(), tailwindcss()]
|
plugins: [sveltekit(), tailwindcss()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user