Team based role auth for routes
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user