From e2568a1bbe184daa59d8824987da405ad93f4c7f Mon Sep 17 00:00:00 2001 From: Torben Indorf Date: Sun, 9 Feb 2020 09:48:43 +0100 Subject: [PATCH] started the authentication basic routing --- .env.development | 4 ++ .eslintrc.js | 1 - package-lock.json | 22 ++++++++++- package.json | 6 ++- src/api/AuthRepository.ts | 24 ++++++++++++ src/api/RepositoryFactory.ts | 18 +++++++++ src/helpers/AuthGuard.ts | 18 +++++++++ src/main.ts | 2 +- src/router/index.ts | 66 ++++++++++++++++++++++---------- src/store/auth.store.ts | 50 ++++++++++++++++++++++++ src/store/index.ts | 11 ------ src/store/store.ts | 30 +++++++++++++++ src/views/About.vue | 5 --- src/views/AreaSelection.view.vue | 23 +++++++++++ src/views/Home.view.vue | 18 +++++++++ src/views/Home.vue | 14 ------- src/views/Login.view.vue | 39 +++++++++++++++++++ src/views/MapOverview.view.vue | 17 ++++++++ tsconfig.json | 19 +++++++-- 19 files changed, 326 insertions(+), 61 deletions(-) create mode 100644 .env.development create mode 100644 src/api/AuthRepository.ts create mode 100644 src/api/RepositoryFactory.ts create mode 100644 src/helpers/AuthGuard.ts create mode 100644 src/store/auth.store.ts delete mode 100644 src/store/index.ts create mode 100644 src/store/store.ts delete mode 100644 src/views/About.vue create mode 100644 src/views/AreaSelection.view.vue create mode 100644 src/views/Home.view.vue delete mode 100644 src/views/Home.vue create mode 100644 src/views/Login.view.vue create mode 100644 src/views/MapOverview.view.vue diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..3f6a77b --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +VUE_APP_TITLE=GelegeApp - [DEV] + +VUE_APP_BASE_URL= http://localhost:1337/ + diff --git a/.eslintrc.js b/.eslintrc.js index b46f91e..b96c291 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,6 @@ module.exports = { rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'comma-dangle': ['error', 'never'] }, parserOptions: { parser: '@typescript-eslint/parser' diff --git a/package-lock.json b/package-lock.json index 6901264..d211a1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5417,8 +5417,7 @@ "flatted": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" }, "flush-write-stream": { "version": "1.1.1", @@ -7517,6 +7516,11 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "lodash.transform": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", @@ -11661,6 +11665,20 @@ "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz", "integrity": "sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ==" }, + "vuex-class": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/vuex-class/-/vuex-class-0.3.2.tgz", + "integrity": "sha512-m0w7/FMsNcwJgunJeM+wcNaHzK2KX1K1rw2WUQf7Q16ndXHo7pflRyOV/E8795JO/7fstyjH3EgqBI4h4n4qXQ==" + }, + "vuex-persist": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vuex-persist/-/vuex-persist-2.2.0.tgz", + "integrity": "sha512-o/qbBeMcKZZqMvCXc7kfIew/5cjHxlP1f53rx5YYp3r2tk2kxXYK/UZumxKn7OXywlurl2r0mgkuBzH6nIWFjw==", + "requires": { + "flatted": "^2.0.0", + "lodash.merge": "^4.6.2" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index fa00701..49e935d 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "vue": "^2.6.10", "vue-class-component": "^7.0.2", "vue-property-decorator": "^8.3.0", - "vue-router": "^3.1.3", - "vuex": "^3.1.2" + "vue-router": "^3.1.5", + "vuex": "^3.1.2", + "vuex-class": "^0.3.2", + "vuex-persist": "^2.2.0" }, "devDependencies": { "@vue/cli-plugin-babel": "^4.1.0", diff --git a/src/api/AuthRepository.ts b/src/api/AuthRepository.ts new file mode 100644 index 0000000..c54642e --- /dev/null +++ b/src/api/AuthRepository.ts @@ -0,0 +1,24 @@ +import axios, {AxiosInstance} from 'axios'; + +/** + * Auth repository that provides authentication api methods + */ +export default class AuthRepository { + + protected axiosClient!: AxiosInstance; + private baseURL = process.env.VUE_APP_BASE_URL; + + constructor() { + this.createHttpClient(); + } + + private createHttpClient() { + this.axiosClient = axios.create({ + baseURL: `${this.baseURL}` + }); + } + + public async login(payload: { username: string, password: string }): Promise { + return this.axiosClient.post('auth/loginUser', payload); + } +} diff --git a/src/api/RepositoryFactory.ts b/src/api/RepositoryFactory.ts new file mode 100644 index 0000000..2b1ab89 --- /dev/null +++ b/src/api/RepositoryFactory.ts @@ -0,0 +1,18 @@ +import AuthRepository from '@/api/AuthRepository'; + +const repositories: any = { + auth: { Repository: AuthRepository, instance: null } +}; + +/** + * Factory to create repositories. All created repositories are singleton instances. + */ +export const RepositoryFactory = { + get: (name: string) => { + if (repositories[name].instance) { + return repositories[name].instance; + } else { + return new repositories[name].Repository(); + } + } +}; diff --git a/src/helpers/AuthGuard.ts b/src/helpers/AuthGuard.ts new file mode 100644 index 0000000..c79fa54 --- /dev/null +++ b/src/helpers/AuthGuard.ts @@ -0,0 +1,18 @@ +import store from '@/store/store' + +export default class AuthGuard { + /** + * Checks if the token is available. If no token is set, redirect user to login_view. + * @param to + * @param from + * @param next + * @param permission + */ + public static async checkRouteAccess(to: any, from: any, next: any) { + if (store.getters['auth/token']) { + next(); + } else { + next({name: 'login'}); + } + } +} diff --git a/src/main.ts b/src/main.ts index c61cd30..01611e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import Vue from 'vue'; import Ionic, { IonicVueRouter } from '@ionic/vue'; import App from './App.vue'; import router from './router'; -import store from './store'; +import store from './store/store'; import '@ionic/core/css/core.css'; import '@ionic/core/css/ionic.bundle.css'; diff --git a/src/router/index.ts b/src/router/index.ts index 5861bfe..c172095 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,30 +1,54 @@ import Vue from 'vue'; -import VueRouter from 'vue-router'; -import HelloWorld from '../components/HelloWorld.vue'; -import { IonicVueRouter } from '@ionic/vue'; +import {IonicVueRouter} from '@ionic/vue'; +import LoginView from "@/views/Login.view.vue"; +import HomeView from "@/views/Home.view.vue"; +import AreaSelectionView from "@/views/AreaSelection.view.vue"; +import MapOverviewView from "@/views/MapOverview.view.vue"; +import AuthGuard from "@/helpers/AuthGuard"; +import Router from 'vue-router'; Vue.use(IonicVueRouter); -const routes = [ - { - path: '/', - name: 'home', - component: HelloWorld - }, - { - path: '/about', - name: 'about', - // route level code-splitting - // this generates a separate chunk (about.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') +// Handle NavigationDuplicated stack error: +// https://github.com/vuejs/vue-router/issues/2881#issuecomment-520554378 +const originalPush = Router.prototype.push; +// @ts-ignore +IonicVueRouter.prototype.push = function push(location, onResolve, onReject) { + if (onResolve || onReject) { + return originalPush.call(this, location, onResolve, onReject); } -]; +// @ts-ignore + return originalPush.call(this, location).catch((err) => err); +}; -const router = new IonicVueRouter({ +export default new IonicVueRouter({ mode: 'history', base: process.env.BASE_URL, - routes + routes: [ + { + path: '/login', + name: 'login', + component: LoginView + }, + { + path: '/home', + component: HomeView, + beforeEnter: (to: any, from: any, next: any) => AuthGuard.checkRouteAccess(to, from, next), + children: [ + { + path: 'areaSelection', + name: 'areaSelection', + component: AreaSelectionView + }, { + path: 'mapOverview', + name: 'mapOverview', + component: MapOverviewView + } + ] + }, + { + path: '*', + redirect: {name: 'login'}, + } + ] }); - -export default router; diff --git a/src/store/auth.store.ts b/src/store/auth.store.ts new file mode 100644 index 0000000..17352dc --- /dev/null +++ b/src/store/auth.store.ts @@ -0,0 +1,50 @@ +import { ActionTree, Module } from 'vuex'; +import { RepositoryFactory } from '@/api/RepositoryFactory'; +import AuthRepository from '@/api/AuthRepository'; + +const authRepository: AuthRepository = RepositoryFactory.get('auth'); + +const store = { + token: undefined, + isAdmin: false, + lastLogin: undefined +}; + +const actions: ActionTree = { + loginAction: async ({ commit }, payload: { username: string, password: string }): Promise => { + const response = await authRepository.login(payload); + + if (response && response.data.token) { + commit('saveToken', response.data.token); + } + + return response; + }, +}; + +const mutations = { + saveToken: (state: any, token: any) => { + state.token = token; + state.lastLogin = new Date(); + }, + clearToken: (state: any) => { + state.token = null; + state.lastLogin = null; + }, + saveAdminPermission: (state: any, isAdmin: boolean) => state.isAdmin = isAdmin +}; + +const getters = { + token: (state: any) => state.token, + lastLogin: (state: any) => state.lastLogin, + isAdmin: (state: any) => state.isAdmin +}; + +const authStore: Module = { + state: store, + actions, + mutations, + getters +}; + +export default authStore; diff --git a/src/store/index.ts b/src/store/index.ts deleted file mode 100644 index 08e6a5b..0000000 --- a/src/store/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; - -Vue.use(Vuex); - -export default new Vuex.Store({ - state: {}, - mutations: {}, - actions: {}, - modules: {} -}); diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..dcc5a72 --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,30 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import VuexPersistence from 'vuex-persist'; +import authStore from "@/store/auth.store"; + +Vue.use(Vuex); +// set up persistence storage plugin +const vuexPersistence = new VuexPersistence({ + key: process.env.VUE_APP_TITLE, + storage: window.localStorage, + reducer: (state: any) => ({ + auth: { + token: state.auth.token, + user: state.auth.user + } + }), + filter: (mutation) => ['auth/saveLogin'].includes(mutation.type) +}); + +export default new Vuex.Store({ + state: {}, + mutations: {}, + actions: {}, + modules: { + auth: { + namespaced: true, + ...authStore + } + } +}); diff --git a/src/views/About.vue b/src/views/About.vue deleted file mode 100644 index e41a05d..0000000 --- a/src/views/About.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/views/AreaSelection.view.vue b/src/views/AreaSelection.view.vue new file mode 100644 index 0000000..5c1ab76 --- /dev/null +++ b/src/views/AreaSelection.view.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/views/Home.view.vue b/src/views/Home.view.vue new file mode 100644 index 0000000..4995c1a --- /dev/null +++ b/src/views/Home.view.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/views/Home.vue b/src/views/Home.vue deleted file mode 100644 index 9028115..0000000 --- a/src/views/Home.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/src/views/Login.view.vue b/src/views/Login.view.vue new file mode 100644 index 0000000..be4331e --- /dev/null +++ b/src/views/Login.view.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/views/MapOverview.view.vue b/src/views/MapOverview.view.vue new file mode 100644 index 0000000..1e71e33 --- /dev/null +++ b/src/views/MapOverview.view.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json index 58faef5..27e8e4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,11 +12,20 @@ "sourceMap": true, "resolveJsonModule": true, "baseUrl": ".", - "types": ["webpack-env"], + "types": [ + "webpack-env" + ], "paths": { - "@/*": ["src/*"] + "@/*": [ + "src/*" + ] }, - "lib": ["esnext", "dom", "dom.iterable", "scripthost"] + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] }, "include": [ "src/**/*.ts", @@ -25,5 +34,7 @@ "tests/**/*.ts", "tests/**/*.tsx" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] }