implemented token based login
This commit is contained in:
59
package-lock.json
generated
59
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"bootstrap-icons": "^1.6.0",
|
"bootstrap-icons": "^1.6.0",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"pinia": "^2.0.0-rc.13",
|
"pinia": "^2.0.0-rc.13",
|
||||||
|
"pinia-plugin-persist": "^0.0.92",
|
||||||
"vue": "^3.2.20",
|
"vue": "^3.2.20",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
"vue-tsc": "^0.3.0"
|
"vue-tsc": "^0.3.0"
|
||||||
@@ -1125,6 +1126,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia-plugin-persist": {
|
||||||
|
"version": "0.0.92",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia-plugin-persist/-/pinia-plugin-persist-0.0.92.tgz",
|
||||||
|
"integrity": "sha512-eBNv1mqWwtiRPg4lraHuhXTqsPt51tRT2yaukW8+Gj9NIbLuPKS/VhnwmTF46WpIMJ0OP5xC4cwz9JOKJGX3GQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": "^0.11.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0",
|
||||||
|
"vue": "^2.0.0 || >=3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia-plugin-persist/node_modules/vue-demi": {
|
||||||
|
"version": "0.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||||
|
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pinia/node_modules/vue-demi": {
|
"node_modules/pinia/node_modules/vue-demi": {
|
||||||
"version": "0.11.4",
|
"version": "0.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||||
@@ -2478,6 +2521,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pinia-plugin-persist": {
|
||||||
|
"version": "0.0.92",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia-plugin-persist/-/pinia-plugin-persist-0.0.92.tgz",
|
||||||
|
"integrity": "sha512-eBNv1mqWwtiRPg4lraHuhXTqsPt51tRT2yaukW8+Gj9NIbLuPKS/VhnwmTF46WpIMJ0OP5xC4cwz9JOKJGX3GQ==",
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "^0.11.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||||
|
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.3.9",
|
"version": "8.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"bootstrap-icons": "^1.6.0",
|
"bootstrap-icons": "^1.6.0",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"pinia": "^2.0.0-rc.13",
|
"pinia": "^2.0.0-rc.13",
|
||||||
|
"pinia-plugin-persist": "^0.0.92",
|
||||||
"vue": "^3.2.20",
|
"vue": "^3.2.20",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
"vue-tsc": "^0.3.0"
|
"vue-tsc": "^0.3.0"
|
||||||
|
|||||||
17
src/App.vue
17
src/App.vue
@@ -4,9 +4,10 @@
|
|||||||
<Notification />
|
<Notification />
|
||||||
<div class="container shadow p-3 text-center">
|
<div class="container shadow p-3 text-center">
|
||||||
<nav class="nav justify-content-center border-bottom mb-4 pb-2">
|
<nav class="nav justify-content-center border-bottom mb-4 pb-2">
|
||||||
<a href="" class="nav-link">Logout</a>
|
|
||||||
<router-link class="nav-link" to="/">Home</router-link>
|
<router-link class="nav-link" to="/">Home</router-link>
|
||||||
<router-link to="Login" class="nav-link">Login</router-link>
|
|
||||||
|
<a v-if="userStore.isLoggedIn" href="#" @click="onLogout()" class="nav-link">Logout</a>
|
||||||
|
<router-link v-else to="Login" class="nav-link">Login</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +19,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import Notification from '@/components/Notification.vue';
|
import Notification from '@/components/Notification.vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useUser } from './stores/user';
|
||||||
|
|
||||||
|
const userStore = useUser()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
async function onLogout() {
|
||||||
|
if(await userStore.logout()){
|
||||||
|
router.push({name: 'Login'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
defineProps<{ msg: string }>()
|
|
||||||
|
|
||||||
const count = ref(0)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Recommended IDE setup:
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>See <code>README.md</code> for more information.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
|
||||||
Vite Docs
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<button type="button" @click="count++">count is: {{ count }}</button>
|
|
||||||
<p>
|
|
||||||
Edit
|
|
||||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin: 0 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: #eee;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #304455;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPersist from 'pinia-plugin-persist'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import Oruga from '@oruga-ui/oruga-next'
|
import Oruga from '@oruga-ui/oruga-next'
|
||||||
@@ -8,8 +9,11 @@ import 'bootstrap'
|
|||||||
import "bootstrap/scss/bootstrap.scss"
|
import "bootstrap/scss/bootstrap.scss"
|
||||||
import "bootstrap-icons/font/bootstrap-icons.css"
|
import "bootstrap-icons/font/bootstrap-icons.css"
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(piniaPersist)
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(Oruga)
|
.use(Oruga)
|
||||||
.use(createPinia())
|
.use(pinia)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import { useUser } from '@/stores/user'
|
||||||
import Login from '../views/Login.vue'
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home
|
component: () => import('@/views/Home.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
requiresAdmin: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
component: Login
|
component: () => import('@/views/Login.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
requiresAdmin: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*/',
|
||||||
|
name: 'not-found',
|
||||||
|
redirect: '/login'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -20,4 +32,9 @@ const router = createRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
if (to.meta.requiresAuth && !useUser().isLoggedIn) return '/login'
|
||||||
|
if (to.meta.requiresAdmin && !useUser().isAdmin) return false
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ type AuthSuccResult = {
|
|||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
user: string,
|
user: string,
|
||||||
role: string
|
role: string,
|
||||||
|
token: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthErrResult = {
|
type AuthErrResult = {
|
||||||
@@ -20,17 +21,34 @@ type AuthErrResult = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUser = defineStore('userStore', {
|
|
||||||
|
|
||||||
|
export const useUser = defineStore({
|
||||||
|
id: 'storeUser',
|
||||||
|
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
user: '',
|
user: '',
|
||||||
role: '',
|
role: '',
|
||||||
isLoggedIn: false
|
isLoggedIn: false,
|
||||||
|
rememberMe: false,
|
||||||
|
token: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
isAdmin: (state) => state.role === 'admin',
|
||||||
|
|
||||||
|
preferredStorage: (state) => {
|
||||||
|
return localStorage
|
||||||
|
/* if (state.rememberMe) return localStorage
|
||||||
|
else return sessionStorage */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async login(username: string, password: string) : Promise<boolean> {
|
async login(username: string, password: string, rememberMe: boolean): Promise<boolean> {
|
||||||
|
|
||||||
const { notifications } = storeToRefs(useNotifications())
|
const { notifications } = storeToRefs(useNotifications())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -38,10 +56,13 @@ export const useUser = defineStore('userStore', {
|
|||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(response)
|
||||||
|
|
||||||
this.isLoggedIn = true
|
this.isLoggedIn = true
|
||||||
this.user = response.data.user
|
this.user = response.data.user
|
||||||
this.role = response.data.role
|
this.role = response.data.role
|
||||||
|
this.token = response.data.token
|
||||||
|
|
||||||
notifications.value.push({
|
notifications.value.push({
|
||||||
type: response.data.notification.type,
|
type: response.data.notification.type,
|
||||||
@@ -67,6 +88,54 @@ export const useUser = defineStore('userStore', {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout(): Promise<boolean> {
|
||||||
|
|
||||||
|
const { notifications } = storeToRefs(useNotifications())
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ai = axios.create({
|
||||||
|
baseURL: 'http://localhost:3333/api/v1/',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer '+this.token
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
await ai.post('/logout')
|
||||||
|
|
||||||
|
notifications.value.push({
|
||||||
|
type: 'success',
|
||||||
|
text: 'Logged out successfully',
|
||||||
|
randomKey: getUnixTime(new Date()).toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$reset()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
if(error instanceof Error) {
|
||||||
|
const notification = {
|
||||||
|
type: 'danger',
|
||||||
|
text: error.message,
|
||||||
|
randomKey: getUnixTime(new Date()).toString()
|
||||||
|
/**TODO #19 Generate randomKey in notification-store! */
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.value.push(notification)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
persist: {
|
||||||
|
enabled: true,
|
||||||
|
strategies: [
|
||||||
|
{
|
||||||
|
storage: localStorage
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<img src="/src/assets/logo.png">
|
<img src="/src/assets/logo.png">
|
||||||
<form class="m-auto">
|
<form class="m-auto">
|
||||||
<h1 class="h3 mb-3">Bitte einloggen</h1>
|
<h1 class="h3 mb-3">Bitte einloggen</h1>
|
||||||
@@ -15,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check my-3 mx-auto" style="width: 170px">
|
<div class="form-check my-3 mx-auto" style="width: 170px">
|
||||||
<input class="form-check-input" type="checkbox" id="rememberMeCheckbox">
|
<input class="form-check-input" v-model="input.rememberMe" type="checkbox" id="rememberMeCheckbox">
|
||||||
<label for="rememberMeCheckbox" class="form-check-label">
|
<label for="rememberMeCheckbox" class="form-check-label">
|
||||||
Eingeloggt bleiben
|
Eingeloggt bleiben
|
||||||
</label>
|
</label>
|
||||||
@@ -27,24 +24,22 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUser } from '@/stores/user'
|
import { useUser } from '@/stores/user'
|
||||||
import { useNotifications } from '@/stores/notifications'
|
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const userStore = useUser()
|
const userStore = useUser()
|
||||||
const noteStore = useNotifications()
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const input = reactive({
|
const input = reactive({
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
rememberMe: false
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
|
/**TODO #20 Use sessionStorage or localStorage based on rememberMe! */
|
||||||
if(await userStore.login(input.username, input.password)) {
|
if(await userStore.login(input.username, input.password, input.rememberMe)) {
|
||||||
router.push({name: 'Home'})
|
router.push({name: 'Home'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user