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"/> {{ $t('user_interface_link') }} <icon iname="user" class="sm"/>
</b-button> </b-button>
</li> </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" > <b-button @click.prevent="logout" to="/logout" variant="outline-dark" block size="sm" >
{{ $t('logout') }} <icon iname="sign-out" class="sm"/> {{ $t('logout') }} <icon iname="sign-out" class="sm"/>
</b-button> </b-button>
@ -19,7 +19,7 @@
</header> </header>
<main> <main>
<router-view/> <router-view v-if="isReady"/>
</main> </main>
<footer> <footer>
@ -34,7 +34,11 @@
<b-nav-item href="https://donate.yunohost.org/" target="_blank" link-classes='text-secondary'> <b-nav-item href="https://donate.yunohost.org/" target="_blank" link-classes='text-secondary'>
<icon iname="heart" class="sm"/> Donate <icon iname="heart" class="sm"/> Donate
</b-nav-item> </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> </b-nav>
</nav> </nav>
</footer> </footer>
@ -42,25 +46,54 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex'
import api from '@/helpers/api' import api from '@/helpers/api'
export default { export default {
name: 'App', name: 'App',
computed: { data: () => {
connected: function () { return {
return this.$store.state.connected // 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: { methods: {
async logout() { async logout() {
const disconnected = await api.logout() await api.logout()
if (disconnected) {
this.$store.commit('CONNECTED', false); this.$store.commit('CONNECTED', false);
this.$router.push('/login') 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> </script>
@ -99,12 +132,12 @@ footer {
font-size: 0.875rem; font-size: 0.875rem;
margin-top: 2rem; margin-top: 2rem;
li { .nav-item {
&:not(:first-child) a::before { &:not(:first-child) a::before {
content: "•"; content: "•";
width: 1rem; width: 1rem;
display: inline-block; display: inline-block;
margin-left: -1rem; margin-left: -1.15rem;
} }
&:first-child { &:first-child {
margin-left: -1rem; margin-left: -1rem;

View file

@ -114,7 +114,7 @@
"everything_good": "Everything good!", "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.", "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", "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", "form_input_example": "Example: %s",
"from_to": "from %s to %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).", "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) { if (store.state.connected || to.meta.noAuth) {
next() next()
} else { } 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({ export default new Vuex.Store({
state: { 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: { mutations: {
['CONNECTED'] (state, connected) { ['CONNECTED'] (state, connected) {
localStorage.setItem('connected', connected) localStorage.setItem('connected', connected)
state.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: { actions: {
}, },
modules: { modules: {

View file

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