Team based role auth for routes

This commit is contained in:
2024-05-23 09:32:22 -04:00
parent 55bc1acbb3
commit c03ad48615
6 changed files with 63 additions and 26 deletions

View File

@@ -6,6 +6,7 @@ import {
Functions, Functions,
ID, ID,
AppwriteException, AppwriteException,
Teams,
} from 'appwrite'; } from 'appwrite';
import { useAuthStore } from 'src/stores/auth'; import { useAuthStore } from 'src/stores/auth';
import { Dialog, Notify } from 'quasar'; import { Dialog, Notify } from 'quasar';
@@ -67,6 +68,7 @@ const AppwriteIds = process.env.DEV
const account = new Account(client); const account = new Account(client);
const databases = new Databases(client); const databases = new Databases(client);
const functions = new Functions(client); const functions = new Functions(client);
const teams = new Teams(client);
let appRouter: Router; let appRouter: Router;
@@ -136,6 +138,7 @@ async function login(email: string, password: string) {
export { export {
client, client,
account, account,
teams,
databases, databases,
functions, functions,
ID, ID,

View File

@@ -3,7 +3,12 @@
<q-card> <q-card>
<q-toolbar> <q-toolbar>
<q-toolbar-title>Select a Boat and Time</q-toolbar-title> <q-toolbar-title>Select a Boat and Time</q-toolbar-title>
<q-btn icon="close" flat round dense v-close-popup /> <q-btn
icon="close"
flat
round
dense
v-close-popup />
</q-toolbar> </q-toolbar>
<q-separator /> <q-separator />
<CalendarHeaderComponent v-model="selectedDate" /> <CalendarHeaderComponent v-model="selectedDate" />
@@ -21,8 +26,7 @@
:short-interval-label="true" :short-interval-label="true"
v-model="selectedDate" v-model="selectedDate"
:column-count="boats.length" :column-count="boats.length"
v-touch-swipe.left.right="handleSwipe" v-touch-swipe.left.right="handleSwipe">
>
<template #head-day="{ scope }"> <template #head-day="{ scope }">
<div style="text-align: center; font-weight: 800"> <div style="text-align: center; font-weight: 800">
{{ getBoatDisplayName(scope) }} {{ getBoatDisplayName(scope) }}
@@ -30,7 +34,9 @@
</template> </template>
<template #day-body="{ scope }"> <template #day-body="{ scope }">
<div v-for="block in getBoatBlocks(scope)" :key="block.$id"> <div
v-for="block in getBoatBlocks(scope)"
:key="block.$id">
<div <div
class="timeblock" class="timeblock"
:class="selectedBlock?.$id === block.$id ? 'selected' : ''" :class="selectedBlock?.$id === block.$id ? 'selected' : ''"
@@ -43,9 +49,9 @@
" "
:id="block.id" :id="block.id"
@click="selectBlock($event, scope, block)" @click="selectBlock($event, scope, block)"
v-close-popup v-close-popup>
> {{ boats[scope.columnIndex].name }}
{{ boats[scope.columnIndex].name }}<br /> <br />
{{ {{
selectedBlock?.$id === block.$id ? 'Selected' : 'Available' selectedBlock?.$id === block.$id ? 'Selected' : 'Available'
}} }}
@@ -53,26 +59,25 @@
</div> </div>
<div <div
v-for="reservation in getBoatReservations(scope)" v-for="reservation in getBoatReservations(scope)"
:key="reservation.$id" :key="reservation.$id">
>
<div <div
class="reservation" class="reservation column"
:style=" :style="
reservationStyles( reservationStyles(
reservation, reservation,
scope.timeStartPos, scope.timeStartPos,
scope.timeDurationHeight scope.timeDurationHeight
) )
" ">
> {{ getUserName(reservation.user) || 'loading...' }}
{{ getUserName(reservation.user) || 'loading...' }}<br /> <br />
<q-chip icon="key">{{ reservation.reason }}</q-chip> <q-chip icon="key">{{ reservation.reason }}</q-chip>
</div> </div>
</div> </div>
</template> </template>
</QCalendarDay> </QCalendarDay>
</div></q-card </div>
> </q-card>
</div> </div>
</template> </template>

View File

@@ -166,7 +166,6 @@ const onReset = () => {
bookingForm.value = { ...newForm }; bookingForm.value = { ...newForm };
}; };
const onSubmit = () => { const onSubmit = () => {
console.log('SUBMIT!');
const booking = bookingForm.value; const booking = bookingForm.value;
if ( if (
!(booking.boat && booking.startDate && booking.endDate && auth.currentUser) !(booking.boat && booking.startDate && booking.endDate && auth.currentUser)

View File

@@ -9,6 +9,10 @@ import {
import routes from './routes'; import routes from './routes';
import { useAuthStore } from 'src/stores/auth'; import { useAuthStore } from 'src/stores/auth';
const publicRoutes = routes
.filter((route) => route.meta?.publicRoute)
.map((r) => r.path);
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
* directly export the Router instantiation; * directly export the Router instantiation;
@@ -35,15 +39,33 @@ export default route(function (/* { store, ssrContext } */) {
history: createHistory(process.env.VUE_ROUTER_BASE), history: createHistory(process.env.VUE_ROUTER_BASE),
}); });
Router.beforeEach((to) => { Router.beforeEach(async (to, from, next) => {
const publicPages = routes const authStore = useAuthStore();
.filter((route) => route.meta?.publicRoute) const currentUser = authStore.currentUser;
.map((r) => r.path); const authRequired = !publicRoutes.includes(to.path);
const authRequired = !publicPages.includes(to.path); const requiredRoles = to.meta?.requiredRoles as string[];
if (authRequired && !useAuthStore().currentUser) { if (authRequired && !currentUser) {
return '/login'; return next('/login');
} }
if (requiredRoles) {
if (!currentUser) {
return next('/login');
}
try {
const hasRole = await authStore.hasRequiredRole(requiredRoles);
if (!hasRole) {
return next(from);
}
} catch (error) {
console.error('Failed to fetch user teams:', error);
return next('/error'); // Redirect to an error page or handle it as needed
}
}
next();
}); });
return Router; return Router;

View File

@@ -44,7 +44,7 @@ const routes: RouteRecordRaw[] = [
path: 'manage', path: 'manage',
component: () => import('src/pages/schedule/ManageCalendar.vue'), component: () => import('src/pages/schedule/ManageCalendar.vue'),
name: 'manage-schedule', name: 'manage-schedule',
meta: { requiresScheduleAdmin: true }, meta: { requiredRoles: ['Schedule Admins'] },
}, },
], ],
}, },
@@ -102,7 +102,7 @@ const routes: RouteRecordRaw[] = [
{ {
path: '/admin', path: '/admin',
component: () => import('layouts/AdminLayout.vue'), component: () => import('layouts/AdminLayout.vue'),
meta: { requiresAdmin: true }, meta: { requiredRoles: ['admin'] },
children: [ children: [
{ {
path: '/user', path: '/user',

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ID, account, functions } from 'boot/appwrite'; import { ID, account, functions, teams } from 'boot/appwrite';
import { ExecutionMethod, OAuthProvider, type Models } from 'appwrite'; import { ExecutionMethod, OAuthProvider, type Models } from 'appwrite';
import { ref } from 'vue'; import { ref } from 'vue';
@@ -15,6 +15,13 @@ export const useAuthStore = defineStore('auth', () => {
} }
} }
const hasRequiredRole = async (requiredRoles: string[]): Promise<boolean> => {
const userTeams = await teams.list();
const userTeamNames = userTeams.teams.map((team) => team.name);
console.log(requiredRoles.some((role) => userTeamNames.includes(role)));
return requiredRoles.some((role) => userTeamNames.includes(role));
};
async function register(email: string, password: string) { async function register(email: string, password: string) {
await account.create(ID.unique(), email, password); await account.create(ID.unique(), email, password);
return await login(email, password); return await login(email, password);
@@ -65,6 +72,7 @@ export const useAuthStore = defineStore('auth', () => {
return { return {
currentUser, currentUser,
getUserNameById, getUserNameById,
hasRequiredRole,
register, register,
login, login,
googleLogin, googleLogin,