This commit is contained in:
j3d1 2023-12-13 14:02:39 +01:00
parent 6c69948c44
commit b6ed492382
6 changed files with 96 additions and 26 deletions

View file

@ -6,7 +6,7 @@
<div aria-live="polite" aria-atomic="true" v-if="isLoggedIn" <div aria-live="polite" aria-atomic="true" v-if="isLoggedIn"
class="d-flex justify-content-end align-items-start fixed-top mx-1 my-5 py-3" class="d-flex justify-content-end align-items-start fixed-top mx-1 my-5 py-3"
style="min-height: 200px; z-index: 100000; pointer-events: none"> style="min-height: 200px; z-index: 100000; pointer-events: none">
<Toast v-for="toast in toasts" :key="toast" :title="toast.title" :message="toast.message" <Toast v-for="(toast , index) in toasts" :key="index" :title="toast.title" :message="toast.message"
:color="toast.color" :color="toast.color"
@close="removeToast(toast.key)" style="pointer-events: auto"/> @close="removeToast(toast.key)" style="pointer-events: auto"/>
</div> </div>

View file

@ -11,25 +11,25 @@
</div> </div>
</div> </div>
<ul class="nav nav-tabs flex-nowrap"> <ul class="nav nav-tabs flex-nowrap">
<li class="nav-item"> <li class="nav-item" v-if="checkPermission(getEventSlug, 'inventory.view_item')">
<router-link :to="{name: 'items', params: {event: getEventSlug}}" <router-link :to="{name: 'items', params: {event: getEventSlug}}"
:class="['nav-link', { active: getActiveView === 'items' || getActiveView === 'item' }]"> :class="['nav-link', { active: getActiveView === 'items' || getActiveView === 'item' }]">
Items Items
</router-link> </router-link>
</li> </li>
<li class="nav-item" v-if="checkRole('team')"> <li class="nav-item" v-if="checkPermission(getEventSlug, 'tickets.view_issuethread')">
<router-link :to="{name: 'tickets', params: {event: getEventSlug}}" <router-link :to="{name: 'tickets', params: {event: getEventSlug}}"
:class="['nav-link', { active: getActiveView === 'tickets' || getActiveView === 'ticket' }]"> :class="['nav-link', { active: getActiveView === 'tickets' || getActiveView === 'ticket' }]">
Tickets Tickets
</router-link> </router-link>
</li> </li>
<li class="nav-item" v-if="checkRole('admin')"> <li class="nav-item" v-if="checkPermission(getEventSlug, 'inventory.delete_event')">
<router-link :to="{name: 'admin'}" :class="['nav-link', { active: getActiveView === 'admin' }]"> <router-link :to="{name: 'admin'}" :class="['nav-link', { active: getActiveView === 'admin' }]">
Admin Admin
</router-link> </router-link>
</li> </li>
</ul> </ul>
<form class="form-inline mt-1 my-lg-auto my-xl-auto w-100 d-inline"> <form class="form-inline mt-1 my-lg-auto my-xl-auto w-100 d-inline" v-if="hasPermissions">
<input <input
class="form-control w-100" class="form-control w-100"
type="search" type="search"
@ -39,7 +39,7 @@
disabled disabled
> >
</form> </form>
<div class="custom-control-inline mr-1"> <div class="custom-control-inline mr-1" v-if="hasPermissions">
<div class="btn-group btn-group-toggle mx-1"> <div class="btn-group btn-group-toggle mx-1">
<button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')"> <button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
<font-awesome-icon icon="th"/> <font-awesome-icon icon="th"/>
@ -99,14 +99,15 @@ export default {
] ]
}), }),
computed: { computed: {
...mapState(['events', 'activeEvent', 'layout']), ...mapState(['events', 'layout']),
...mapGetters(['getEventSlug', 'getActiveView', "checkRole"]), ...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions"]),
}, },
methods: { methods: {
...mapActions(['changeEvent', 'changeView', 'searchEventItems']), ...mapActions(['changeEvent', 'changeView', 'searchEventItems']),
...mapMutations(['setLayout']), ...mapMutations(['setLayout']),
navigateTo(link) { navigateTo(link) {
this.$router.push(link); if (this.$router.currentRoute.path !== link)
this.$router.push(link);
} }
} }
}; };

View file

@ -13,6 +13,7 @@ import Tickets from "@/views/Tickets.vue";
import Ticket from "@/views/Ticket.vue"; import Ticket from "@/views/Ticket.vue";
import Admin from "@/views/admin/Admin.vue"; import Admin from "@/views/admin/Admin.vue";
import store from "@/store"; import store from "@/store";
import Empty from "@/views/Empty.vue";
Vue.use(VueRouter); Vue.use(VueRouter);
@ -21,17 +22,29 @@ const routes = [
{path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}}, {path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}},
{path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}}, {path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}},
{path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}}, {path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}},
{path: '/:event/boxes', name: 'boxes', component: Boxes, meta: {requiresAuth: true}}, {path: '/:event/boxes', name: 'boxes', component: Boxes, meta:
{path: '/:event/items', name: 'items', component: Items, meta: {requiresAuth: true}}, {requiresAuth: true, requiresPermission: 'inventory.view_container'}},
{path: '/:event/box/:uid', name: 'box', component: Boxes, meta: {requiresAuth: true}}, {path: '/:event/items', name: 'items', component: Items, meta:
{path: '/:event/item/:uid', name: 'item', component: Items, meta: {requiresAuth: true}}, {requiresAuth: true, requiresPermission: 'inventory.view_item'}},
{path: '/:event/tickets', name: 'tickets', component: Tickets, meta: {requiresAuth: true}}, {path: '/:event/box/:uid', name: 'box', component: Boxes, meta:
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta: {requiresAuth: true}}, {requiresAuth: true, requiresPermission: 'inventory.view_container'}},
{path: '/admin', name: 'admin', component: Admin, meta: {requiresAuth: true}}, {path: '/:event/item/:uid', name: 'item', component: Items, meta:
{path: '/admin/files', name: 'files', component: Files, meta: {requiresAuth: true}}, {requiresAuth: true, requiresPermission: 'inventory.view_item'}},
{path: '/admin/events', name: 'events', component: Events, meta: {requiresAuth: true}}, {path: '/:event/tickets', name: 'tickets', component: Tickets, meta:
{path: '/admin/debug', name: 'debug', component: Debug, meta: {requiresAuth: true}}, {requiresAuth: true, requiresPermission: 'inventory.view_issuethread'}},
{path: '/admin/users', name: 'users', component: Events, meta: {requiresAuth: true}}, {path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta:
{requiresAuth: true, requiresPermission: 'inventory.view_issuethread'}},
{path: '/admin', name: 'admin', component: Admin, meta:
{requiresAuth: true, requiresPermission: 'inventory.delete_event'}},
{path: '/admin/files', name: 'files', component: Files, meta:
{requiresAuth: true, requiresPermission: 'inventory.delete_event'}},
{path: '/admin/events', name: 'events', component: Events, meta:
{requiresAuth: true, requiresPermission: 'inventory.delete_event'}},
{path: '/admin/debug', name: 'debug', component: Debug, meta:
{requiresAuth: true, requiresPermission: 'inventory.delete_event'}},
{path: '/admin/users', name: 'users', component: Events, meta:
{requiresAuth: true, requiresPermission: 'inventory.delete_event'}},
{path: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}},
{path: '*', component: Error}, {path: '*', component: Error},
]; ];
@ -57,11 +70,16 @@ const router = new VueRouter({
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.getters.isLoggedIn) { if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
//console.log("Not logged in, redirecting to login page") console.log("Not logged in, redirecting to login page")
next({ next({
name: 'login', name: 'login',
query: {redirect: to.fullPath}, query: {redirect: to.fullPath},
}) })
} else if (to.meta.requiresPermission && !store.getters.checkPermission(to.params.event || "*", to.meta.requiresPermission)) {
console.log("Not enough permissions, redirecting to empty page")
next({
path: '/user',
})
} else { } else {
next() next()
} }

View file

@ -67,11 +67,11 @@ const store = new Vuex.Store({
loadedBoxes: [], loadedBoxes: [],
toasts: [], toasts: [],
tickets: [], tickets: [],
userRoles: ['admin', 'team', 'orga', 'user'],
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3', lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'), lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
remember: false, remember: false,
user: null, user: null,
userPermissions: [],
token: null, token: null,
token_expiry: null, token_expiry: null,
local_loaded: false, local_loaded: false,
@ -81,11 +81,13 @@ const store = new Vuex.Store({
getActiveView: state => state.route.name || 'items', getActiveView: state => state.route.name || 'items',
getFilters: state => state.route.query, getFilters: state => state.route.query,
getBoxes: state => state.loadedBoxes, getBoxes: state => state.loadedBoxes,
checkRole: state => role => state.userRoles.includes(role), checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`),
hasPermissions: state => state.userPermissions.length > 0,
isLoggedIn(state) { isLoggedIn(state) {
if (!state.local_loaded) { if (!state.local_loaded) {
state.remember = localStorage.getItem('remember') === 'true' state.remember = localStorage.getItem('remember') === 'true'
state.user = localStorage.getItem('user') state.user = localStorage.getItem('user')
state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]')
state.token = localStorage.getItem('token') state.token = localStorage.getItem('token')
state.token_expiry = localStorage.getItem('token_expiry') state.token_expiry = localStorage.getItem('token_expiry')
state.local_loaded = true state.local_loaded = true
@ -156,6 +158,11 @@ const store = new Vuex.Store({
if (user) if (user)
localStorage.setItem('user', user); localStorage.setItem('user', user);
}, },
setPermissions(state, permissions) {
state.userPermissions = permissions;
if (permissions)
localStorage.setItem('permissions', JSON.stringify(permissions));
},
setToken(state, {token, expiry}) { setToken(state, {token, expiry}) {
state.token = token; state.token = token;
state.token_expiry = expiry; state.token_expiry = expiry;
@ -167,6 +174,7 @@ const store = new Vuex.Store({
state.user = null; state.user = null;
state.token = null; state.token = null;
localStorage.removeItem('user'); localStorage.removeItem('user');
localStorage.removeItem('permissions');
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('token_expiry'); localStorage.removeItem('token_expiry');
router.push('/login'); router.push('/login');
@ -219,7 +227,7 @@ const store = new Vuex.Store({
async afterLogin({dispatch}) { async afterLogin({dispatch}) {
const boxes = dispatch('loadBoxes'); const boxes = dispatch('loadBoxes');
const items = dispatch('loadEventItems'); const items = dispatch('loadEventItems');
const tickets = dispatch('loadTickets'); const tickets = dispatch('loadTickets');
const user = dispatch('loadUserInfo'); const user = dispatch('loadUserInfo');
}, },
async fetchImage({state}, url) { async fetchImage({state}, url) {
@ -228,6 +236,7 @@ const store = new Vuex.Store({
async loadUserInfo({commit}) { async loadUserInfo({commit}) {
const {data} = await axios.get('/2/self/'); const {data} = await axios.get('/2/self/');
commit('setUser', data.username); commit('setUser', data.username);
commit('setPermissions', data.permissions);
}, },
async loadEvents({commit}) { async loadEvents({commit}) {
const {data} = await axios.get('/2/events/'); const {data} = await axios.get('/2/events/');
@ -247,7 +256,7 @@ const store = new Vuex.Store({
try { try {
commit('replaceLoadedItems', []); commit('replaceLoadedItems', []);
const slug = getters.getEventSlug; const slug = getters.getEventSlug;
if( slug in state.itemCache ) { if (slug in state.itemCache) {
commit('replaceLoadedItems', state.itemCache[slug]); commit('replaceLoadedItems', state.itemCache[slug]);
} }
const {data} = await axios.get(`/2/${slug}/items/`); const {data} = await axios.get(`/2/${slug}/items/`);

29
web/src/views/Empty.vue Normal file
View file

@ -0,0 +1,29 @@
<template>
<div class="container-fluid px-xl-5 mt-3">
<div class="row">
<div class="col-xl-8 offset-xl-2">
<div class="card bg-dark text-light mb-2" id="filters">
<div class="card-header">
<h3 class="text-center">User: {{user}}</h3>
</div>
<div class="card-body">
<p>Your Acoount is not yet activated. Please contact an admin.</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'Empty',
computed: mapState(['user']),
};
</script>
<style scoped>
</style>

View file

@ -1,5 +1,18 @@
<template> <template>
<p>Error</p> <div class="container-fluid px-xl-5 mt-3">
<div class="row">
<div class="col-xl-8 offset-xl-2">
<div class="card bg-dark text-light mb-2" id="filters">
<div class="card-header">
<h3 class="text-center">Error</h3>
</div>
<div class="card-body">
<p>Something went wrong. <a href="/">Go back to the start page </a>or contact an admin.</p>
</div>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>