Blocking admin pages if you don't already have an admin role. Adding add and remove roles to admin page.

This commit is contained in:
Bradley Shellnut 2024-03-19 12:01:15 -07:00
parent 5e174c875f
commit 66eb09b237
8 changed files with 153 additions and 57 deletions

View file

@ -1 +1,5 @@
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;
export const forbiddenMessage = {
type: 'error',
message: 'You are not allowed to access this'
} as const;

View file

@ -1,7 +1,7 @@
import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle';
import { user_roles } from '../../../../schema';
export async function load(event) {

View file

@ -1,13 +1,14 @@
import { eq, inArray, not } from 'drizzle-orm';
import { and, eq, inArray, not } from 'drizzle-orm';
import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import { roles, users } from '../../../../../../schema';
import { roles, user_roles, users } from '../../../../../../schema';
export const load: PageServerLoad = async (event) => {
const { params } = event;
const { id } = params;
const { user } = event.locals;
// TODO: Ensure admin user
if (!event.locals.user) {
@ -30,10 +31,15 @@ export const load: PageServerLoad = async (event) => {
}
});
const containsAdminRole = foundUser?.user_roles?.some(
(user_role) => user_role?.role?.name === 'admin'
);
if (!containsAdminRole) {
console.log('Not an admin');
redirect(302, '/login', notSignedInMessage, event);
}
const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [];
console.log('currentRoleIds', currentRoleIds);
let availableRoles: { name: string; cuid: string }[] = [];
if (currentRoleIds?.length > 0) {
availableRoles = await db.query.roles.findMany({
@ -53,13 +59,83 @@ export const load: PageServerLoad = async (event) => {
export const actions = {
addRole: async (event) => {
const { params, request } = event;
d;
const data = await request.formData();
console.log('data', data);
const { request, locals } = event;
const { user } = locals;
const roleCUID = data.get('value');
const dbRole = await db.query.roles.findFirst({ where: eq(roles.cuid, roleCUID?.toString()) });
if (!user) {
redirect(302, '/login', notSignedInMessage, event);
}
const userRoles = await db.query.user_roles.findMany({
where: eq(users.id, user.id),
with: {
role: {
columns: {
name: true,
cuid: true
}
}
}
});
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
if (!containsAdminRole) {
redirect(302, '/login', forbiddenMessage, event);
}
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
});
console.log('dbRole', dbRole);
if (dbRole) {
await db.insert(user_roles).values({
user_id: user.id,
role_id: dbRole.id
});
return {
success: true
};
}
},
removeRole: async (event) => {
const { request, locals } = event;
const { user } = locals;
if (!user) {
redirect(302, '/login', notSignedInMessage, event);
}
const userRoles = await db.query.user_roles.findMany({
where: eq(users.id, user.id),
with: {
role: {
columns: {
name: true,
cuid: true
}
}
}
});
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
if (!containsAdminRole) {
redirect(302, '/login', forbiddenMessage, event);
}
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
});
console.log('dbRole', dbRole);
if (dbRole) {
await db
.delete(user_roles)
.where(and(eq(user_roles.user_id, user.id), eq(user_roles.role_id, dbRole.id)));
return {
success: true
};
}
}
};

View file

@ -5,6 +5,8 @@
// import AddRolesForm from './add-roles-form.svelte';
export let data;
export let form;
const { user, availableRoles } = data;
const { user_roles }: { user_roles: { role: { name: string, cuid: string } }[] } = user;
</script>
@ -17,15 +19,29 @@
<h2>User Roles</h2>
{#each user_roles as user_role}
<p>{capitalize(user_role?.role?.name)}</p>
{#if user_role?.role?.name !== 'user'}
<form action="?/removeRole" method="POST" use:enhance data-sveltekit-reload>
<div class="flex flex-row space-x-3 place-items-center mt-2">
<input id="role" type="hidden" name="role" value={user_role?.role?.cuid} />
<Button type="submit">Remove</Button>
<p>{capitalize(user_role?.role?.name)}</p>
</div>
</form>
{:else}
<p>{capitalize(user_role?.role?.name)}</p>
{/if}
{/each}
{#if form?.success}
<p>Sucessfully added role</p>
{/if}
<h2>Roles Available to Assign</h2>
<!--<AddRolesForm {availableRoles} />-->
{#each availableRoles as role}
<form action="?/addRole" method="POST" use:enhance>
<form action="?/addRole" method="POST" use:enhance data-sveltekit-reload>
<div class="flex flex-row space-x-3 place-items-center mt-2">
<input type="hidden" name="role" value={role?.cuid} />
<input id="role" type="hidden" name="role" value={role?.cuid} />
<Button type="submit">Add</Button>
<p>{capitalize(role?.name)}</p>
</div>

View file

@ -1,20 +1,20 @@
<script lang="ts">
import * as Form from "$lib/components/ui/form";
import { Input } from "$lib/components/ui/input";
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
import * as Form from '$lib/components/ui/form';
import { Input } from '$lib/components/ui/input';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import { addRoleSchema, type AddRoleSchema } from '$lib/validations/account';
import {
type SuperValidated,
type Infer,
superForm,
} from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
superForm
} from 'sveltekit-superforms';
import { zodClient } from 'sveltekit-superforms/adapters';
export let availableRoles: { name: string; cuid: string }[] = [];
const data: SuperValidated<Infer<AddRoleSchema>> = availableRoles;
const form = superForm(data, {
validators: zodClient(addRoleSchema),
validators: zodClient(addRoleSchema)
// onUpdated: ({ form: f }) => {
// if (f.valid) {
// toast.success("You submitted" + JSON.stringify(f.data, null, 2));

View file

@ -1,37 +1,37 @@
<script lang="ts">
import Ellipsis from "lucide-svelte/icons/ellipsis";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
import Ellipsis from 'lucide-svelte/icons/ellipsis';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { Button } from '$lib/components/ui/button';
import { User } from 'lucide-svelte';
export let cuid: string;
export let cuid: string;
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button
variant="ghost"
builders={[builder]}
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(cuid)}>
Copy User ID
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Trigger asChild let:builder>
<Button
variant="ghost"
builders={[builder]}
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(cuid)}>
Copy User ID
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<a href={`/admin/users/${cuid}`}>
<DropdownMenu.Item>
<User class="mr-2 h-4 w-4" />
<span>View user</span>
</DropdownMenu.Item>
</a>
</DropdownMenu.Content>
</DropdownMenu.Content>
</DropdownMenu.Root>

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { Checkbox } from "$lib/components/ui/checkbox";
import type { Writable } from "svelte/store";
import { Checkbox } from '$lib/components/ui/checkbox';
import type { Writable } from 'svelte/store';
export let checked: Writable<boolean>;
export let checked: Writable<boolean>;
</script>
<Checkbox bind:checked={$checked} />

View file

@ -29,12 +29,12 @@ const signUpDefaults = {
};
export const load: PageServerLoad = async (event) => {
redirect(
302,
'/waitlist',
{ type: 'error', message: 'Sign-up not yet available. Please add your email to the waitlist!' },
event
);
// redirect(
// 302,
// '/waitlist',
// { type: 'error', message: 'Sign-up not yet available. Please add your email to the waitlist!' },
// event
// );
if (event.locals.user) {
const message = { type: 'success', message: 'You are already signed in' } as const;