diff --git a/quasar.conf.js b/quasar.conf.js index 0074565..e13865c 100644 --- a/quasar.conf.js +++ b/quasar.conf.js @@ -103,7 +103,7 @@ module.exports = configure(function(ctx) { // directives: [], // Quasar plugins - plugins: ['LocalStorage', 'Loading'] + plugins: ['LocalStorage', 'SessionStorage', 'Loading'] }, // animations: 'all', // --- includes all animations diff --git a/src/App.vue b/src/App.vue index 0b7a348..ae9bb31 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,6 +7,6 @@ import { defineComponent } from '@vue/composition-api'; export default defineComponent({ - name: 'App', + name: 'App' }); diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 54dc836..b6255dc 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -16,9 +16,9 @@ export default boot>(({ Vue, store }) => { axios.defaults.baseURL = config.baseURL; axios.interceptors.request.use(config => { - const session = store.state.user.session; - if (session.token) { - config.headers = {'Authorization': 'Bearer ' + session.token}; + const session = store.state.session.currentSession; + if (session?.token) { + config.headers = { Authorization: 'Bearer ' + session.token }; } return config; }); diff --git a/src/boot/login.ts b/src/boot/login.ts index a9c431f..a4571eb 100644 --- a/src/boot/login.ts +++ b/src/boot/login.ts @@ -1,53 +1,49 @@ import { boot } from 'quasar/wrappers'; import { StateInterface } from 'src/store'; import { RouteRecord } from 'vue-router'; -import { Store } from 'vuex' +import { Store } from 'vuex'; export default boot>(({ router, store }) => { router.beforeEach((to, from, next) => { - store - .dispatch('user/loadFromLocalStorage') - .then(() => { - const user = store.state.user.user; - const session = store.state.user.session; + const user = store.state.user.currentUser; + const session = store.state.session.currentSession; - let permissions: string[] = []; - user.roles.forEach(role => { - permissions = permissions.concat(role.permissions); - }); - - if (to.name != 'login') { - if (session.expires >= new Date() || session.token === '') { - store.dispatch('user/doLogout').catch(error => { - console.warn(error); - }); - return next({ name: 'login', query: { redirect: to.fullPath } }); - } - if ( - to.matched.every((record: RouteRecord) => { - if (!('meta' in record) || !('permissions' in record.meta)) - return true; - if (record.meta) { - if ((<{permissions: FG.Permission[]}>record.meta).permissions) { - return (<{permissions: FG.Permission[]}>record.meta).permissions.every((permission: string) => { - return permissions.includes( - permission - ); - }) - } - } - }) - ) { - next(); - } else { - next({ name: 'login', query: { redirect: to.fullPath } }); - } - } else { - next(); - } - }) - .catch(error => { - console.exception(error); + let permissions: string[] = []; + if (user) { + user.roles.forEach(role => { + permissions = permissions.concat(role.permissions); }); + } + + if (to.name != 'login') { + if (!session || session.expires <= new Date()) { + store.dispatch('session/logout').catch(error => { + console.warn(error); + }); + return next({ name: 'login', query: { redirect: to.fullPath } }); + } + + if ( + to.matched.every((record: RouteRecord) => { + if (!('meta' in record) || !('permissions' in record.meta)) + return true; + if (record.meta) { + if ((<{ permissions: FG.Permission[] }>record.meta).permissions) { + return (<{ permissions: FG.Permission[] }>( + record.meta + )).permissions.every((permission: string) => { + return permissions.includes(permission); + }); + } + } + }) + ) { + next(); + } else { + next({ name: 'login', query: { redirect: to.fullPath } }); + } + } else { + next(); + } }); }); diff --git a/src/config.ts b/src/config.ts index 8df1729..90b3ca8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ const config = { -baseURL: '/api' + baseURL: '/api' }; export default config; diff --git a/src/flaschengeist.d.ts b/src/flaschengeist.d.ts index aaf068d..4203fde 100644 --- a/src/flaschengeist.d.ts +++ b/src/flaschengeist.d.ts @@ -1,57 +1,58 @@ declare namespace FG { - interface Session { - expires: Date; - token: string; - lifetime: number; - browser: string; - platform: string; - } - interface User { - userid: string; - display_name: string; - firstname: string; - lastname: string; - mail: string; - roles: Array; - } - type Permission = string; - interface Role { - name: string; - permissions: Array; - } - interface Transaction { - id: number; - time: Date; - amount: number; - sender_id: string; - receiver_id: string; - author_id: string; - } - interface Event { - id: number; - start: Date; - description?: any; - type: EventType; - slots: Array; - } - interface EventSlot { - id: number; - start: Date; - end?: any; - jobs: Array; - } - type EventType = string; - interface Job { - userid: string; - value: number; - } - interface JobSlot { - type: JobType; - users: Array; - required_jobs: number; - } - interface JobType { - id: number; - name: string; - } + interface Session { + expires: Date; + token: string; + lifetime: number; + browser: string; + platform: string; + userid: string; + } + interface User { + userid: string; + display_name: string; + firstname: string; + lastname: string; + mail: string; + roles: Array; + } + type Permission = string; + interface Role { + name: string; + permissions: Array; + } + interface Transaction { + id: number; + time: Date; + amount: number; + sender_id: string; + receiver_id: string; + author_id: string; + } + interface Event { + id: number; + start: Date; + description?: any; + type: EventType; + slots: Array; + } + interface EventSlot { + id: number; + start: Date; + end?: any; + jobs: Array; + } + type EventType = string; + interface Job { + userid: string; + value: number; + } + interface JobSlot { + type: JobType; + users: Array; + required_jobs: number; + } + interface JobType { + id: number; + name: string; + } } diff --git a/src/index.template.html b/src/index.template.html index 7eb371e..576b093 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -1,24 +1,34 @@ + + <%= productName %> - - <%= productName %> + + + + + - - - - - + + + + - - - - - - - -
- - - \ No newline at end of file + + +
+ + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 8142d39..2399539 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -14,7 +14,7 @@ - + Flaschengeist @@ -31,7 +31,7 @@ :permissions="shortcut.permissions" /> - + @@ -54,7 +54,7 @@ :permissions="link.permissions" /> - + @@ -81,7 +81,7 @@ /> - + - + @@ -102,11 +102,11 @@ diff --git a/src/pages/Login.vue b/src/pages/Login.vue index 5e33c75..c67012c 100644 --- a/src/pages/Login.vue +++ b/src/pages/Login.vue @@ -48,7 +48,7 @@ export default defineComponent({ function doLogin() { console.log(userid.value, password.value); ctx.root.$store - .dispatch('user/login', { + .dispatch('session/login', { userid: userid.value, password: password.value }) diff --git a/src/plugins.d.ts b/src/plugins.d.ts index 8db022b..3f92f4a 100644 --- a/src/plugins.d.ts +++ b/src/plugins.d.ts @@ -13,7 +13,7 @@ declare namespace FG_Plugin { title: string; icon: string; children?: PluginRouteConfig[]; - meta?: {permissions?: string[]} + meta?: { permissions?: string[] }; } interface Plugin { @@ -34,7 +34,7 @@ declare namespace FG_Plugin { title: string; link: string; icon: string; - permissions?: string[] + permissions?: string[]; } interface LoadedPlugin { diff --git a/src/plugins/balance/store/balance.ts b/src/plugins/balance/store/balance.ts index 5b7b49d..79ec232 100644 --- a/src/plugins/balance/store/balance.ts +++ b/src/plugins/balance/store/balance.ts @@ -38,7 +38,8 @@ const mutations: MutationTree = { const actions: ActionTree = { getBalance({ commit, rootState }) { axios - .get(`/users/${rootState.user.user.userid}/balance`) + /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */ + .get(`/users/${rootState.user.currentUser?.userid}/balance`) .then(({ data }: AxiosResponse) => { commit('setBalance', data.balance); commit('setCredit', data.credit); @@ -50,7 +51,8 @@ const actions: ActionTree = { }, getLimit({ rootState }) { axios - .get(`/users/${rootState.user.user.userid}/balance/limit`) + /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */ + .get(`/users/${rootState.user.currentUser?.userid}/balance/limit`) .then(({ data }) => { console.log(data); }) @@ -60,7 +62,10 @@ const actions: ActionTree = { }, changeBalance({ rootState, dispatch }, amount: number) { axios - .put(`/users/${rootState.user.user.userid}/balance`, <{ amount: number }>{ + /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */ + .put(`/users/${rootState.user.currentUser?.userid}/balance`, < + { amount: number } + >{ amount: amount }) .then(() => { diff --git a/src/plugins/user/components/settings/Main.vue b/src/plugins/user/components/settings/Main.vue index 1c2e7e8..607aa00 100644 --- a/src/plugins/user/components/settings/Main.vue +++ b/src/plugins/user/components/settings/Main.vue @@ -100,14 +100,12 @@ export default defineComponent({ setup(_, { root }) { const store = >root.$store; - const user = computed(() => { - return store.state.user.user; - }); + const user = computed(() => store.state.user.currentUser); - const firstname = ref(user.value.firstname); - const lastname = ref(user.value.lastname); - const mail = ref(user.value.mail); - const display_name = ref(user.value.display_name); + const firstname = ref(user.value?.firstname); + const lastname = ref(user.value?.lastname); + const mail = ref(user.value?.mail); + const display_name = ref(user.value?.display_name); const password = ref(''); const new_password = ref(''); diff --git a/src/plugins/user/components/settings/Sessions.vue b/src/plugins/user/components/settings/Sessions.vue index 06ebdef..d06a53a 100644 --- a/src/plugins/user/components/settings/Sessions.vue +++ b/src/plugins/user/components/settings/Sessions.vue @@ -76,12 +76,12 @@ export default defineComponent({ } function deleteSession(token: string) { - store.dispatch('sessions/deleteSession', token).catch(error => { + store.dispatch('session/deleteSession', token).catch(error => { console.warn(error); }); } function isThisSession(token: string) { - return store.state.user.session.token == token; + return store.state.session.currentSession?.token === token; } return { diff --git a/src/plugins/user/models.ts b/src/plugins/user/models.ts index 64149db..c743f7d 100644 --- a/src/plugins/user/models.ts +++ b/src/plugins/user/models.ts @@ -2,3 +2,8 @@ export interface LoginData { userid: string; password: string; } + +export interface LoginResponse { + user: FG.User; + session: FG.Session; +} diff --git a/src/plugins/user/pages/MainPage.vue b/src/plugins/user/pages/MainPage.vue index 1560720..c868326 100644 --- a/src/plugins/user/pages/MainPage.vue +++ b/src/plugins/user/pages/MainPage.vue @@ -34,7 +34,7 @@ export default defineComponent({ const checkMain = computed(() => { return root.$route.matched.length == 2; }); - return { checkMain, mainRoutes}; + return { checkMain, mainRoutes }; } }); diff --git a/src/plugins/user/pages/Settings.vue b/src/plugins/user/pages/Settings.vue index acd40ba..6fc4106 100644 --- a/src/plugins/user/pages/Settings.vue +++ b/src/plugins/user/pages/Settings.vue @@ -43,11 +43,11 @@ export default defineComponent({ const store = >root.$store; onBeforeMount(() => { - store.dispatch('sessions/getSessions').catch(error => { + store.dispatch('session/getSessions').catch(error => { console.warn(error); }); }); - const sessions = computed(() => store.state.sessions.sessions); + const sessions = computed(() => store.state.session.sessions); function showRootGetters() { console.log(sessions.value); @@ -55,7 +55,7 @@ export default defineComponent({ const sessionsLoading = computed( () => - store.state.sessions.loading || + store.state.session.loading || store.state.user.getUserLoading || store.state.user.updateUserLoading ); diff --git a/src/plugins/user/pages/User.vue b/src/plugins/user/pages/User.vue index d51d87e..1dde46e 100644 --- a/src/plugins/user/pages/User.vue +++ b/src/plugins/user/pages/User.vue @@ -27,9 +27,9 @@ export default defineComponent({ setup(_, { root }) { const store = >root.$store; - const userObj = computed(() => store.state.user.user); + const userObj = computed(() => store.state.user.currentUser); - const sessionObj = computed(() => store.state.user.session); + const sessionObj = computed(() => store.state.session.currentSession); return { userObj, sessionObj }; } diff --git a/src/plugins/user/plugin.ts b/src/plugins/user/plugin.ts index 877d8c2..7bf40e3 100644 --- a/src/plugins/user/plugin.ts +++ b/src/plugins/user/plugin.ts @@ -12,7 +12,7 @@ const plugin: FG_Plugin.Plugin = { version: '0.0.1', store: new Map>([ ['user', userStore], - ['sessions', sessionsStore] + ['session', sessionsStore] ]) }; diff --git a/src/plugins/user/store/session.ts b/src/plugins/user/store/session.ts index 1727b3a..d192bef 100644 --- a/src/plugins/user/store/session.ts +++ b/src/plugins/user/store/session.ts @@ -1,20 +1,33 @@ import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'; +import { LoginData, LoginResponse } from 'src/plugins/user/models'; import { StateInterface } from 'src/store'; import { axios } from 'src/boot/axios'; import { AxiosResponse } from 'axios'; import { Router } from 'src/router'; +import { LocalStorage, Loading } from 'quasar'; export interface SessionInterface { + currentSession?: FG.Session; sessions: FG.Session[]; loading: boolean; } const state: SessionInterface = { sessions: [], + currentSession: + LocalStorage.getItem('currentSession') || undefined, loading: false }; const mutations: MutationTree = { + setCurrentSession(state, session: FG.Session) { + LocalStorage.set('currentSession', session); + state.currentSession = session; + }, + clearCurrentSession(state) { + LocalStorage.remove('currentSession'); + state.currentSession = undefined; + }, setSessions(state, sessions: FG.Session[]) { state.sessions = sessions; }, @@ -24,40 +37,75 @@ const mutations: MutationTree = { }; const actions: ActionTree = { - getSessions({ commit, rootState, dispatch }) { - commit('setLoading', true); - axios - .get('/auth') - .then((response: AxiosResponse) => { - console.log(response.data); - response.data.forEach(session => { - session.expires = new Date(session.expires); - }); - commit('setSessions', response.data); - const currentSession = response.data.find((session: FG.Session) => { - return session.token === rootState.user.session.token; - }); - if (currentSession) { - void dispatch('user/setSession', currentSession, { root: true }); - } + login({ commit }, data: LoginData) { + Loading.show({ + message: 'Du wirst angemeldet' + }); + void axios + .post('/auth', data) + .then((response: AxiosResponse) => { + response.data.session.expires = new Date(response.data.session.expires); + commit('setCurrentSession', response.data.session); + commit('user/setCurrentUser', response.data.user, { root: true }); + void Router.push({ name: 'user-main' }); }) .catch(error => { console.exception(error); }) .finally(() => { - commit('setLoading', false); + Loading.hide(); }); }, - deleteSession({ commit, dispatch, rootState }, token: string) { + /** + * Logout from current session + */ + logout({ dispatch, rootState }) { + Loading.show({ message: 'Session wird abgemeldet' }); + dispatch('deleteSession', rootState.session.currentSession?.token).finally( + () => { + Loading.hide(); + } + ); + }, + /** + * Delete a given session + */ + deleteSession({ commit, rootState }, token: string | null) { + if (token === null) return; + commit('setLoading', true); axios .delete(`/auth/${token}`) .then(() => { - if (token === rootState.user.session.token) { - void dispatch('user/setSession', null, { root: true }); - Router.go(0); + if (token === rootState.session.currentSession?.token) { + commit('clearCurrentSession'); + commit('user/clearCurrentUser', null, { root: true }); + void Router.push({ name: 'login' }); } else { - void dispatch('getSessions'); + commit('getSessions'); + } + }) + .finally(() => { + commit('setLoading', false); + }); + }, + /** + * Get all sessions from current User + */ + getSessions({ commit, state, dispatch }) { + commit('setLoading', true); + axios + .get('/auth') + .then((response: AxiosResponse) => { + response.data.forEach(session => { + session.expires = new Date(session.expires); + }); + commit('setSessions', response.data); + const currentSession = response.data.find((session: FG.Session) => { + return session.token === state.currentSession?.token; + }); + if (currentSession) { + void dispatch('setCurrentSession', currentSession); } }) .catch(error => { @@ -70,6 +118,9 @@ const actions: ActionTree = { }; const getters: GetterTree = { + currentSession(state) { + return state.currentSession; + }, sessions(state) { return state.sessions; }, diff --git a/src/plugins/user/store/user.ts b/src/plugins/user/store/user.ts index c92a902..b4efbe5 100644 --- a/src/plugins/user/store/user.ts +++ b/src/plugins/user/store/user.ts @@ -1,131 +1,81 @@ import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'; import { StateInterface } from 'src/store'; import { axios } from 'boot/axios'; -import { LoginData } from 'src/plugins/user/models'; import { AxiosResponse } from 'axios'; -import { LocalStorage, Loading } from 'quasar'; -import { Router } from 'src/router'; +import { SessionStorage } from 'quasar'; -export interface UserStateInterface extends LoginResponse { +export interface UserStateInterface { updateUserLoading: boolean; getUserLoading: boolean; + currentUser?: FG.User; + users: FG.User[]; } -export interface LoginResponse { - user: FG.User; - session: FG.Session; -} - -const empty_session: FG.Session = { - browser: '', - expires: new Date(), - lifetime: -1, - platform: '', - token: '' -}; - -const empty_user: FG.User = { - display_name: '', - firstname: '', - lastname: '', - mail: '', - roles: [], - userid: '' -}; - const state: UserStateInterface = { - user: empty_user, - session: empty_session, + users: [], + currentUser: SessionStorage.getItem('currentUser') || undefined, updateUserLoading: false, getUserLoading: false }; const mutations: MutationTree = { - setUser(state, data: FG.User) { - state.user = data; + setCurrentUser(state, data: FG.User) { + SessionStorage.set('currentUser', data); + state.currentUser = data; }, - setSession(state, data: FG.Session) { - state.session = data; + clearCurrentUser(state) { + SessionStorage.remove('currentUser'); + state.currentUser = undefined; + }, + setUsers(state, data: FG.User[]) { + state.users = data; }, setLoading( state, data: { key: 'updateUserLoading' | 'getUserLoading'; data: boolean } ) { state[data.key] = data.data; - }, - showState(state) { - console.log(state); } }; const actions: ActionTree = { - login({ commit }, data: LoginData) { - Loading.show({ - message: 'Du wirst eingeloggt' - }); - void axios - .post('/auth', data) - .then((response: AxiosResponse) => { - response.data.session.expires = new Date(response.data.session.expires); - commit('setUser', response.data.user); - commit('setSession', response.data.session); - commit('showState'); - LocalStorage.set('user', response.data.user); - LocalStorage.set('session', response.data.session); - - void Router.push({ name: 'user-main' }); - }) - .catch(error => { - console.exception(error); - }) - .finally(() => { - Loading.hide(); - }); + getCurrentUser({ commit, rootState }) { + if (rootState.session.currentSession) { + commit('setLoading', { key: 'getUserLoading', data: true }); + axios + .get(`/users/${rootState.session.currentSession.userid}`) + .then((response: AxiosResponse) => { + commit('setCurrentUser', response.data); + }) + .catch(err => { + console.warn(err); + }) + .finally(() => { + commit('setLoading', { key: 'getUserLoading', data: false }); + }); + } else { + console.debug('User not logged in, can not get current_user.'); + } }, - doLogout({ commit }, token: string) { - Loading.show({ message: 'Du wirst ausgeloggt' }); - void axios - .delete(`/auth/${token}`) - .then(() => { - commit('setUser', empty_user); - commit('setSession', empty_session); - }) - .finally(() => { - LocalStorage.remove('user'); - LocalStorage.remove('session'); - Loading.hide(); - }); - }, - - logout({ dispatch }, token: string) { - dispatch('doLogout', token).finally(() => { - void Router.push({ name: 'login' }); - }); - }, - - getUser({ commit, state }) { - commit('setLoading', { key: 'getUserLoading', data: true }); + getUsers({ commit }) { axios - .get(`/users/${state.user.userid}`) - .then((response: AxiosResponse) => { - commit('setUser', response.data); - LocalStorage.set('user', response.data); + .get(`/users`) + .then((response: AxiosResponse) => { + commit('setUsers', response.data); }) .catch(err => { console.warn(err); - }) - .finally(() => { - commit('setLoading', { key: 'getUserLoading', data: false }); }); }, updateUser({ commit, state, dispatch }, data) { commit('setLoading', { key: 'updateUserLoading', data: true }); + if (!state.currentUser) throw 'Not logged in'; axios - .put(`/users/${state.user.userid}`, data) + .put(`/users/${state.currentUser.userid}`, data) .then(() => { - void dispatch('getUser'); + void dispatch('getCurrentUser'); }) .catch(error => { console.log(error); @@ -133,46 +83,30 @@ const actions: ActionTree = { .finally(() => { commit('setLoading', { key: 'updateUserLoading', data: false }); }); - }, - - loadFromLocalStorage({ commit }) { - let data = LocalStorage.getItem('user'); - commit('setUser', data ? data : empty_user); - data = LocalStorage.getItem('session'); - commit('setSession', data ? data : empty_session); - commit('showState'); - }, - - setSession({ commit }, session: FG.Session) { - if (session) { - commit('setSession', session); - LocalStorage.set('session', session); - } else { - commit('setSession', empty_session); - LocalStorage.remove('session'); - } } }; const getters: GetterTree = { - user({ user }) { - return user; + currentUser({ currentUser }) { + return currentUser; }, - displayName({ user }) { - return user.display_name; - }, - session({ session }) { - return session; + users({ users }) { + return users; }, loading({ updateUserLoading, getUserLoading }) { return updateUserLoading || getUserLoading; }, - permissions({user}) { - let permissions: string[] = [] - user.roles.forEach(role => { - permissions = permissions.concat(role.permissions); - }); - return permissions + displayName({ currentUser }) { + return currentUser?.display_name; + }, + permissions({ currentUser }) { + let permissions: string[] = []; + if (currentUser) { + currentUser.roles.forEach(role => { + permissions = permissions.concat(role.permissions); + }); + } + return permissions; } }; diff --git a/src/router/routes.ts b/src/router/routes.ts index cda0866..666df2f 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -26,7 +26,7 @@ const routes: RouteConfig[] = [ { name: 'about', path: 'about', - meta: { 'permission': 'user' }, + meta: { permission: 'user' }, component: () => import('pages/about/About.vue') } ] diff --git a/src/store/index.ts b/src/store/index.ts index 51e99bc..1d59a5a 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -9,7 +9,7 @@ import { UserStateInterface } from 'src/plugins/user/store/user'; */ export interface StateInterface { user: UserStateInterface; - sessions: SessionInterface; + session: SessionInterface; } export default store(function({ Vue }) { diff --git a/src/store/store-flag.d.ts b/src/store/store-flag.d.ts index ec274bd..af80dbe 100644 --- a/src/store/store-flag.d.ts +++ b/src/store/store-flag.d.ts @@ -1,8 +1,8 @@ // THIS FEATURE-FLAG FILE IS AUTOGENERATED, // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING -import "quasar/dist/types/feature-flag"; +import 'quasar/dist/types/feature-flag'; -declare module "quasar/dist/types/feature-flag" { +declare module 'quasar/dist/types/feature-flag' { interface QuasarFeatureFlags { store: true; }