diff --git a/front/src/auth/index.js b/front/src/auth/index.js deleted file mode 100644 index 80236942858440d517d2fe80b7bb43c71b3ea7c0..0000000000000000000000000000000000000000 --- a/front/src/auth/index.js +++ /dev/null @@ -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 diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 9112c258863e2f8c088197ecafba3f1b61744559..d6a2539224afb89412f5488cd1e7b02ade452cdb 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -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 } diff --git a/front/src/components/audio/SearchBar.vue b/front/src/components/audio/SearchBar.vue index 2324c88392f258f95b7915cce04ea1b9166d5e80..386e24a74f677d40fc60d1b536c15793288a5454 100644 --- a/front/src/components/audio/SearchBar.vue +++ b/front/src/components/audio/SearchBar.vue @@ -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) { diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index 77d5ecc32701d7bcf6b52fce5984644a612ba46d..f0e1f14fa70fbb52a85ba5d99ddc7ffdb4e11dbf 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -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 } diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue index efb98e382804ecb2623d4f96f5e6b9935559052b..4932318603e2c3eb2263b0c152e26fade0aa6fc1 100644 --- a/front/src/components/audio/track/Table.vue +++ b/front/src/components/audio/track/Table.vue @@ -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 } } diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue index 54e7b82e096433aacd18d02053e66c164e51ca60..99b439af8b3e25c821ddb1dbc0c86c6419961d2c 100644 --- a/front/src/components/auth/Login.vue +++ b/front/src/components/auth/Login.vue @@ -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 }) } diff --git a/front/src/components/auth/Logout.vue b/front/src/components/auth/Logout.vue index f4b2979bc05394dc04f7644e88700a08b13989d5..fbacca70338ed295dc284664d443c9b239b25da0 100644 --- a/front/src/components/auth/Logout.vue +++ b/front/src/components/auth/Logout.vue @@ -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> diff --git a/front/src/components/auth/Profile.vue b/front/src/components/auth/Profile.vue index 2aaae9e2df34d292f5cc66ae04dee5898bc9d335..607fa8ff2b84ffdde60ccbf3b514938dfeb5aba4 100644 --- a/front/src/components/auth/Profile.vue +++ b/front/src/components/auth/Profile.vue @@ -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 } } } diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue index e8b053b6d0124175fda02254e71a85a420401e5e..3e3de9c61044852d2ae1ba9cb46b5c32a512f3cb 100644 --- a/front/src/components/library/Library.vue +++ b/front/src/components/library/Library.vue @@ -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> diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue index 36a76e822c2ac0218d8c23a1fb2cca8f21db4f29..48cd801c3d8a96fc34b4904febbacb0d6243d66f 100644 --- a/front/src/components/library/Track.vue +++ b/front/src/components/library/Track.vue @@ -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 } diff --git a/front/src/components/metadata/Search.vue b/front/src/components/metadata/Search.vue index 8a400cf7b0e5b488e794afb62a0ddab8d38ce59a..c3dc7433c5e268baa4e848bcb9a22125b555bd93 100644 --- a/front/src/components/metadata/Search.vue +++ b/front/src/components/metadata/Search.vue @@ -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) { diff --git a/front/src/main.js b/front/src/main.js index eb714976fa4ff46c943e66ca03c0e74496a09583..0c9230e8e8acc3059bf7bf030f53e74e5a69e204 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -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', diff --git a/front/src/store/auth.js b/front/src/store/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..851d9c214ba70bfc8831bd4798be2fa01e3c92b7 --- /dev/null +++ b/front/src/store/auth.js @@ -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') + }) + } + } +} diff --git a/front/src/store/index.js b/front/src/store/index.js index afd6e3f84cd6d53002359f7b496b427fa33540f1..35e50e0304f352315a7134f8370d63757114234f 100644 --- a/front/src/store/index.js +++ b/front/src/store/index.js @@ -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