From f29a24ad68421c74d44b3df3a0989cd296d7550c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 21 Apr 2015 12:02:01 -0700 Subject: [PATCH] GUAC-1161: Convert login page to directive which accepts a dynamic form. Display login directive when credentials are needed. --- .../index/config/indexInterceptorConfig.js | 31 ------ .../app/index/config/indexRouteConfig.js | 9 -- .../app/index/controllers/indexController.js | 23 +++++ .../services/authenticationInterceptor.js | 63 ------------ .../app/login/controllers/loginController.js | 71 -------------- .../main/webapp/app/login/directives/login.js | 96 +++++++++++++++++++ .../src/main/webapp/app/login/loginModule.js | 6 +- .../main/webapp/app/login/styles/login.css | 34 +++++++ .../webapp/app/login/templates/login.html | 48 +++++----- .../app/navigation/directives/guacUserMenu.js | 10 +- guacamole/src/main/webapp/index.html | 36 ++++--- .../src/main/webapp/translations/en_US.json | 8 +- 12 files changed, 214 insertions(+), 221 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js delete mode 100644 guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js delete mode 100644 guacamole/src/main/webapp/app/login/controllers/loginController.js create mode 100644 guacamole/src/main/webapp/app/login/directives/login.js diff --git a/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js b/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js deleted file mode 100644 index 8ceb6c09f..000000000 --- a/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The config block for setting up the authentication interceptor. - */ -angular.module('index').config(['$httpProvider', - function indexInterceptorConfig($httpProvider) { - $httpProvider.interceptors.push('authenticationInterceptor'); -}]); - - diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index 3d51f4b52..6942537ba 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -161,15 +161,6 @@ angular.module('index').config(['$routeProvider', '$locationProvider', resolve : { updateCurrentToken: updateCurrentToken } }) - // Login screen - .when('/login/', { - title : 'APP.NAME', - bodyClassName : 'login', - templateUrl : 'app/login/templates/login.html', - controller : 'loginController' - // No need to update token here - the login screen ignores all auth - }) - // Client view .when('/client/:type/:id/:params?', { bodyClassName : 'client', diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 75b38c1fe..8d9b5d5e8 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -36,6 +36,14 @@ angular.module('index').controller('indexController', ['$scope', '$injector', */ $scope.guacNotification = guacNotification; + /** + * The credentials that the authentication service is currently expecting, + * if any. If the user is logged in, this will be null. + * + * @type Form[]|Form|Field[]|Field + */ + $scope.expectedCredentials = null; + /** * Basic page-level information. */ @@ -92,6 +100,21 @@ angular.module('index').controller('indexController', ['$scope', '$injector', keyboard.reset(); }; + // Display login screen if a whole new set of credentials is needed + $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, expected) { + $scope.expectedCredentials = expected; + }); + + // Prompt for remaining credentials if provided credentials were not enough + $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, expected) { + // TODO: Implement insufficient credential prompting + }); + + // Clear login screen if login was successful + $scope.$on('guacLogin', function loginSuccessful() { + $scope.expectedCredentials = null; + }); + // Update title and CSS class upon navigation $scope.$on('$routeChangeSuccess', function(event, current, previous) { diff --git a/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js b/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js deleted file mode 100644 index 7750243f7..000000000 --- a/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -angular.module('index').factory('authenticationInterceptor', ['$injector', - function authenticationInterceptor($injector) { - - // Required services - var $location = $injector.get('$location'); - var $q = $injector.get('$q'); - - var service = {}; - - /** - * Redirect users to login if authorization fails. This is called - * automatically when this service is registered as an interceptor, as - * documented at: - * - * https://docs.angularjs.org/api/ng/service/$http#interceptors - * - * @param {HttpPromise} rejection - * The promise associated with the HTTP request that failed. - * - * @returns {Promise} - * A rejected promise containing the originally-rejected HttpPromise. - */ - service.responseError = function responseError(rejection) { - - // Only redirect failed authentication requests - if ((rejection.status === 401 || rejection.status === 403) - && rejection.config.url === 'api/tokens') { - - // Only redirect if not already on login page - if ($location.path() !== '/login/') - $location.path('/login/'); - - } - - return $q.reject(rejection); - - }; - - return service; - -}]); diff --git a/guacamole/src/main/webapp/app/login/controllers/loginController.js b/guacamole/src/main/webapp/app/login/controllers/loginController.js deleted file mode 100644 index e43faf3ca..000000000 --- a/guacamole/src/main/webapp/app/login/controllers/loginController.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -angular.module('login').controller('loginController', ['$scope', '$injector', - function loginController($scope, $injector) { - - // Required services - var $location = $injector.get('$location'); - var authenticationService = $injector.get('authenticationService'); - var userPageService = $injector.get('userPageService'); - - /** - * Whether an error occurred during login. - * - * @type Boolean - */ - $scope.loginError = false; - - /** - * Whether the password field has focus. - * - * @type Boolean - */ - $scope.passwordFocused = false; - - /** - * Submits the currently-specified username and password to the - * authentication service, redirecting to the main view if successful. - */ - $scope.login = function login() { - - // Attempt login once existing session is destroyed - authenticationService.login($scope.username, $scope.password) - - // Redirect to main view upon success - .then(function loginSuccessful() { - userPageService.getHomePage() - .then(function homePageRetrieved(homePage) { - $location.url(homePage.url); - }); - }) - - // Reset and focus password upon failure - ['catch'](function loginFailed() { - $scope.loginError = true; - $scope.passwordFocused = true; - $scope.password = ''; - }); - - }; - -}]); diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js new file mode 100644 index 000000000..1b8b407ca --- /dev/null +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive for displaying an arbitrary login form. + */ +angular.module('login').directive('guacLogin', [function guacLogin() { + + // Login directive + var directive = { + restrict : 'E', + replace : true, + templateUrl : 'app/login/templates/login.html' + }; + + // Login directive scope + directive.scope = { + + /** + * The login form or set of fields. This will be displayed to the user + * to capture their credentials. + * + * @type Form[]|Form|Field[]|Field + */ + form : '=' + + }; + + // Controller for login directive + directive.controller = ['$scope', '$injector', + function loginController($scope, $injector) { + + // Required services + var $route = $injector.get('$route'); + var authenticationService = $injector.get('authenticationService'); + + /** + * Whether an error occurred during login. + * + * @type Boolean + */ + $scope.loginError = false; + + /** + * All form values entered by the user. + */ + $scope.values = {}; + + /** + * Submits the currently-specified username and password to the + * authentication service, redirecting to the main view if successful. + */ + $scope.login = function login() { + + // Attempt login once existing session is destroyed + authenticationService.authenticate($scope.values) + + // Clear and reload upon success + .then(function loginSuccessful() { + $scope.loginError = false; + $scope.values = {}; + $route.reload(); + }) + + // Reset upon failure + ['catch'](function loginFailed() { + $scope.loginError = true; + $scope.values.password = ''; + }); + + }; + + }]; + + return directive; + +}]); diff --git a/guacamole/src/main/webapp/app/login/loginModule.js b/guacamole/src/main/webapp/app/login/loginModule.js index 54e531233..aa879f727 100644 --- a/guacamole/src/main/webapp/app/login/loginModule.js +++ b/guacamole/src/main/webapp/app/login/loginModule.js @@ -23,4 +23,8 @@ /** * The module for the login functionality. */ -angular.module('login', ['element', 'navigation']); +angular.module('login', [ + 'element', + 'form', + 'navigation' +]); diff --git a/guacamole/src/main/webapp/app/login/styles/login.css b/guacamole/src/main/webapp/app/login/styles/login.css index 87f636095..7bea2dfc8 100644 --- a/guacamole/src/main/webapp/app/login/styles/login.css +++ b/guacamole/src/main/webapp/app/login/styles/login.css @@ -27,6 +27,8 @@ div.login-ui { left: 0; top: 0; display: table; + background: white; + z-index: 20; } p.login-error { @@ -52,3 +54,35 @@ p.login-error { text-align: center; color: #964040; } + +.login-fields .form-field .password-field .toggle-password { + display: none; +} + +.login-fields .labeled-field { + display: block; + position: relative; +} + +.login-fields .field-header { + + display: block; + position: absolute; + left: 0; + right: 0; + overflow: hidden; + + z-index: -1; + margin: 0.5em; + font-size: 0.9em; + opacity: 0.5; + +} + +.login-fields .form-field.empty input { + background: transparent; +} + +.login-fields .form-field input:focus { + background: white; +} diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index e07611894..ac6879a14 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -1,26 +1,25 @@ - -
+ @@ -35,10 +34,9 @@ THE SOFTWARE.
{{'APP.NAME' | translate}}
- + diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 4d3b3047e..4efe62cf0 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -48,6 +48,7 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() // Get required services var $document = $injector.get('$document'); var $location = $injector.get('$location'); + var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); var userPageService = $injector.get('userPageService'); @@ -100,12 +101,15 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() }; /** - * Logs out the current user, redirecting them to back to the login - * screen after logout completes. + * Logs out the current user, redirecting them to back to the root + * after logout completes. */ $scope.logout = function logout() { authenticationService.logout()['finally'](function logoutComplete() { - $location.path('/login/'); + if ($location.path() !== '/') + $location.url('/'); + else + $route.reload(); }); }; diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 40b49bb1c..861cc4b44 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -34,22 +34,30 @@ THE SOFTWARE. - -
-
- + +
+ + +
+
+ +
+ +
+
+ + +
+
+ +
+
+
- -
-
- - -
-
- -
-
+ + + diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index f3d8cf281..3d72affc9 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -141,12 +141,12 @@ "LOGIN": { - "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", - "ERROR_INVALID_LOGIN" : "Invalid Login", + "ERROR_INVALID_LOGIN" : "Invalid Login", - "FIELD_PLACEHOLDER_USERNAME" : "Username", - "FIELD_PLACEHOLDER_PASSWORD" : "Password" + "FIELD_HEADER_USERNAME" : "Username", + "FIELD_HEADER_PASSWORD" : "Password" },