Add login form validation
- Sending incorrect credentials returns a 401 and we don't want the page
to redirect if we are trying to login. Wrapped the redirect in an if
block.
- Returning a promise used by the logout action, which is needed
when not redirecting the page. Didn't add to the if block since
other errors that use the router to redirect will need the Promise
returned also, e.g. 403.
Signed-off-by: Derick Montague <derick.montague@ibm.com>
Change-Id: I6db706ef7c71ed13baed95dc4264e6ae11d13ad3
diff --git a/src/main.js b/src/main.js
index 023c24e..b69c659 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,6 +4,7 @@
import store from './store';
import { dateFilter } from 'vue-date-fns';
import {
+ AlertPlugin,
BadgePlugin,
ButtonPlugin,
CollapsePlugin,
@@ -24,6 +25,7 @@
Vue.filter('date', dateFilter);
+Vue.use(AlertPlugin);
Vue.use(BadgePlugin);
Vue.use(ButtonPlugin);
Vue.use(CollapsePlugin);
diff --git a/src/store/api.js b/src/store/api.js
index 4918d80..0f8c948 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -10,12 +10,16 @@
// TODO: Provide user with a notification and way to keep system active
if (response.status == 401) {
- window.location = '/login';
+ if (response.config.url != '/login') {
+ window.location = '/login';
+ }
}
if (response.status == 403) {
router.push({ name: 'unauthorized' });
}
+
+ return Promise.reject(error);
});
export default {
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 88456e9..3a554b6 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -13,7 +13,7 @@
},
mutations: {
authRequest(state) {
- state.status = 'loading';
+ state.status = 'processing';
},
authSuccess(state) {
state.status = 'authenticated';
@@ -22,6 +22,9 @@
authError(state) {
state.status = 'error';
},
+ authReset(state) {
+ state.status = '';
+ },
logout(state) {
state.status = '';
Cookies.remove('XSRF-TOKEN');
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
index b6adf9e..706d3ec 100644
--- a/src/views/Login/Login.vue
+++ b/src/views/Login/Login.vue
@@ -1,67 +1,125 @@
<template>
- <b-container class="login-container" fluid>
- <b-row class="login-row" align-v="center">
- <b-col class="login-branding mt-5 mb-5" md="6">
- <div class="login-branding__container">
- <img
- class="logo"
- width="200px"
- src="@/assets/images/openbmc-logo.svg"
- alt=""
- />
- <h1>OpenBMC</h1>
- </div>
- </b-col>
+ <main>
+ <b-container class="login-container" fluid>
+ <b-row class="login-row" align-v="center">
+ <b-col class="login-branding mt-5 mb-5" md="6">
+ <div class="login-branding__container">
+ <img
+ class="logo"
+ width="200px"
+ src="@/assets/images/openbmc-logo.svg"
+ alt=""
+ />
+ <h1>OpenBMC</h1>
+ </div>
+ </b-col>
- <b-col class="login-form" md="6">
- <b-form @submit.prevent="login">
- <b-form-group
- id="username-group"
- label="Username"
- label-for="username"
- >
- <b-form-input id="username" v-model="username" type="text" required>
- </b-form-input>
- </b-form-group>
-
- <b-form-group
- id="password-group"
- label="Password"
- label-for="password"
- >
- <b-form-input
- id="password"
- v-model="password"
- type="password"
- required
+ <b-col md="6">
+ <b-form class="login-form" @submit.prevent="login" novalidate>
+ <b-alert
+ class="login-error"
+ v-if="authStatus == 'error'"
+ show
+ variant="danger"
>
- </b-form-input>
- </b-form-group>
+ <p id="login-error-alert">
+ <strong>{{ errorMsg.title }}</strong>
+ <span v-if="errorMsg.action">{{ errorMsg.action }}</span>
+ </p>
+ </b-alert>
+ <div class="login-form__section">
+ <label for="username">Username</label>
+ <b-form-input
+ id="username"
+ v-model="userInfo.username"
+ :aria-describedby="
+ authStatus == 'error' ? 'login-error-alert' : ''
+ "
+ type="text"
+ required
+ autofocus="autofocus"
+ >
+ </b-form-input>
+ </div>
- <b-button type="submit" variant="primary">Login</b-button>
- </b-form>
- </b-col>
- </b-row>
- </b-container>
+ <div class="login-form__section">
+ <label for="password">Password</label>
+ <b-form-input
+ id="password"
+ v-model="userInfo.password"
+ :aria-describedby="
+ authStatus == 'error' ? 'login-error-alert' : ''
+ "
+ type="password"
+ required
+ >
+ </b-form-input>
+ </div>
+
+ <b-button
+ type="submit"
+ variant="primary"
+ :disabled="authStatus == 'processing'"
+ >Log in</b-button
+ >
+ </b-form>
+ </b-col>
+ </b-row>
+ </b-container>
+ </main>
</template>
<script>
export default {
name: 'Login',
+ computed: {
+ authStatus() {
+ return this.$store.getters['authentication/authStatus'];
+ }
+ },
data() {
return {
- username: '',
- password: ''
+ errorMsg: {
+ title: null,
+ action: null
+ },
+ userInfo: {
+ username: null,
+ password: null
+ },
+ disableSubmitButton: false
};
},
methods: {
+ resetState: function() {
+ this.errorMsg.title = null;
+ this.errorMsg.action = null;
+ this.$store.commit('authentication/authReset');
+ },
+ validateRequiredFields: function() {
+ if (!this.userInfo.username || !this.userInfo.password) {
+ this.$store.commit('authentication/authError');
+ }
+ },
login: function() {
- const username = this.username;
- const password = this.password;
- this.$store
- .dispatch('authentication/login', [username, password])
- .then(() => this.$router.push('/'))
- .catch(error => console.log(error));
+ this.resetState();
+ this.validateRequiredFields();
+ if (this.authStatus !== 'error') {
+ const username = this.userInfo.username;
+ const password = this.userInfo.password;
+ this.$store
+ .dispatch('authentication/login', [username, password])
+ .then(() => {
+ this.$router.push('/');
+ })
+ .catch(error => {
+ this.errorMsg.title = 'Invalid username or password.';
+ this.errorMsg.action = 'Please try again.';
+ console.log(error);
+ });
+ } else {
+ this.errorMsg.title = 'Username and password required.';
+ }
}
}
};
@@ -95,7 +153,7 @@
}
}
-.login-form form {
+.login-form {
max-width: 360px;
margin-right: auto;
margin-left: auto;
@@ -105,6 +163,30 @@
}
}
+.login-form__section {
+ margin-bottom: $spacer;
+}
+
+.login-error {
+ margin-bottom: $spacer * 2;
+
+ p {
+ margin-bottom: 0;
+ }
+
+ strong {
+ display: block;
+ font-size: 1rem;
+ font-weight: 600;
+ margin-bottom: 0;
+ }
+
+ strong + span {
+ margin-top: $spacer / 2;
+ margin-bottom: 0;
+ }
+}
+
.login-branding {
text-align: center;
}