Add login and logout functionality

- Add AuthenticationStore
- Add ability to login and logout
- Add route navigation guard
- Add login styles
- Add temporary authentication for api call
- Add Login directory
- Add index.js

In order to login a .env.development.local file that contains
BASE_URL="https://<ip address> or <FQDN>"

Signed-off-by: Derick Montague <derick.montague@ibm.com>
Change-Id: I88b93e287e66f4bae82a1ec2934cdef12d78264e
diff --git a/src/App.vue b/src/App.vue
index d0e1a0a..5667495 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,18 +1,6 @@
 <template>
   <div id="app">
-    <AppHeader />
-    <b-container fluid class="page-container">
-      <b-row no-gutters>
-        <b-col tag="nav" cols="12" md="3" lg="2">
-          <AppNavigation />
-        </b-col>
-        <b-col cols="12" md="9" lg="10">
-          <main id="#main-content">
-            <router-view />
-          </main>
-        </b-col>
-      </b-row>
-    </b-container>
+    <router-view />
   </div>
 </template>
 
@@ -21,22 +9,7 @@
 </style>
 
 <script>
-import AppHeader from "@/components/AppHeader";
-import AppNavigation from "@/components/AppNavigation";
 export default {
-  name: "App",
-  components: {
-    AppHeader,
-    AppNavigation
-  }
+  name: "App"
 };
 </script>
-
-<style lang="scss" scoped>
-.page-container {
-  margin-right: 0;
-  margin-left: 0;
-  padding-right: 0;
-  padding-left: 0;
-}
-</style>
diff --git a/src/assets/images/openbmc-logo.svg b/src/assets/images/openbmc-logo.svg
new file mode 100644
index 0000000..d0fa158
--- /dev/null
+++ b/src/assets/images/openbmc-logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 241.23 240.05"><defs><style>.cls-1{fill:#a6a8ab;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}.cls-4{fill:url(#linear-gradient-3);}.cls-5{fill:url(#linear-gradient-4);}.cls-6{fill:#626366;}</style><linearGradient id="linear-gradient" x1="82.9" y1="11.55" x2="82.9" y2="154.54" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00b0da"/><stop offset="1" stop-color="#008abf"/></linearGradient><linearGradient id="linear-gradient-2" x1="81.55" y1="27.55" x2="81.55" y2="158.66" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="156.66" y1="51.54" x2="156.66" y2="154.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a5d440"/><stop offset="1" stop-color="#8cce3f"/></linearGradient><linearGradient id="linear-gradient-4" x1="158.41" y1="51.54" x2="158.41" y2="154.8" xlink:href="#linear-gradient-3"/></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M241.23,205.77a18.66,18.66,0,1,0-.24,16L237.26,220a14.51,14.51,0,1,1,.21-12.46Z"/><path class="cls-2" d="M65.85,81.86a53.68,53.68,0,0,0,11.61,33.41c-.1.29-.15.6-.22.9a10.81,10.81,0,0,0-.34,2.57,11,11,0,1,0,11-11,10.75,10.75,0,0,0-1.2.07c-.31,0-.61.08-.91.13A42.82,42.82,0,0,1,99.95,43.86h0V2.07l-.77.21q-3.63.94-7.12,2.2c-1.29.47-2.58,1-3.84,1.48h0V38.19l-.13.1A53.79,53.79,0,0,0,65.85,81.86Z"/><path class="cls-3" d="M120.28,96.58a14.54,14.54,0,0,1-14.55-14.37H93.59v0a26.29,26.29,0,0,0,21,25.65v45.35A71.13,71.13,0,0,1,63.9,38.1c.31.06.63.1,1,.13s.64,0,1,0a10.83,10.83,0,1,0-10.25-7.41,82.23,82.23,0,0,0,64.18,133.6c1.41,0,2.81-.06,4.2-.14l1.63-.09h0V95.57A14.47,14.47,0,0,1,120.28,96.58Z"/><path class="cls-4" d="M171.95,68.54a53.78,53.78,0,0,0-9.85-19.71,11.31,11.31,0,0,0,.32-1.3,10.78,10.78,0,0,0,.24-2.17,11,11,0,1,0-8.89,10.8,42.83,42.83,0,0,1-14.17,64.08V162c1.08-.27,2.14-.56,3.2-.87a82.35,82.35,0,0,0,8.53-3V125.91a53.91,53.91,0,0,0,20.6-57.37Z"/><path class="cls-5" d="M184.63,132.75A82.21,82.21,0,0,0,119.79,0c-1.64,0-3.26.06-4.87.16h-.11V68.55h0A14.53,14.53,0,0,1,120,67.48h.27A14.56,14.56,0,0,1,134.87,82s0,.07,0,.11,0,.08,0,.13h11.08A26.21,26.21,0,0,0,125.81,56.8V11.3A71.14,71.14,0,0,1,176,125.83h-.07a11,11,0,0,0-12.58,10.88,11,11,0,0,0,11,11h0a11,11,0,0,0,10.54-14.13C184.82,133.3,184.73,133,184.63,132.75Z"/><polygon class="cls-1" points="201.22 231.42 201.22 195.65 196.53 195.65 182 225.14 167.47 195.65 162.72 195.65 162.72 231.42 166.89 231.42 166.89 204.21 180.33 231.42 183.67 231.42 197.1 204.21 197.1 231.42 201.22 231.42"/><path class="cls-6" d="M119.82,208.4a10.6,10.6,0,0,0-7.9-3.34,10.15,10.15,0,0,0-4.16.83,15.94,15.94,0,0,0-3.62,2.24v-2.7H99.91v26h4.23V216.64a7.74,7.74,0,0,1,2.08-5.5,7.48,7.48,0,0,1,10.66,0,7.76,7.76,0,0,1,2.08,5.48v14.78h4.11v-15a11.12,11.12,0,0,0-3.24-8"/><path class="cls-6" d="M63.68,224.4a12.41,12.41,0,0,1-4.86,5.17,13.54,13.54,0,0,1-7,1.85H45.54V240h-4V205.43H51.64a13.41,13.41,0,0,1,9.57,3.76,12.73,12.73,0,0,1,2.47,15.21m-18.14,3.24h5.77A9.48,9.48,0,0,0,58.05,225a8.59,8.59,0,0,0,2.76-6.54,8.38,8.38,0,0,0-2.7-6.41,9.43,9.43,0,0,0-6.68-2.51H45.54Z"/><path class="cls-6" d="M96.44,219.75a4.56,4.56,0,0,0,.14-1.36c0-7.38-6.27-13.36-14-13.36s-14,6-14,13.36,6.27,13.36,14,13.36a14,14,0,0,0,11.93-6.52l-3.25-2.45a9.89,9.89,0,0,1-8.68,5,9.43,9.43,0,1,1,0-18.83,9.8,9.8,0,0,1,9.35,6.54c0,.07.15.63.17.7H76.59v3.52Z"/><path class="cls-6" d="M33.19,213.53A14.53,14.53,0,1,1,18.66,199a14.53,14.53,0,0,1,14.53,14.53m4.12,0a18.66,18.66,0,1,0-18.66,18.66,18.66,18.66,0,0,0,18.66-18.66"/><path class="cls-1" d="M154.37,220.69a6.77,6.77,0,0,1-6.75,6.79H132.14V213.9h15.49a6.78,6.78,0,0,1,6.75,6.79m-2.29-15.93a5.08,5.08,0,0,1-5.05,5.08l-14.89,0V199.67H147a5.07,5.07,0,0,1,5.05,5.08m.94,6.7a9,9,0,0,0-5.69-15.75H128v35.75h20.14l.28,0v0A10.73,10.73,0,0,0,153,211.46"/></g></g></svg>
\ No newline at end of file
diff --git a/src/assets/styles/_obmc-custom.scss b/src/assets/styles/_obmc-custom.scss
index 45dec1f..7deb6cb 100644
--- a/src/assets/styles/_obmc-custom.scss
+++ b/src/assets/styles/_obmc-custom.scss
@@ -1,16 +1,3 @@
-// Major breakpoints using 16px base em * 16
-// xs: 0, sm: 768px, md: 1024px, lg: 1376px, xl: 1600px
-// Using em's will allow for changes if base font size is updated
-// to accommodate for responsive text. Using 0 as xs due to
-// Bootstrap requirements.
-$grid-breakpoints: (
-  xs: 0,
-  sm: 48em,
-  md: 64em,
-  lg: 86em,
-  xl: 100em
-);
-
 // Required
 @import "~bootstrap/scss/functions";
 @import "./functions";
diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue
index 9177403..8dacd03 100644
--- a/src/components/AppHeader/AppHeader.vue
+++ b/src/components/AppHeader/AppHeader.vue
@@ -7,9 +7,9 @@
           <b-nav-text>BMC System Management</b-nav-text>
         </b-navbar-nav>
         <b-navbar-nav small class="ml-auto">
-          <b-nav-item>
+          <b-nav-item @click="logout">
             <user-avatar-20 />
-            User Avatar
+            Logout
           </b-nav-item>
         </b-navbar-nav>
       </b-navbar>
@@ -77,6 +77,11 @@
   methods: {
     getHostInfo() {
       this.$store.dispatch("global/getHostName");
+    },
+    logout() {
+      this.$store.dispatch("authentication/logout").then(() => {
+        this.$router.push("/login");
+      });
     }
   }
 };
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
new file mode 100644
index 0000000..dcfb52e
--- /dev/null
+++ b/src/layouts/AppLayout.vue
@@ -0,0 +1,38 @@
+<template>
+  <div>
+    <AppHeader />
+    <b-container fluid class="page-container">
+      <b-row no-gutters>
+        <b-col tag="nav" cols="12" md="3" lg="2">
+          <AppNavigation />
+        </b-col>
+        <b-col cols="12" md="9" lg="10">
+          <main id="#main-content">
+            <router-view />
+          </main>
+        </b-col>
+      </b-row>
+    </b-container>
+  </div>
+</template>
+
+<script>
+import AppHeader from "@/components/AppHeader";
+import AppNavigation from "@/components/AppNavigation";
+export default {
+  name: "App",
+  components: {
+    AppHeader,
+    AppNavigation
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  margin-right: 0;
+  margin-left: 0;
+  padding-right: 0;
+  padding-left: 0;
+}
+</style>
diff --git a/src/main.js b/src/main.js
index e18ce89..6f4acc6 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,7 +7,10 @@
   BadgePlugin,
   ButtonPlugin,
   CollapsePlugin,
+  FormPlugin,
   FormCheckboxPlugin,
+  FormGroupPlugin,
+  FormInputPlugin,
   LayoutPlugin,
   LinkPlugin,
   ListGroupPlugin,
@@ -22,7 +25,11 @@
 Vue.use(BadgePlugin);
 Vue.use(ButtonPlugin);
 Vue.use(CollapsePlugin);
+Vue.use(FormPlugin);
 Vue.use(FormCheckboxPlugin);
+Vue.use(FormGroupPlugin);
+Vue.use(FormInputPlugin);
+Vue.use(LayoutPlugin);
 Vue.use(LayoutPlugin);
 Vue.use(LinkPlugin);
 Vue.use(ListGroupPlugin);
diff --git a/src/router/index.js b/src/router/index.js
index 11d1a47..698aa70 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,28 +1,35 @@
 import Vue from "vue";
 import VueRouter from "vue-router";
+import store from "../store/index";
+import AppLayout from "../layouts/AppLayout.vue";
 
 Vue.use(VueRouter);
 
 const routes = [
   {
     path: "/",
-    name: "overview",
-    component: () => import("@/views/Overview")
+    name: "",
+    meta: {
+      requiresAuth: true
+    },
+    component: AppLayout,
+    children: [
+      {
+        path: "",
+        component: () => import("@/views/Overview")
+      },
+      {
+        path: "/access-control/local-user-management",
+        name: "local-users",
+        component: () => import("@/views/AccessControl/LocalUserManagement")
+      }
+    ]
   },
   {
-    path: "/access-control/local-user-management",
-    name: "local-users",
-    component: () => import("@/views/AccessControl/LocalUserManagement")
+    path: "/login",
+    name: "login",
+    component: () => import("@/views/Login")
   }
-  // {
-  //   path: "/about",
-  //   name: "about",
-  //   // route level code-splitting
-  //   // this generates a separate chunk (about.[hash].js) for this route
-  //   // which is lazy-loaded when the route is visited.
-  //   component: () =>
-  //     import(/* webpackChunkName: "about" */ "../views/About.vue")
-  // }
 ];
 
 const router = new VueRouter({
@@ -32,4 +39,16 @@
   linkExactActiveClass: "nav__link--current"
 });
 
+router.beforeEach((to, from, next) => {
+  if (to.matched.some(record => record.meta.requiresAuth)) {
+    if (store.getters["authentication/isLoggedIn"]) {
+      next();
+      return;
+    }
+    next("/login");
+  } else {
+    next();
+  }
+});
+
 export default router;
diff --git a/src/store/api.js b/src/store/api.js
index d40ad0a..39a6355 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -2,15 +2,9 @@
 
 const api = Axios.create();
 
-// TODO: this is a temporary workaround until
-// authentication with login is working
-const username = process.env.VUE_APP_USERNAME;
-const password = process.env.VUE_APP_PASSWORD;
-if (username && password) {
-  api.defaults.auth = {};
-  api.defaults.auth.username = username;
-  api.defaults.auth.password = password;
-}
+// TODO: Permanent authentication solution
+// Using defaults to set auth for sending
+// auth object in header
 
 export default {
   get(path) {
@@ -30,5 +24,6 @@
   },
   all(promises) {
     return Axios.all(promises);
-  }
+  },
+  defaults: api.defaults
 };
diff --git a/src/store/index.js b/src/store/index.js
index af06a47..b4be6cc 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,8 +1,9 @@
 import Vue from "vue";
 import Vuex from "vuex";
 
-import LocalUserManagementStore from "./modules/AccessControl/LocalUserMangementStore";
 import GlobalStore from "./modules/GlobalStore";
+import AuthenticationStore from "./modules/Authentication/AuthenticanStore";
+import LocalUserManagementStore from "./modules/AccessControl/LocalUserMangementStore";
 
 Vue.use(Vuex);
 
@@ -12,6 +13,7 @@
   actions: {},
   modules: {
     global: GlobalStore,
+    authentication: AuthenticationStore,
     localUsers: LocalUserManagementStore
   }
 });
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
new file mode 100644
index 0000000..828c3cc
--- /dev/null
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -0,0 +1,56 @@
+import api from "../../api";
+
+const AuthenticationStore = {
+  namespaced: true,
+  state: {
+    auth: {},
+    status: "",
+    token: sessionStorage.getItem("token") || ""
+  },
+  getters: {
+    authStatus: state => state.status,
+    isLoggedIn: state => !!state.token
+  },
+  mutations: {
+    authRequest(state) {
+      state.status = "loading";
+    },
+    authSuccess(state, token, auth) {
+      state.status = "authenicated";
+      state.auth = auth;
+      state.token = token;
+    },
+    authError(state) {
+      state.status = "error";
+    },
+    logout(state) {
+      state.status = "";
+      state.token = "";
+    }
+  },
+  actions: {
+    login({ commit }, auth) {
+      commit("authRequest");
+      return api
+        .post("/login", auth)
+        .then(response => {
+          const token = response.data.token;
+          sessionStorage.setItem("token", token);
+          api.defaults.auth = auth; // TODO Permanent Solution
+          commit("authSuccess", token, auth);
+        })
+        .catch(error => {
+          commit("authError");
+          sessionStorage.removeItem("token");
+          throw new Error(error);
+        });
+    },
+    logout({ commit }) {
+      commit("logout");
+      sessionStorage.removeItem("token");
+      api.defaults.auth = {}; // Permanent solution
+    }
+  }
+};
+
+export default AuthenticationStore;
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
new file mode 100644
index 0000000..f122d28
--- /dev/null
+++ b/src/views/Login/Login.vue
@@ -0,0 +1,111 @@
+<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>
+
+      <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-form-input>
+          </b-form-group>
+
+          <b-button type="submit" variant="primary">Login</b-button>
+        </b-form>
+      </b-col>
+    </b-row>
+  </b-container>
+</template>
+
+<script>
+export default {
+  name: "Login",
+  data() {
+    return {
+      username: "",
+      password: ""
+    };
+  },
+  methods: {
+    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));
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "~bootstrap/scss/functions";
+@import "~bootstrap/scss/variables";
+@import "~bootstrap/scss/mixins";
+
+.login-container {
+  @include media-breakpoint-up(md) {
+    background: linear-gradient(
+      to right,
+      var(--light) 50%,
+      var(--secondary-light) 50%
+    );
+  }
+}
+
+.login-row {
+  @include media-breakpoint-up(md) {
+    min-height: 100vh;
+  }
+}
+
+.login-branding__container {
+  @include media-breakpoint-up(md) {
+    float: right;
+    margin-right: 4rem;
+  }
+}
+
+.login-form form {
+  max-width: 360px;
+  margin-right: auto;
+  margin-left: auto;
+
+  @include media-breakpoint-up(md) {
+    margin-left: 4rem;
+  }
+}
+
+.login-branding {
+  text-align: center;
+}
+</style>
diff --git a/src/views/Login/index.js b/src/views/Login/index.js
new file mode 100644
index 0000000..7fc2b5c
--- /dev/null
+++ b/src/views/Login/index.js
@@ -0,0 +1,2 @@
+import Login from "./Login.vue";
+export default Login;