Now use vuex to manage state for authentication

This commit is contained in:
Eliot Berriot 2017-12-23 17:47:13 +01:00
parent df94ae37bf
commit b5ce65fc3e
No known key found for this signature in database
GPG Key ID: DD6965E2476E5C27
14 changed files with 137 additions and 179 deletions

View File

@ -1,99 +0,0 @@
import logger from '@/logging'
import config from '@/config'
import cache from '@/cache'
import Vue from 'vue'
import favoriteTracks from '@/favorites/tracks'
// URL and endpoint constants
const LOGIN_URL = config.API_URL + 'token/'
const USER_PROFILE_URL = config.API_URL + 'users/users/me/'
// const SIGNUP_URL = API_URL + 'users/'
let userData = {
authenticated: false,
username: '',
availablePermissions: {},
profile: {}
}
let auth = {
// Send a request to the login URL and save the returned JWT
login (context, creds, redirect, onError) {
return context.$http.post(LOGIN_URL, creds).then(response => {
logger.default.info('Successfully logged in as', creds.username)
cache.set('token', response.data.token)
cache.set('username', creds.username)
this.user.authenticated = true
this.user.username = creds.username
this.connect()
// Redirect to a specified route
if (redirect) {
context.$router.push(redirect)
}
}, response => {
logger.default.error('Error while logging in', response.data)
if (onError) {
onError(response)
}
})
},
// To log out, we just need to remove the token
logout () {
cache.clear()
this.user.authenticated = false
logger.default.info('Log out, goodbye!')
},
checkAuth () {
logger.default.info('Checking authentication...')
var jwt = this.getAuthToken()
var username = cache.get('username')
if (jwt) {
this.user.authenticated = true
this.user.username = username
logger.default.info('Logged back in as ' + username)
this.connect()
} else {
logger.default.info('Anonymous user')
this.user.authenticated = false
}
},
getAuthToken () {
return cache.get('token')
},
// The object to be passed as a header for authenticated requests
getAuthHeader () {
return 'JWT ' + this.getAuthToken()
},
fetchProfile () {
let resource = Vue.resource(USER_PROFILE_URL)
return resource.get({}).then((response) => {
logger.default.info('Successfully fetched user profile')
return response.data
}, (response) => {
logger.default.info('Error while fetching user profile')
})
},
connect () {
// called once user has logged in successfully / reauthenticated
// e.g. after a page refresh
let self = this
this.fetchProfile().then(data => {
Vue.set(self.user, 'profile', data)
Object.keys(data.permissions).forEach(function (key) {
// this makes it easier to check for permissions in templates
Vue.set(self.user.availablePermissions, key, data.permissions[String(key)].status)
})
})
favoriteTracks.fetch()
}
}
Vue.set(auth, 'user', userData)
export default auth

View File

@ -28,8 +28,8 @@
<div class="tabs">
<div class="ui bottom attached active tab" data-tab="library">
<div class="ui inverted vertical fluid menu">
<router-link class="item" v-if="auth.user.authenticated" :to="{name: 'profile', params: {username: auth.user.username}}"><i class="user icon"></i> Logged in as {{ auth.user.username }}</router-link>
<router-link class="item" v-if="auth.user.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i> Logout</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}"><i class="user icon"></i> Logged in as {{ $store.state.auth.username }}</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i> Logout</router-link>
<router-link class="item" v-else :to="{name: 'login'}"><i class="sign in icon"></i> Login</router-link>
<router-link class="item" :to="{path: '/library'}"><i class="sound icon"> </i>Browse library</router-link>
<router-link class="item" :to="{path: '/favorites'}"><i class="heart icon"></i> Favorites</router-link>
@ -97,7 +97,6 @@ import Player from '@/components/audio/Player'
import favoriteTracks from '@/favorites/tracks'
import Logo from '@/components/Logo'
import SearchBar from '@/components/audio/SearchBar'
import auth from '@/auth'
import backend from '@/audio/backend'
import draggable from 'vuedraggable'
@ -113,7 +112,6 @@ export default {
},
data () {
return {
auth: auth,
backend: backend,
favoriteTracks
}

View File

@ -12,7 +12,6 @@
<script>
import jQuery from 'jquery'
import config from '@/config'
import auth from '@/auth'
import router from '@/router'
const SEARCH_URL = config.API_URL + 'search?query={query}'
@ -27,7 +26,7 @@ export default {
},
apiSettings: {
beforeXHR: function (xhrObject) {
xhrObject.setRequestHeader('Authorization', auth.getAuthHeader())
xhrObject.setRequestHeader('Authorization', this.$store.getters['auth/header'])
return xhrObject
},
onResponse: function (initialResponse) {

View File

@ -15,7 +15,6 @@
<script>
import {mapState} from 'vuex'
import backend from '@/audio/backend'
import auth from '@/auth'
import url from '@/utils/url'
// import logger from '@/logging'
@ -40,12 +39,12 @@ export default {
return null
}
let path = backend.absoluteUrl(file.path)
if (auth.user.authenticated) {
if (this.$store.state.auth.authenticated) {
// we need to send the token directly in url
// so authentication can be checked by the backend
// because for audio files we cannot use the regular Authentication
// header
path = url.updateQueryString(path, 'jwt', auth.getAuthToken())
path = url.updateQueryString(path, 'jwt', this.$store.state.auth.token)
}
return path
}

View File

@ -58,9 +58,9 @@
Keep your PRIVATE_TOKEN secret as it gives access to your account.
</div>
<pre>
export PRIVATE_TOKEN="{{ auth.getAuthToken ()}}"
export PRIVATE_TOKEN="{{ $store.state.auth.token ()}}"
<template v-for="track in tracks"><template v-if="track.files.length > 0">
curl -G -o "{{ track.files[0].filename }}" <template v-if="auth.user.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ backend.absoluteUrl(track.files[0].path) }}"</template></template>
curl -G -o "{{ track.files[0].filename }}" <template v-if="$store.state.auth.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ backend.absoluteUrl(track.files[0].path) }}"</template></template>
</pre>
</div>
</div>
@ -83,7 +83,6 @@ curl -G -o "{{ track.files[0].filename }}" <template v-if="auth.user.authenticat
<script>
import backend from '@/audio/backend'
import auth from '@/auth'
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
import PlayButton from '@/components/audio/PlayButton'
@ -102,7 +101,6 @@ export default {
data () {
return {
backend: backend,
auth: auth,
showDownloadModal: false
}
}

View File

@ -39,12 +39,11 @@
</template>
<script>
import auth from '@/auth'
export default {
name: 'login',
props: {
next: {type: String}
next: {type: String, default: '/'}
},
data () {
return {
@ -72,14 +71,17 @@ export default {
}
// We need to pass the component's this context
// to properly make use of http in the auth service
auth.login(this, credentials, {path: this.next}, function (response) {
// error callback
if (response.status === 400) {
self.error = 'invalid_credentials'
} else {
self.error = 'unknown_error'
this.$store.dispatch('auth/login', {
credentials,
next: this.next,
onError: response => {
if (response.status === 400) {
self.error = 'invalid_credentials'
} else {
self.error = 'unknown_error'
}
}
}).then((response) => {
}).then(e => {
self.isLoading = false
})
}

View File

@ -3,8 +3,8 @@
<div class="ui vertical stripe segment">
<div class="ui small text container">
<h2>Are you sure you want to log out?</h2>
<p>You are currently logged in as {{ auth.user.username }}</p>
<button class="ui button" @click="logout">Yes, log me out!</button>
<p>You are currently logged in as {{ $store.state.auth.username }}</p>
<button class="ui button" @click="$store.dispatch('auth/logout')">Yes, log me out!</button>
</form>
</div>
</div>
@ -12,23 +12,8 @@
</template>
<script>
import auth from '@/auth'
export default {
name: 'logout',
data () {
return {
// We need to initialize the component with any
// properties that will be used in it
auth: auth
}
},
methods: {
logout () {
auth.logout()
this.$router.push({name: 'index'})
}
}
name: 'logout'
}
</script>

View File

@ -3,17 +3,17 @@
<div v-if="isLoading" class="ui vertical segment">
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div>
<template v-if="profile">
<template v-if="$store.state.auth.profile">
<div :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']">
<h2 class="ui center aligned icon header">
<i class="circular inverted user green icon"></i>
<div class="content">
{{ profile.username }}
{{ $store.state.auth.profile.username }}
<div class="sub header">Registered since {{ signupDate }}</div>
</div>
</h2>
<div class="ui basic green label">this is you!</div>
<div v-if="profile.is_staff" class="ui yellow label">
<div v-if="$store.state.auth.profile.is_staff" class="ui yellow label">
<i class="star icon"></i>
Staff member
</div>
@ -23,35 +23,21 @@
</template>
<script>
import auth from '@/auth'
var dateFormat = require('dateformat')
const dateFormat = require('dateformat')
export default {
name: 'login',
props: ['username'],
data () {
return {
profile: null
}
},
created () {
this.fetchProfile()
},
methods: {
fetchProfile () {
let self = this
auth.fetchProfile().then(data => {
self.profile = data
})
}
this.$store.dispatch('auth/fetchProfile')
},
computed: {
signupDate () {
let d = new Date(this.profile.date_joined)
let d = new Date(this.$store.state.auth.profile.date_joined)
return dateFormat(d, 'longDate')
},
isLoading () {
return !this.profile
return !this.$store.state.auth.profile
}
}
}

View File

@ -4,8 +4,8 @@
<router-link class="ui item" to="/library" exact>Browse</router-link>
<router-link class="ui item" to="/library/artists" exact>Artists</router-link>
<div class="ui secondary right menu">
<router-link v-if="auth.user.availablePermissions['import.launch']" class="ui item" to="/library/import/launch" exact>Import</router-link>
<router-link v-if="auth.user.availablePermissions['import.launch']" class="ui item" to="/library/import/batches">Import batches</router-link>
<router-link v-if="$store.state.auth.availablePermissions['import.launch']" class="ui item" to="/library/import/launch" exact>Import</router-link>
<router-link v-if="$store.state.auth.availablePermissions['import.launch']" class="ui item" to="/library/import/batches">Import batches</router-link>
</div>
</div>
<router-view :key="$route.fullPath"></router-view>
@ -14,15 +14,8 @@
<script>
import auth from '@/auth'
export default {
name: 'library',
data: function () {
return {
auth
}
}
name: 'library'
}
</script>

View File

@ -61,7 +61,6 @@
<script>
import auth from '@/auth'
import url from '@/utils/url'
import logger from '@/logging'
import backend from '@/audio/backend'
@ -124,8 +123,8 @@ export default {
downloadUrl () {
if (this.track.files.length > 0) {
let u = backend.absoluteUrl(this.track.files[0].path)
if (auth.user.authenticated) {
u = url.updateQueryString(u, 'jwt', auth.getAuthToken())
if (this.$store.state.auth.authenticated) {
u = url.updateQueryString(u, 'jwt', this.$store.state.auth.token)
}
return u
}

View File

@ -23,7 +23,6 @@
<script>
import jQuery from 'jquery'
import config from '@/config'
import auth from '@/auth'
export default {
props: {
@ -66,7 +65,7 @@ export default {
},
apiSettings: {
beforeXHR: function (xhrObject, s) {
xhrObject.setRequestHeader('Authorization', auth.getAuthHeader())
xhrObject.setRequestHeader('Authorization', this.$store.getters['auth/header'])
return xhrObject
},
onResponse: function (initialResponse) {

View File

@ -9,7 +9,6 @@ import Vue from 'vue'
import App from './App'
import router from './router'
import VueResource from 'vue-resource'
import auth from './auth'
import VueLazyload from 'vue-lazyload'
import store from './store'
@ -26,8 +25,8 @@ Vue.config.productionTip = false
Vue.http.interceptors.push(function (request, next) {
// modify headers
if (auth.user.authenticated) {
request.headers.set('Authorization', auth.getAuthHeader())
if (store.state.auth.authenticated) {
request.headers.set('Authorization', store.getters['auth/header'])
}
next(function (response) {
// redirect to login form when we get unauthorized response from server
@ -38,7 +37,7 @@ Vue.http.interceptors.push(function (request, next) {
})
})
auth.checkAuth()
store.dispatch('auth/check')
/* eslint-disable no-new */
new Vue({
el: '#app',

98
front/src/store/auth.js Normal file
View File

@ -0,0 +1,98 @@
import Vue from 'vue'
import config from '@/config'
import logger from '@/logging'
import cache from '@/cache'
import router from '@/router'
// import favoriteTracks from '@/favorites/tracks'
const LOGIN_URL = config.API_URL + 'token/'
const USER_PROFILE_URL = config.API_URL + 'users/users/me/'
export default {
namespaced: true,
state: {
authenticated: false,
username: '',
availablePermissions: {},
profile: null,
token: ''
},
getters: {
header: state => {
return 'JWT ' + state.token
}
},
mutations: {
profile: (state, value) => {
state.profile = value
},
authenticated: (state, value) => {
state.authenticated = value
},
username: (state, value) => {
state.username = value
},
token: (state, value) => {
state.token = value
}
},
actions: {
// Send a request to the login URL and save the returned JWT
login ({commit, dispatch, state}, {next, credentials, onError}) {
let resource = Vue.resource(LOGIN_URL)
return resource.save({}, credentials).then(response => {
logger.default.info('Successfully logged in as', credentials.username)
commit('token', response.data.token)
cache.set('token', response.data.token)
commit('username', credentials.username)
cache.set('username', credentials.username)
commit('authenticated', true)
dispatch('fetchProfile')
// Redirect to a specified route
router.push(next)
}, response => {
logger.default.error('Error while logging in', response.data)
onError(response)
})
},
logout ({commit}) {
cache.clear()
commit('authenticated', false)
commit('profile', null)
logger.default.info('Log out, goodbye!')
router.push({name: 'index'})
},
check ({commit, dispatch, state}) {
logger.default.info('Checking authentication...')
var jwt = cache.get('token')
var username = cache.get('username')
if (jwt) {
commit('authenticated', true)
commit('username', username)
commit('token', jwt)
logger.default.info('Logged back in as ' + username)
dispatch('fetchProfile')
} else {
logger.default.info('Anonymous user')
commit('authenticated', false)
}
},
fetchProfile ({commit, state}) {
let resource = Vue.resource(USER_PROFILE_URL)
return resource.get({}).then((response) => {
logger.default.info('Successfully fetched user profile')
let data = response.data
commit('profile', data)
// favoriteTracks.fetch()
console.log('AFTER')
Object.keys(data.permissions).forEach(function (key) {
// this makes it easier to check for permissions in templates
state.availablePermissions[key] = data.permissions[String(key)].status
})
return response.data
}, (response) => {
logger.default.info('Error while fetching user profile')
})
}
}
}

View File

@ -1,6 +1,7 @@
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './auth'
import queue from './queue'
import radios from './radios'
import player from './player'
@ -9,6 +10,7 @@ Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth,
queue,
radios,
player