improve connection state detection

blocks all api calls until we are sure the user is connected.
if session has expired, force reconnection.
This commit is contained in:
Axolotle 2020-07-12 19:09:53 +02:00
parent bd148897f8
commit 88d7b736e2
5 changed files with 66 additions and 21 deletions

View file

@ -9,7 +9,7 @@
{{ $t('user_interface_link') }} <icon iname="user" class="sm"/>
</b-button>
</li>
<li class="nav-item" v-bind:hidden="!connected">
<li class="nav-item" v-show="connected">
<b-button @click.prevent="logout" to="/logout" variant="outline-dark" block size="sm" >
{{ $t('logout') }} <icon iname="sign-out" class="sm"/>
</b-button>
@ -19,7 +19,7 @@
</header>
<main>
<router-view/>
<router-view v-if="isReady"/>
</main>
<footer>
@ -34,7 +34,11 @@
<b-nav-item href="https://donate.yunohost.org/" target="_blank" link-classes='text-secondary'>
<icon iname="heart" class="sm"/> Donate
</b-nav-item>
<b-nav-text class="ml-auto" id="yunohost-version">version</b-nav-text>
<i18n v-if="yunohostInfos" path="footer_version" tag="b-nav-text" class="ml-auto" id="yunohost-version">
<template v-slot:ynh><b-link href="https://yunohost.org">YunoHost</b-link></template>
<template v-slot:version>{{ yunohostInfos.version }}</template>
<template v-slot:repo>{{ yunohostInfos.repo }}</template>
</i18n>
</b-nav>
</nav>
</footer>
@ -42,25 +46,54 @@
</template>
<script>
import { mapState } from 'vuex'
import api from '@/helpers/api'
export default {
name: 'App',
computed: {
connected: function () {
return this.$store.state.connected
data: () => {
return {
// isReady blocks the rendering of the rooter-view until we have a true info
// about the connected state of the user.
isReady: false,
}
},
computed: {
...mapState(['connected', 'yunohostInfos']),
},
methods: {
async logout() {
const disconnected = await api.logout()
if (disconnected) {
await api.logout()
this.$store.commit('CONNECTED', false);
this.$router.push('/login')
}
}
},
},
// This hook is only triggered at page reload so the value of state.connected
// always come from the localStorage
async created() {
if (!this.$store.state.connected) {
// user is not connected: allow the login view to be rendered.
this.isReady = true
return
}
// localStorage 'connected' value may be true, but session may have expired.
// Try to get the yunohost version.
try {
const data = await api.getVersion()
this.$store.commit('YUNOHOST_INFOS', data.yunohost)
} catch (err) {
// Session expired, reset the 'connected' state and redirect with a query
// FIXME is there a case where the error may not be a 401 therefor requires
// better handling ?
this.$store.commit('CONNECTED', false);
this.$router.push({name: 'login', query: {redirect: this.$route.path}})
} finally {
// in any case allow the router-view to be rendered
this.isReady = true;
}
}
}
</script>
@ -99,12 +132,12 @@ footer {
font-size: 0.875rem;
margin-top: 2rem;
li {
.nav-item {
&:not(:first-child) a::before {
content: "•";
width: 1rem;
display: inline-block;
margin-left: -1rem;
margin-left: -1.15rem;
}
&:first-child {
margin-left: -1rem;

View file

@ -114,7 +114,7 @@
"everything_good": "Everything good!",
"experimental_warning": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.",
"firewall": "Firewall",
"footer_version": "Powered by <a href='https://yunohost.org'>YunoHost</a> %s (%s).",
"footer_version": "Powered by {ynh} {version} ({repo}).",
"form_input_example": "Example: %s",
"from_to": "from %s to %s",
"good_practices_about_admin_password": "You are now about to define a new admin password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",

View file

@ -18,7 +18,7 @@ router.beforeEach((to, from, next) => {
if (store.state.connected || to.meta.noAuth) {
next()
} else {
next({path: '/login', query: {redirect: to.path}})
next({name: 'login', query: {redirect: to.path}})
}
})

View file

@ -6,14 +6,24 @@ Vue.use(Vuex)
export default new Vuex.Store({
state: {
connected: localStorage.getItem('connected') === 'true'
connected: localStorage.getItem('connected') === 'true',
yunohostInfos: null
},
// Mutations must be synchronous. They are used to change the store state.
mutations: {
['CONNECTED'] (state, connected) {
localStorage.setItem('connected', connected)
state.connected = connected
if (!connected) {
state.yunohostInfos = null
}
},
['YUNOHOST_INFOS'] (state, data) {
console.log('version changed', data);
state.yunohostInfos = data
}
},
// Actions may be asynchronous. They are used to commit mutations.
actions: {
},
modules: {

View file

@ -12,13 +12,13 @@
v-model="password"
type="password"
:placeholder="$t('administration_password')"
:state="state"
:state="isValid"
></b-form-input>
<template v-slot:append>
<b-button type="submit" variant="success">{{ $t('login') }}</b-button>
</template>
</b-input-group>
<b-form-invalid-feedback :state="state">
<b-form-invalid-feedback :state="isValid">
{{ $t('wrong_password') }}
</b-form-invalid-feedback>
</b-form>
@ -33,7 +33,7 @@ export default {
data: () => {
return {
password: '',
state: null,
isValid: null,
}
},
methods: {
@ -42,9 +42,11 @@ export default {
if (connected) {
this.$store.commit('CONNECTED', true);
this.$router.push(this.$route.query.redirect || '/')
const infos = await api.getVersion();
this.$store.commit('YUNOHOST_INFOS', infos.yunohost)
} else {
this.$store.commit('CONNECTED', false);
this.state = false
this.isValid = false
}
}
},