added login stuff
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Pünkösd Marcell 2020-12-06 01:43:47 +01:00
parent 0a09a1b0b7
commit 633f2af5eb
11 changed files with 255 additions and 33 deletions

1
.env.development Normal file
View File

@ -0,0 +1 @@
VUE_APP_API_LOCATION=https://videon.k8s.kmlabz.com/api/

1
.env.production Normal file
View File

@ -0,0 +1 @@
VUE_APP_API_LOCATION=https://videon.k8s.kmlabz.com/api/

11
package-lock.json generated
View File

@ -2490,6 +2490,14 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
}, },
"axios": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"babel-eslint": { "babel-eslint": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@ -5475,8 +5483,7 @@
"follow-redirects": { "follow-redirects": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
"dev": true
}, },
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",

View File

@ -8,6 +8,7 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"node-sass": "^5.0.0", "node-sass": "^5.0.0",

View File

@ -1,44 +1,66 @@
<template> <template>
<div> <div>
<form novalidate @submit.prevent="performLogin">
<form novalidate @submit.prevent="performLogin" @input="loginCredsChanged">
<md-card-content> <md-card-content>
<md-content class="text-alert" v-if="invalidLogin">Wrong username or password</md-content>
<md-field> <md-field>
<label>Username</label> <label>Username</label>
<md-input :disabled="processing"></md-input> <md-input :disabled="authInProgress" v-model="creds.name"></md-input>
</md-field> </md-field>
<md-field> <md-field>
<label>Password</label> <label>Password</label>
<md-input :disabled="processing" type="password"></md-input> <md-input :disabled="authInProgress" v-model="creds.password" type="password"></md-input>
</md-field> </md-field>
</md-card-content> </md-card-content>
<md-card-actions> <md-card-actions>
<md-progress-spinner :md-diameter="30" :md-stroke="3" md-mode="indeterminate" v-if="processing" <md-progress-spinner :md-diameter="30" :md-stroke="3" md-mode="indeterminate" v-if="authInProgress"
class="md-accent"></md-progress-spinner> class="md-accent"></md-progress-spinner>
<md-button type="submit" class="md-primary" :disabled="processing">Login</md-button> <md-button type="submit" class="md-primary" :disabled="authInProgress">Login</md-button>
</md-card-actions> </md-card-actions>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
export default { export default {
name: "Login", name: "Login",
data() { data() {
return { return {
processing: false creds: {
name: "",
password: ""
},
invalidLogin: false
} }
}, },
methods: { methods: {
performLogin() { performLogin() {
this.processing = true; this.$store.dispatch("performLogin", this.creds).then(() => {
this.$router.push({name: 'Dashboard'});
}).catch(() => {
this.invalidLogin = true;
})
},
loginCredsChanged() {
this.invalidLogin = false;
} }
},
computed: {
...mapGetters(['authInProgress'])
} }
} }
</script> </script>
<style scoped> <style scoped>
.text-alert {
color: red;
font-weight: bold;
}
</style> </style>

View File

@ -5,41 +5,56 @@
<md-field> <md-field>
<label>Username</label> <label>Username</label>
<md-input :disabled="processing"></md-input> <md-input v-model="form.name" :disabled="authInProgress"></md-input>
</md-field> </md-field>
<md-field :md-toggle-password="false"> <md-field :md-toggle-password="false">
<label>Password</label> <label>Password</label>
<md-input type="password" :disabled="processing"></md-input> <md-input v-model="form.password" type="password" :disabled="authInProgress"></md-input>
</md-field> </md-field>
<md-field :md-toggle-password="false"> <md-field :md-toggle-password="false">
<label>Confirm password</label> <label>Confirm password</label>
<md-input :disabled="processing" type="password"></md-input> <md-input v-model="form.passwordConfirm" :disabled="authInProgress" type="password"></md-input>
</md-field> </md-field>
</md-card-content> </md-card-content>
<md-card-actions> <md-card-actions>
<md-progress-spinner :md-diameter="30" :md-stroke="3" md-mode="indeterminate" v-if="processing" <md-progress-spinner :md-diameter="30" :md-stroke="3" md-mode="indeterminate" v-if="authInProgress"
class="md-accent"></md-progress-spinner> class="md-accent"></md-progress-spinner>
<md-button type="submit" class="md-primary" :disabled="processing">Register</md-button> <md-button type="submit" class="md-primary" :disabled="authInProgress && passwordGood">Register</md-button>
</md-card-actions> </md-card-actions>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
import {mapGetters} from "vuex";
export default { export default {
name: "Register", name: "Register",
data() { data() {
return { return {
processing: false form: {
name: "",
password: "",
passwordConfirm: ""
}
} }
}, },
methods: { methods: {
performRegister() { performRegister() {
this.processing = true; const creds = {name: this.form.name, password: this.form.password};
this.$store.dispatch("performRegister", creds).then(() => {
this.$router.push({name: 'Dashboard'});
})
}
},
computed: {
...mapGetters(['authInProgress']),
passwordGood() {
return this.form.password !== "" && this.form.password === this.form.passwordConfirm;
} }
} }
} }

View File

@ -13,7 +13,7 @@
</md-list-item> </md-list-item>
<md-list-item> <md-list-item>
<md-button class="md-raised md-accent">Logout</md-button> <md-button class="md-raised md-accent" @click="performLogout">Logout</md-button>
</md-list-item> </md-list-item>
</md-list> </md-list>
@ -23,7 +23,14 @@
<script> <script>
export default { export default {
name: "WorkspaceDrawerContent" name: "WorkspaceDrawerContent",
methods: {
performLogout() {
this.$store.dispatch("performLogout").then(() => {
this.$router.push({name: "Welcome"})
})
}
}
} }
</script> </script>

View File

@ -1,4 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import axios from 'axios'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import store from './store' import store from './store'
@ -39,9 +41,29 @@ Vue.use(MdRipple);
Vue.use(MdDialog); Vue.use(MdDialog);
Vue.use(MdSpeedDial); Vue.use(MdSpeedDial);
Vue.prototype.$api = axios.create({
baseURL: process.env.VUE_APP_API_LOCATION
});
new Vue({ new Vue({
router, router,
store, store,
render: h => h(App) render: h => h(App),
created() {
this.$api.interceptors.response.use(undefined, function (err) {
return new Promise(function (resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
this.$store.dispatch("performLogout").then(() => {
this.$router.push({name: 'Welcome'});
});
}
reject(err);
});
});
this.$api.interceptors.request.use((config) => {
if (this.$store && this.$store.getters.isLoggedIn) {
config.headers["Authorization"] = "Bearer " + this.$store.state.auth.token;
}
})
}
}).$mount('#app') }).$mount('#app')

View File

@ -3,18 +3,30 @@ import VueRouter from 'vue-router'
import Dashboard from '@/views/Dashboard' import Dashboard from '@/views/Dashboard'
import Welcome from "@/views/Welcome"; import Welcome from "@/views/Welcome";
Vue.use(VueRouter) import store from '@/store'
Vue.use(VueRouter);
const routes = [ const routes = [
{ {
path: '/', path: '/welcome',
name: 'Welcome', name: 'Welcome',
component: Welcome component: Welcome,
meta: {
allowVisit(loggedin) {
return !loggedin;
}
}
}, },
{ {
path: '/dashboard', path: '/',
name: 'Dashboard', name: 'Dashboard',
component: Dashboard component: Dashboard,
meta: {
allowVisit(loggedin) {
return loggedin;
}
}
}, },
{ {
path: '/about', path: '/about',
@ -22,7 +34,12 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue') component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue'),
meta: {
allowVisit() {
return true;
}
}
} }
] ]
@ -32,4 +49,25 @@ const router = new VueRouter({
routes routes
}) })
router.beforeEach((to, from, next) => {
const loggedin = store.getters.isLoggedIn;
const visitAllowed = to.matched.some(record => record.meta.allowVisit(loggedin))
if (visitAllowed) {
next();
return;
}
if (loggedin) {
next({name: 'Dashboard'})
} else {
next({name: 'Welcome'})
}
})
export default router export default router

99
src/store/auth.js Normal file
View File

@ -0,0 +1,99 @@
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
const JWT_KEY_NAME = "JWT";
const baseURL = process.env.VUE_APP_API_LOCATION;
Vue.use(Vuex)
export default {
state() {
return {
auth: {
processing: false,
token: localStorage.getItem(JWT_KEY_NAME) || '',
name: ''
}
}
},
mutations: {
auth_started(state) {
state.auth.processing = true;
},
auth_success(state, token, name) {
state.auth.processing = false;
state.auth.token = token;
state.auth.name = name;
},
auth_fail(state) {
state.auth.processing = false;
state.auth.token = '';
state.auth.name = '';
},
logout(state) {
state.auth.token = '';
state.auth.name = '';
}
},
actions: {
performLogin({commit}, creds) {
return new Promise((resolve, reject) => {
commit('auth_started')
axios.post("auth/login", creds, {baseURL}).then(resp => {
const token = resp.data.token;
if (!token) {
return reject();
}
const user = creds.name;
localStorage.setItem(JWT_KEY_NAME, token)
commit('auth_success', token, user)
return resolve(resp);
}).catch(err => {
commit('auth_fail')
localStorage.removeItem(JWT_KEY_NAME)
return reject(err);
})
})
},
performRegister({commit}, creds) {
return new Promise((resolve, reject) => {
commit('auth_started')
axios.post("auth/signup", creds, {baseURL}).then(resp => {
const token = resp.data.token;
if (!token) {
return reject();
}
const user = creds.name;
localStorage.setItem(JWT_KEY_NAME, token)
commit('auth_success', token, user)
resolve(resp)
}).catch(err => {
commit('auth_fail')
localStorage.removeItem(JWT_KEY_NAME)
reject(err)
})
})
},
performLogout({commit}) {
return new Promise((resolve) => {
localStorage.removeItem(JWT_KEY_NAME)
commit('logout')
resolve();
});
}
},
modules: {},
getters: {
isLoggedIn(state) {
return !!state.auth.token;
},
authInProgress(state) {
return state.auth.processing;
}
}
}

View File

@ -1,15 +1,24 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import auth from "@/store/auth";
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
}, },
mutations: { mutations: {
}, },
actions: { actions: {
}, },
modules: { modules: {
auth
},
getters: {
} }
}) })