From b5ce65fc3ec221856c4fe1734f1fb9592f379345 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Sat, 23 Dec 2017 17:47:13 +0100
Subject: [PATCH] Now use vuex to manage state for authentication

---
 front/src/auth/index.js                    | 99 ----------------------
 front/src/components/Sidebar.vue           |  6 +-
 front/src/components/audio/SearchBar.vue   |  3 +-
 front/src/components/audio/Track.vue       |  5 +-
 front/src/components/audio/track/Table.vue |  6 +-
 front/src/components/auth/Login.vue        | 20 +++--
 front/src/components/auth/Logout.vue       | 21 +----
 front/src/components/auth/Profile.vue      | 28 ++----
 front/src/components/library/Library.vue   | 13 +--
 front/src/components/library/Track.vue     |  5 +-
 front/src/components/metadata/Search.vue   |  3 +-
 front/src/main.js                          |  7 +-
 front/src/store/auth.js                    | 98 +++++++++++++++++++++
 front/src/store/index.js                   |  2 +
 14 files changed, 137 insertions(+), 179 deletions(-)
 delete mode 100644 front/src/auth/index.js
 create mode 100644 front/src/store/auth.js

diff --git a/front/src/auth/index.js b/front/src/auth/index.js
deleted file mode 100644
index 80236942..00000000
--- 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 9112c258..d6a25392 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 2324c883..386e24a7 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 77d5ecc3..f0e1f14f 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 efb98e38..49323186 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 54e7b82e..99b439af 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 f4b2979b..fbacca70 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 2aaae9e2..607fa8ff 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 e8b053b6..3e3de9c6 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 36a76e82..48cd801c 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 8a400cf7..c3dc7433 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 eb714976..0c9230e8 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 00000000..851d9c21
--- /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 afd6e3f8..35e50e03 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
-- 
GitLab