mirror of
https://github.com/MarcZierle/photo-log-frontend.git
synced 2025-04-07 21:14:37 +00:00
add login and token refresh
This commit is contained in:
parent
f5148dd017
commit
dc0e5451ce
15
package-lock.json
generated
15
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.8.8",
|
"autoprefixer": "^9.8.8",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
"axios-auth-refresh": "^3.3.4",
|
||||||
"lorem-ipsum": "^2.0.8",
|
"lorem-ipsum": "^2.0.8",
|
||||||
"postcss": "^7.0.39",
|
"postcss": "^7.0.39",
|
||||||
"tailwind-children": "^0.5.0",
|
"tailwind-children": "^0.5.0",
|
||||||
@ -859,6 +860,14 @@
|
|||||||
"follow-redirects": "^1.14.4"
|
"follow-redirects": "^1.14.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios-auth-refresh": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.4.tgz",
|
||||||
|
"integrity": "sha512-cGq3bZu+lip5j+byaQRZaZ3wpCUxs93jGV0614VYP5k2H1vbdoaw6HGazaUJxcRsFMctR3DItCAx1Dn7KerlcA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -4758,6 +4767,12 @@
|
|||||||
"follow-redirects": "^1.14.4"
|
"follow-redirects": "^1.14.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"axios-auth-refresh": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.4.tgz",
|
||||||
|
"integrity": "sha512-cGq3bZu+lip5j+byaQRZaZ3wpCUxs93jGV0614VYP5k2H1vbdoaw6HGazaUJxcRsFMctR3DItCAx1Dn7KerlcA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.8.8",
|
"autoprefixer": "^9.8.8",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
"axios-auth-refresh": "^3.3.4",
|
||||||
"lorem-ipsum": "^2.0.8",
|
"lorem-ipsum": "^2.0.8",
|
||||||
"postcss": "^7.0.39",
|
"postcss": "^7.0.39",
|
||||||
"tailwind-children": "^0.5.0",
|
"tailwind-children": "^0.5.0",
|
||||||
|
@ -45,8 +45,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
store.dispatch('loadAuth')
|
||||||
|
|
||||||
console.log("Starting connection to WebSocket Server")
|
console.log("Starting connection to WebSocket Server")
|
||||||
this.connection = new WebSocket("wss://zierle-training-staging.riezel.com/ws/notifications/")
|
this.connection = new WebSocket("wss://zierle-training.riezel.com/ws/notifications/")
|
||||||
|
|
||||||
this.connection.onmessage = function(event) {
|
this.connection.onmessage = function(event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import createAuthRefreshInterceptor from 'axios-auth-refresh';
|
||||||
|
|
||||||
|
import store from '../store'
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: 'https://zierle-training.riezel.com/api/v1',
|
baseURL: 'https://zierle-training.riezel.com/api/v1',
|
||||||
@ -10,6 +13,35 @@ const apiClient = axios.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const refreshAuthLLogic = (failedRequest) => {
|
||||||
|
apiClient.post('/token/refresh/', {'refresh': store.getters.auth.tokens.refresh}).then(response => {
|
||||||
|
store.dispatch('setTokens', response.data)
|
||||||
|
failedRequest.response.config.headers['Authorization'] = 'Bearer ' + store.getters.auth.tokens.access
|
||||||
|
return Promise.resolve()
|
||||||
|
}).catch(error => {
|
||||||
|
store.dispatch('logout')
|
||||||
|
return Promise.reject()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupApiClient() {
|
||||||
|
createAuthRefreshInterceptor(apiClient, refreshAuthLLogic, {
|
||||||
|
statusCodes: [401, 403]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use(function(config) {
|
||||||
|
let auth = store.getters.auth
|
||||||
|
if (auth.authenticated) {
|
||||||
|
config.headers['Authorization'] = 'Bearer ' + auth.tokens.access
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
export function login(username, password) {
|
||||||
|
return apiClient.post('/token/', {email: username, password})
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhotoLogList() {
|
export function getPhotoLogList() {
|
||||||
return apiClient.get('/photologs/')
|
return apiClient.get('/photologs/')
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,14 @@
|
|||||||
<router-link :to="{name: 'CreateLog'}">+ New Log</router-link>
|
<router-link :to="{name: 'CreateLog'}">+ New Log</router-link>
|
||||||
<router-link :to="{name: 'LogTemplatesList'}">All Templates</router-link>
|
<router-link :to="{name: 'LogTemplatesList'}">All Templates</router-link>
|
||||||
<router-link :to="{name: 'CreateLogTemplate'}">+ New Template</router-link>
|
<router-link :to="{name: 'CreateLogTemplate'}">+ New Template</router-link>
|
||||||
|
<router-link :to="{name: 'Login'}" v-if="!loggedIn">Login</router-link>
|
||||||
|
<div v-else @click="logout">Logged in as {{authUser}}</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import store from '@/store/index.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
data() {
|
data() {
|
||||||
@ -28,6 +32,14 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('scroll', this.updateScrollPosition)
|
window.addEventListener('scroll', this.updateScrollPosition)
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
loggedIn() {
|
||||||
|
return store.getters.auth.authenticated
|
||||||
|
},
|
||||||
|
authUser() {
|
||||||
|
return store.getters.auth.user
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateScrollPosition() {
|
updateScrollPosition() {
|
||||||
this.scrollPosition = window.scrollY
|
this.scrollPosition = window.scrollY
|
||||||
@ -37,6 +49,10 @@ export default {
|
|||||||
} else if (this.showNavBg && this.scrollPosition <= 10) {
|
} else if (this.showNavBg && this.scrollPosition <= 10) {
|
||||||
this.showNavBg = false
|
this.showNavBg = false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
store.dispatch('logout')
|
||||||
|
this.$router.push({name: 'Login'})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -61,4 +77,4 @@ a:hover {
|
|||||||
.router-link-exact-active {
|
.router-link-exact-active {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,6 +2,7 @@ import { createApp } from 'vue'
|
|||||||
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'
|
||||||
|
import { setupApiClient } from './api'
|
||||||
|
|
||||||
import { createMetaManager, plugin as metaPlugin } from 'vue-meta'
|
import { createMetaManager, plugin as metaPlugin } from 'vue-meta'
|
||||||
|
|
||||||
@ -22,5 +23,7 @@ app.use(naive)
|
|||||||
|
|
||||||
loadAndSetLocale(i18n, 'en')
|
loadAndSetLocale(i18n, 'en')
|
||||||
|
|
||||||
|
setupApiClient()
|
||||||
|
|
||||||
//await router.isReady()
|
//await router.isReady()
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
@ -13,6 +13,7 @@ const CreateLogTemplate = () => import('../views/CreateLogTemplate.vue')
|
|||||||
const CameraCapture = () => import('../views/CameraCapture.vue')
|
const CameraCapture = () => import('../views/CameraCapture.vue')
|
||||||
const ManagePhotos = () => import('../views/ManagePhotos.vue')
|
const ManagePhotos = () => import('../views/ManagePhotos.vue')
|
||||||
const DevView = () => import('../views/DevView.vue')
|
const DevView = () => import('../views/DevView.vue')
|
||||||
|
const LoginView = () => import('../views/LoginView.vue')
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -20,6 +21,11 @@ const routes = [
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: LoginView,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
name: 'LogsList',
|
name: 'LogsList',
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
updatePhoto,
|
updatePhoto,
|
||||||
deletePhoto,
|
deletePhoto,
|
||||||
cropPhoto,
|
cropPhoto,
|
||||||
|
login as apiLogin,
|
||||||
} from '@/api'
|
} from '@/api'
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
@ -27,8 +28,23 @@ export default createStore({
|
|||||||
photoTags: null,
|
photoTags: null,
|
||||||
photos: [],
|
photos: [],
|
||||||
photosStatus: {},
|
photosStatus: {},
|
||||||
|
auth: {
|
||||||
|
authenticated: false,
|
||||||
|
user: null,
|
||||||
|
tokens: {
|
||||||
|
access: null,
|
||||||
|
refresh: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
SET_AUTH(state, {authenticated, tokens, user}) {
|
||||||
|
state.auth.authenticated = authenticated
|
||||||
|
state.auth.user = user
|
||||||
|
state.auth.tokens = tokens
|
||||||
|
|
||||||
|
localStorage.setItem('auth', JSON.stringify(state.auth))
|
||||||
|
},
|
||||||
SET_PHOTO_STATUS(state, {id, status}) {
|
SET_PHOTO_STATUS(state, {id, status}) {
|
||||||
state.photosStatus[id] = status
|
state.photosStatus[id] = status
|
||||||
},
|
},
|
||||||
@ -113,13 +129,56 @@ export default createStore({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
setTokens({commit, state}, {access}) {
|
||||||
|
var refresh = state.auth.tokens.refresh
|
||||||
|
commit('SET_AUTH', {
|
||||||
|
authenticated: true,
|
||||||
|
tokens: {
|
||||||
|
access,
|
||||||
|
refresh,
|
||||||
|
},
|
||||||
|
user: state.auth.user,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
login({commit}, {username, password}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
apiLogin(username, password).then(response => {
|
||||||
|
commit('SET_AUTH', {
|
||||||
|
authenticated: true,
|
||||||
|
tokens: response.data,
|
||||||
|
user: username,
|
||||||
|
})
|
||||||
|
resolve(response)
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
logout({commit}) {
|
||||||
|
commit('SET_AUTH', {
|
||||||
|
authenticated: false,
|
||||||
|
tokens: {
|
||||||
|
access: null,
|
||||||
|
refresh: null,
|
||||||
|
},
|
||||||
|
user: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadAuth({commit}) {
|
||||||
|
let auth = JSON.parse(localStorage.getItem('auth'))
|
||||||
|
if (auth) {
|
||||||
|
commit('SET_AUTH', auth)
|
||||||
|
}
|
||||||
|
},
|
||||||
setPhotoStatus({commit}, {id, status}) {
|
setPhotoStatus({commit}, {id, status}) {
|
||||||
commit('SET_PHOTO_STATUS', {id, status})
|
commit('SET_PHOTO_STATUS', {id, status})
|
||||||
},
|
},
|
||||||
loadPhotoLogList({commit}) {
|
loadPhotoLogList({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoLogList().then((response) => {
|
getPhotoLogList().then((response) => {
|
||||||
commit('SET_PHOTO_LOG_LIST', response.data)
|
commit('SET_PHOTO_LOG_LIST', response.data['results'])
|
||||||
resolve()
|
resolve()
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@ -130,7 +189,7 @@ export default createStore({
|
|||||||
loadPhotoLogTemplateList({commit}) {
|
loadPhotoLogTemplateList({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoLogTemplateList().then((response) => {
|
getPhotoLogTemplateList().then((response) => {
|
||||||
commit('SET_PHOTO_LOG_TEMPLATE_LIST', response.data)
|
commit('SET_PHOTO_LOG_TEMPLATE_LIST', response.data['results'])
|
||||||
resolve()
|
resolve()
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@ -203,7 +262,7 @@ export default createStore({
|
|||||||
loadPhotoGroups({commit}) {
|
loadPhotoGroups({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoGroups().then((response) => {
|
getPhotoGroups().then((response) => {
|
||||||
commit('SET_PHOTO_GROUPS', response.data)
|
commit('SET_PHOTO_GROUPS', response.data['results'])
|
||||||
resolve(response.data)
|
resolve(response.data)
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -213,7 +272,7 @@ export default createStore({
|
|||||||
loadPhotoTags({commit}) {
|
loadPhotoTags({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoTags().then((response) => {
|
getPhotoTags().then((response) => {
|
||||||
commit('SET_PHOTO_TAGS', response.data)
|
commit('SET_PHOTO_TAGS', response.data['results'])
|
||||||
resolve(response.data)
|
resolve(response.data)
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -223,7 +282,7 @@ export default createStore({
|
|||||||
loadPhotosInGroup({commit}, group_id) {
|
loadPhotosInGroup({commit}, group_id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotosByGroup(group_id).then((response) => {
|
getPhotosByGroup(group_id).then((response) => {
|
||||||
commit('SET_PHOTOS_IN_GROUP', {group_id, photos: response.data})
|
commit('SET_PHOTOS_IN_GROUP', {group_id, photos: response.data['results']})
|
||||||
resolve(response.data)
|
resolve(response.data)
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -278,6 +337,9 @@ export default createStore({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
auth(state) {
|
||||||
|
return state.auth
|
||||||
|
},
|
||||||
photoLogById: (state) => (id) => {
|
photoLogById: (state) => (id) => {
|
||||||
let log = state.photoLogList.filter(log => log.id == id)
|
let log = state.photoLogList.filter(log => log.id == id)
|
||||||
if (log.length > 0) {
|
if (log.length > 0) {
|
||||||
|
38
src/views/LoginView.vue
Normal file
38
src/views/LoginView.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pt-10">
|
||||||
|
<h1 class="font-bold text-lg">Login</h1>
|
||||||
|
<n-input placeholder="E-Mail" v-model:value="email" />
|
||||||
|
<n-input type="password" v-model:value="pin" placeholder="PIN" />
|
||||||
|
<n-button @click="login">Login</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import store from '@/store/index.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup () {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
data() {return {
|
||||||
|
email: '',
|
||||||
|
pin: ''
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
login() {
|
||||||
|
store.dispatch('login', {
|
||||||
|
username: this.email,
|
||||||
|
password: this.pin,
|
||||||
|
}).then(() => {
|
||||||
|
this.$router.push({name: 'Home'})
|
||||||
|
}).catch(() =>{
|
||||||
|
alert("Login failed")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user