changeset 182:dccfce4148fc

Extract "login" state and component from auth Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-August/024745.html Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/024866.html
author Andrew Azores <aazores@redhat.com>
date Thu, 07 Sep 2017 13:56:19 -0400
parents 8d3c8addabad
children cc56f2e71a24
files src/app/app.controller.js src/app/app.module.js src/app/app.module.spec.js src/app/auth-interceptor.factory.js src/app/components/about/about.component.js src/app/components/auth/auth.module.js src/app/components/auth/auth.routing.js src/app/components/auth/en.locale.yaml src/app/components/auth/login.controller.js src/app/components/auth/login.controller.spec.js src/app/components/auth/login.html src/app/components/auth/login/en.locale.yaml src/app/components/auth/login/login.component.js src/app/components/auth/login/login.controller.js src/app/components/auth/login/login.controller.spec.js src/app/components/auth/login/login.html src/app/components/auth/login/login.routing.js src/app/components/auth/login/login.routing.spec.js src/app/shared/services/services.module.js
diffstat 19 files changed, 400 insertions(+), 301 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/app.controller.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/app.controller.js	Thu Sep 07 13:56:19 2017 -0400
@@ -25,7 +25,7 @@
  * exception statement from your version.
  */
 
-import authModule from './components/auth/auth.module.js';
+import authModule from 'components/auth/auth.module.js';
 
 class AppController {
   constructor ($scope, environment, $state, authService) {
--- a/src/app/app.module.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/app.module.js	Thu Sep 07 13:56:19 2017 -0400
@@ -35,7 +35,7 @@
 import 'bootstrap-switch';
 
 import configModule from 'shared/config/config.module.js';
-import {default as authModule, config as authModBootstrap} from './components/auth/auth.module.js';
+import {default as authModule, config as authModBootstrap} from 'components/auth/auth.module.js';
 import filters from 'shared/filters/filters.module.js';
 import services from 'shared/services/services.module.js';
 import directives from 'shared/directives/directives.module.js';
--- a/src/app/app.module.spec.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/app.module.spec.js	Thu Sep 07 13:56:19 2017 -0400
@@ -76,12 +76,4 @@
     });
   });
 
-  it('should provide authModule', () => {
-    inject(AUTH_MODULE => {
-      'ngInject';
-      should.exist(AUTH_MODULE);
-      should.exist(angular.module(AUTH_MODULE));
-    });
-  });
-
 });
--- a/src/app/auth-interceptor.factory.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/auth-interceptor.factory.js	Thu Sep 07 13:56:19 2017 -0400
@@ -25,7 +25,7 @@
  * exception statement from your version.
  */
 
-import authModule from './components/auth/auth.module.js';
+import authModule from 'components/auth/auth.module.js';
 
 let name = 'authInterceptorFactory';
 
--- a/src/app/components/about/about.component.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/components/about/about.component.js	Thu Sep 07 13:56:19 2017 -0400
@@ -25,10 +25,14 @@
  * exception statement from your version.
  */
 
+import authModule from 'components/auth/auth.module.js';
 import controller from './about.controller.js';
 
 export default angular
-  .module('aboutComponent', [controller])
+  .module('aboutComponent', [
+    authModule,
+    controller
+  ])
   .component('about', {
     bindings: {
       username: '<'
--- a/src/app/components/auth/auth.module.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/components/auth/auth.module.js	Thu Sep 07 13:56:19 2017 -0400
@@ -29,7 +29,6 @@
 
 import KeycloakAuthService from './keycloak-auth.service.js';
 import BasicAuthService from './basic-auth.service.js';
-import LoginController from './login.controller.js';
 
 let MOD_NAME = 'authModule';
 export default MOD_NAME;
@@ -49,8 +48,6 @@
 }) {
   let mod = angular.module(MOD_NAME, ['ui.router']);
 
-  mod.constant('AUTH_MODULE', MOD_NAME);
-  mod.controller('LoginController', LoginController);
   mod.run((authService, $rootScope) => {
     'ngInject';
     authService.rootScope = $rootScope;
--- a/src/app/components/auth/auth.routing.js	Thu Sep 07 08:10:36 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/**
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * Thermostat is distributed under the GNU General Public License,
- * version 2 or any later version (with a special exception described
- * below, commonly known as the "Classpath Exception").
- *
- * A copy of GNU General Public License (GPL) is included in this
- * distribution, in the file COPYING.
- *
- * Linking Thermostat code with other modules is making a combined work
- * based on Thermostat.  Thus, the terms and conditions of the GPL
- * cover the whole combination.
- *
- * As a special exception, the copyright holders of Thermostat give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent
- * modules, and to copy and distribute the resulting executable under
- * terms of your choice, provided that you also meet, for each linked
- * independent module, the terms and conditions of the license of that
- * module.  An independent module is a module which is not derived from
- * or based on Thermostat code.  If you modify Thermostat, you may
- * extend this exception to your version of the software, but you are
- * not obligated to do so.  If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-function authRouting ($stateProvider, $urlRouterProvider) {
-  'ngInject';
-  // define initial state behaviour
-  $urlRouterProvider.when('', '/');
-
-  $stateProvider
-    .state('/', {
-      url:'/'
-    })
-    .state('login', {
-      url: '/login',
-      template: require('./login.html'),
-      controller: 'LoginController'
-    });
-}
-
-export default angular
-  .module('auth.routing', ['ui.router'])
-  .config(authRouting)
-  .name;
--- a/src/app/components/auth/en.locale.yaml	Thu Sep 07 08:10:36 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-auth:
-  USERNAME: Username
-  PASSWORD: Password
-  REMEMBER_USERNAME_LABEL: Remember username
-  LOGIN_BUTTON_LABEL: Log In
-  WELCOME_TEXT: Welcome to Thermostat | JVM Instrumentation Tool
-  FORGOT_CREDENTIALS: Forgot <a href="" tabindex="5">username</a> or <a href="" tabindex="6">password</a>?
--- a/src/app/components/auth/login.controller.js	Thu Sep 07 08:10:36 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * Thermostat is distributed under the GNU General Public License,
- * version 2 or any later version (with a special exception described
- * below, commonly known as the "Classpath Exception").
- *
- * A copy of GNU General Public License (GPL) is included in this
- * distribution, in the file COPYING.
- *
- * Linking Thermostat code with other modules is making a combined work
- * based on Thermostat.  Thus, the terms and conditions of the GPL
- * cover the whole combination.
- *
- * As a special exception, the copyright holders of Thermostat give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent
- * modules, and to copy and distribute the resulting executable under
- * terms of your choice, provided that you also meet, for each linked
- * independent module, the terms and conditions of the license of that
- * module.  An independent module is a module which is not derived from
- * or based on Thermostat code.  If you modify Thermostat, you may
- * extend this exception to your version of the software, but you are
- * not obligated to do so.  If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-export default class LoginController {
-
-  constructor ($scope, $state, authService) {
-    'ngInject';
-
-    if (authService.status()) {
-      $state.go('landing');
-      return;
-    }
-
-    $scope.rememberUser = angular.isDefined(authService.rememberedUsername);
-    if ($scope.rememberUser) {
-      $scope.username = authService.rememberedUsername;
-    }
-
-    $scope.login = () => {
-      authService.rememberUser($scope.rememberUser);
-      authService.login($scope.username, $scope.password, () => $state.go('landing'));
-    };
-  }
-
-}
--- a/src/app/components/auth/login.controller.spec.js	Thu Sep 07 08:10:36 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/**
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * Thermostat is distributed under the GNU General Public License,
- * version 2 or any later version (with a special exception described
- * below, commonly known as the "Classpath Exception").
- *
- * A copy of GNU General Public License (GPL) is included in this
- * distribution, in the file COPYING.
- *
- * Linking Thermostat code with other modules is making a combined work
- * based on Thermostat.  Thus, the terms and conditions of the GPL
- * cover the whole combination.
- *
- * As a special exception, the copyright holders of Thermostat give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent
- * modules, and to copy and distribute the resulting executable under
- * terms of your choice, provided that you also meet, for each linked
- * independent module, the terms and conditions of the license of that
- * module.  An independent module is a module which is not derived from
- * or based on Thermostat code.  If you modify Thermostat, you may
- * extend this exception to your version of the software, but you are
- * not obligated to do so.  If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-describe('LoginController', () => {
-
-  beforeEach(angular.mock.module($provide => {
-    'ngInject';
-    $provide.value('$transitions', { onBefore: angular.noop });
-  }));
-
-  beforeEach(angular.mock.module('appModule'));
-
-  describe('$scope.login()', () => {
-    let scope, authService, stateGo, alert;
-    beforeEach(inject(($controller, $rootScope) => {
-      'ngInject';
-
-      scope = $rootScope.$new();
-
-      authService = {
-        status: sinon.stub().returns(false),
-        login: sinon.stub().yields(),
-        rememberUser: sinon.spy()
-      };
-
-      stateGo = sinon.spy();
-      alert = sinon.spy(window, 'alert');
-
-      $controller('LoginController', {
-        $scope: scope,
-        $state: { go: stateGo },
-        authService: authService
-      });
-    }));
-
-    afterEach(() => {
-      alert.restore();
-    });
-
-    it('should be supplied', () => {
-      scope.should.have.ownProperty('login');
-    });
-
-    it('should be a function', () => {
-      scope.login.should.be.a.Function();
-    });
-
-    it('should set remember user on authService if set in scope', () => {
-      authService.login.should.not.be.called();
-      stateGo.should.not.be.called();
-
-      scope.rememberUser = true;
-      scope.login();
-      authService.login.yield();
-      authService.rememberUser.should.be.calledOnce();
-      authService.rememberUser.should.be.calledWith(true);
-    });
-
-  });
-
-  describe('when logged in', () => {
-    let scope, authService, stateGo;
-    beforeEach(inject(($controller, $rootScope) => {
-      'ngInject';
-
-      scope = $rootScope.$new();
-
-      authService = {
-        status: sinon.stub().returns(true)
-      };
-      stateGo = sinon.spy();
-
-      $controller('LoginController', {
-        $scope: scope,
-        $state: { go: stateGo },
-        authService: authService
-      });
-    }));
-
-    it('should redirect to landing if already logged in', () => {
-      authService.status.should.be.calledOnce();
-      stateGo.should.be.calledWith('landing');
-    });
-  });
-
-  describe('stored username fill', () => {
-    let scope, authService, stateGo;
-    beforeEach(inject(($controller, $rootScope) => {
-      'ngInject';
-
-      scope = $rootScope.$new();
-
-      authService = {
-        status: sinon.stub().returns(false),
-        rememberedUsername: 'foo-user'
-      };
-      stateGo = sinon.spy();
-
-      $controller('LoginController', {
-        $scope: scope,
-        $state: { go: stateGo },
-        authService: authService
-      });
-    }));
-
-    it('should fill username from authService if available', () => {
-      scope.should.have.ownProperty('username');
-      scope.username.should.equal('foo-user');
-      scope.should.have.ownProperty('rememberUser');
-      scope.rememberUser.should.be.true();
-    });
-  });
-
-});
--- a/src/app/components/auth/login.html	Thu Sep 07 08:10:36 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-<div class="login-pf">
-  <span id="badge">
-    <img src="~images/thermostat_icon_64px.svg" alt=" logo" />
-  </span>
-  <div class="container">
-    <div class="row">
-      <div class="col-sm-12">
-        <div id="brand">
-          <img src="~images/thermostat_logotype_600px.svg" alt="Thermostat"/>
-        </div><!--/#brand-->
-      </div><!--/.col-*-->
-      <div class="col-sm-7 col-md-6 col-lg-5 login">
-        <form class="form-horizontal" role="form">
-          <div class="form-group">
-            <label for="inputUsername" class="col-sm-2 col-md-2 control-label" translate>auth.USERNAME</label>
-            <div class="col-sm-10 col-md-10">
-              <input type="text" class="form-control" id="username" ng-model="username" placeholder="" tabindex="1"/>
-            </div>
-          </div>
-          <div class="form-group">
-            <label for="inputPassword" class="col-sm-2 col-md-2 control-label" translate>auth.PASSWORD</label>
-            <div class="col-sm-10 col-md-10">
-              <input type="password" class="form-control" id="password" ng-model="password" placeholder="" tabindex="2"/>
-            </div>
-          </div>
-          <div class="form-group">
-            <div class="col-xs-8 col-sm-offset-2 col-sm-6 col-md-offset-2 col-md-6">
-              <input name="rememberUser" type="checkbox" tabindex="3" ng-model="rememberUser"/>
-              <label for="rememberUser" translate>auth.REMEMBER_USERNAME_LABEL</label>
-              <span class="help-block" translate>auth.FORGOT_CREDENTIALS</span>
-            </div>
-            <div class="col-xs-4 col-sm-4 col-md-4 submit">
-              <button id="kc-login" type="submit" class="btn btn-primary btn-lg" ng-click="login()" tabindex="4" translate>auth.LOGIN_BUTTON_LABEL</button>
-            </div>
-          </div>
-        </form>
-      </div><!--/.col-*-->
-      <div class="col-sm-5 col-md-6 col-lg-7 details">
-        <p><strong translate>auth.WELCOME_TEXT</strong>
-          <br/>Thermostat NG ver. 1.0.0</p>
-      </div><!--/.col-*-->
-    </div><!--/.row-->
-  </div><!--/.container-->
-</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/en.locale.yaml	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,7 @@
+auth:
+  USERNAME: Username
+  PASSWORD: Password
+  REMEMBER_USERNAME_LABEL: Remember username
+  LOGIN_BUTTON_LABEL: Log In
+  WELCOME_TEXT: Welcome to Thermostat | JVM Instrumentation Tool
+  FORGOT_CREDENTIALS: Forgot <a href="" tabindex="5">username</a> or <a href="" tabindex="6">password</a>?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.component.js	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+import controller from './login.controller.js';
+
+export default angular
+  .module('loginComponent', [controller])
+  .component('login', {
+    controller: 'LoginController',
+    template: require('./login.html')
+  })
+  .name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.controller.js	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+import authModule from 'components/auth/auth.module.js';
+
+class LoginController {
+  constructor ($state, authService) {
+    'ngInject';
+    this._state = $state;
+    this._authService = authService;
+
+    if (authService.status()) {
+      $state.go('landing');
+      return;
+    }
+
+    this.rememberUser = angular.isDefined(authService.rememberedUsername);
+    if (this.rememberUser) {
+      this.username = authService.rememberedUsername;
+    }
+  }
+
+  login () {
+    this._authService.rememberUser(this.rememberUser);
+    this._authService.login(this.username, this.password, () => this._state.go('landing'));
+  }
+}
+
+export default angular
+  .module('login.controller', [authModule])
+  .controller('LoginController', LoginController)
+  .name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.controller.spec.js	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,111 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+import controllerModule from './login.controller.js';
+
+describe('LoginController', () => {
+
+  beforeEach(angular.mock.module(controllerModule));
+
+  describe('#login ()', () => {
+    let ctrl, authService, state;
+    beforeEach(inject($controller => {
+      'ngInject';
+
+      authService = {
+        status: sinon.stub().returns(false),
+        login: sinon.stub().yields(),
+        rememberUser: sinon.spy()
+      };
+
+      state = { go: sinon.spy() };
+
+      ctrl = $controller('LoginController', {
+        $state: state,
+        authService: authService
+      });
+    }));
+
+    it('should set remember user on authService if set on controller', () => {
+      authService.login.should.not.be.called();
+      state.go.should.not.be.called();
+
+      ctrl.rememberUser = true;
+      ctrl.login();
+      authService.login.yield();
+      authService.rememberUser.should.be.calledOnce();
+      authService.rememberUser.should.be.calledWith(true);
+    });
+
+  });
+
+  describe('when logged in', () => {
+    let authService, state;
+    beforeEach(inject($controller => {
+      'ngInject';
+
+      authService = { status: sinon.stub().returns(true) };
+      state = { go: sinon.spy() };
+
+      $controller('LoginController', {
+        $state: state,
+        authService: authService
+      });
+    }));
+
+    it('should redirect to landing if already logged in', () => {
+      authService.status.should.be.calledOnce();
+      state.go.should.be.calledWith('landing');
+    });
+  });
+
+  describe('stored username fill', () => {
+    let ctrl, authService, state;
+    beforeEach(inject($controller => {
+      'ngInject';
+
+      authService = {
+        status: sinon.stub().returns(false),
+        rememberedUsername: 'foo-user'
+      };
+      state = { go: sinon.spy() };
+
+      ctrl = $controller('LoginController', {
+        $state: state,
+        authService: authService
+      });
+    }));
+
+    it('should fill username from authService if available', () => {
+      ctrl.should.have.ownProperty('username');
+      ctrl.username.should.equal('foo-user');
+      ctrl.should.have.ownProperty('rememberUser');
+      ctrl.rememberUser.should.be.true();
+    });
+  });
+
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.html	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,44 @@
+<div class="login-pf">
+  <span id="badge">
+    <img src="~images/thermostat_icon_64px.svg" alt=" logo" />
+  </span>
+  <div class="container">
+    <div class="row">
+      <div class="col-sm-12">
+        <div id="brand">
+          <img src="~images/thermostat_logotype_600px.svg" alt="Thermostat"/>
+        </div>
+      </div>
+      <div class="col-sm-7 col-md-6 col-lg-5 login">
+        <form class="form-horizontal" role="form">
+          <div class="form-group">
+            <label for="inputUsername" class="col-sm-2 col-md-2 control-label" translate>auth.USERNAME</label>
+            <div class="col-sm-10 col-md-10">
+              <input type="text" class="form-control" id="username" ng-model="$ctrl.username" placeholder="" tabindex="1"/>
+            </div>
+          </div>
+          <div class="form-group">
+            <label for="inputPassword" class="col-sm-2 col-md-2 control-label" translate>auth.PASSWORD</label>
+            <div class="col-sm-10 col-md-10">
+              <input type="password" class="form-control" id="password" ng-model="$ctrl.password" placeholder="" tabindex="2"/>
+            </div>
+          </div>
+          <div class="form-group">
+            <div class="col-xs-8 col-sm-offset-2 col-sm-6 col-md-offset-2 col-md-6">
+              <input name="rememberUser" type="checkbox" tabindex="3" ng-model="$ctrl.rememberUser"/>
+              <label for="rememberUser" translate>auth.REMEMBER_USERNAME_LABEL</label>
+              <span class="help-block" translate>auth.FORGOT_CREDENTIALS</span>
+            </div>
+            <div class="col-xs-4 col-sm-4 col-md-4 submit">
+              <button id="kc-login" type="submit" class="btn btn-primary btn-lg" ng-click="$ctrl.login()" tabindex="4" translate>auth.LOGIN_BUTTON_LABEL</button>
+            </div>
+          </div>
+        </form>
+      </div>
+      <div class="col-sm-5 col-md-6 col-lg-7 details">
+        <p><strong translate>auth.WELCOME_TEXT</strong>
+          <br/>Thermostat NG ver. 1.0.0</p>
+      </div>
+    </div>
+  </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.routing.js	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+function config ($urlRouterProvider, $stateProvider) {
+  'ngInject';
+  $urlRouterProvider.when('', '/landing');
+
+  $stateProvider.state('login', {
+    url: '/login',
+    component: 'login',
+    resolve: {
+      lazyLoad: ($q, $ocLazyLoad) => {
+        'ngInject';
+        return $q(resolve => {
+          require.ensure(['./login.component.js'], () => {
+            let module = require('./login.component.js');
+            $ocLazyLoad.load({ name: module.default });
+            resolve(module);
+          });
+        });
+      }
+    }
+  });
+}
+
+export { config };
+
+export default angular
+  .module('login.routing', ['ui.router'])
+  .config(config)
+  .name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/components/auth/login/login.routing.spec.js	Thu Sep 07 13:56:19 2017 -0400
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+describe('LoginRouting', () => {
+
+  let module = require('./login.routing.js');
+
+  let urlRouterProvider, stateProvider, q, ocLazyLoad;
+  beforeEach(() => {
+    urlRouterProvider = { when: sinon.spy() };
+    stateProvider = { state: sinon.spy() };
+    module.config(urlRouterProvider, stateProvider);
+    q = sinon.spy();
+    ocLazyLoad = { load: sinon.spy() };
+  });
+
+  describe('urlRouterProvider', () => {
+    it('should redirect blank path to landing', () => {
+      urlRouterProvider.when.should.be.calledWith('', '/landing');
+    });
+  });
+
+  describe('stateProvider', () => {
+    it('should call $stateProvider.state', () => {
+      stateProvider.state.should.be.calledOnce();
+    });
+
+    it('should define a \'login\' state', () => {
+      stateProvider.state.args[0][0].should.equal('login');
+    });
+
+    it('should map to /login', () => {
+      stateProvider.state.args[0][1].url.should.equal('/login');
+    });
+
+    it('resolve should load login component', done => {
+      let resolveFn = stateProvider.state.args[0][1].resolve.lazyLoad[2];
+      resolveFn.should.be.a.Function();
+      resolveFn(q, ocLazyLoad);
+      q.should.be.calledOnce();
+
+      let deferred = q.args[0][0];
+      deferred.should.be.a.Function();
+
+      let resolve = sinon.stub().callsFake(val => {
+        ocLazyLoad.load.should.be.calledWith({ name: require('./login.component.js').default});
+        val.should.equal(require('./login.component.js'));
+        done();
+      });
+      deferred(resolve);
+    });
+  });
+
+});
--- a/src/app/shared/services/services.module.js	Thu Sep 07 08:10:36 2017 -0400
+++ b/src/app/shared/services/services.module.js	Thu Sep 07 13:56:19 2017 -0400
@@ -25,10 +25,14 @@
  * exception statement from your version.
  */
 
+import authModule from 'components/auth/auth.module.js';
 import configModule from 'shared/config/config.module.js';
 
 export default angular
-  .module('app.services', [configModule])
+  .module('app.services', [
+    authModule,
+    configModule
+  ])
   .name;
 
 let req = require.context('./', true, /\.service\.js/);