diff --git a/data/model.py b/data/model.py index 1a6e91f1e..855e85a2d 100644 --- a/data/model.py +++ b/data/model.py @@ -4,8 +4,7 @@ import dateutil.parser import operator from database import * -from util.validation import (validate_email, validate_username, - validate_password) +from util.validation import * logger = logging.getLogger(__name__) @@ -35,9 +34,7 @@ def create_user(username, password, email): # We allow password none for the federated login case. if password is not None and not validate_password(password): - raise InvalidPasswordException('Invalid password, password must be at ' + - 'least 8 characters and contain no ' + - 'whitespace.') + raise InvalidPasswordException(INVALID_PASSWORD_MESSAGE) try: existing = User.get((User.username == username) | (User.email == email)) @@ -210,6 +207,9 @@ def get_matching_repositories(repo_term, username=None): def change_password(user, new_password): + if not validate_password(new_password): + raise InvalidPasswordException(INVALID_PASSWORD_MESSAGE) + pw_hash = bcrypt.hashpw(new_password, bcrypt.gensalt()) user.password_hash = pw_hash user.save() diff --git a/endpoints/api.py b/endpoints/api.py index 2c6dcf6a8..db8355b54 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -51,8 +51,35 @@ def get_logged_in_user(): 'username': user.username, 'email': user.email, 'gravatar': compute_hash(user.email), + 'askForPassword': user.password_hash is None, }) +@app.route('/api/user/', methods=['PUT']) +@api_login_required +def change_user_details(): + user = current_user.db_user + + user_data = request.get_json(); + + try: + if user_data['password']: + logger.debug('Changing password for user: %s', user.username) + model.change_password(user, user_data['password']) + except model.InvalidPasswordException, ex: + error_resp = jsonify({ + 'message': ex.message, + }) + error_resp.status_code = 400 + return error_resp + + return jsonify({ + 'verified': user.verified, + 'anonymous': False, + 'username': user.username, + 'email': user.email, + 'gravatar': compute_hash(user.email), + 'askForPassword': user.password_hash is None, + }) @app.route('/api/user/', methods=['POST']) def create_user_api(): @@ -60,7 +87,7 @@ def create_user_api(): existing_user = model.get_user(user_data['username']) if existing_user: error_resp = jsonify({ - 'message': 'The username already exists' + 'message': 'The username already exists' }) error_resp.status_code = 400 return error_resp diff --git a/static/css/quay.css b/static/css/quay.css index dc7c2fade..0c7a33c1a 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -137,11 +137,11 @@ margin-left: 0px; } -.form-signup input.ng-invalid.ng-dirty { +form input.ng-invalid.ng-dirty { background-color: #FDD7D9; } -.form-signup input.ng-valid.ng-dirty { +form input.ng-valid.ng-dirty { background-color: #DDFFEE; } @@ -689,6 +689,11 @@ p.editable:hover i { margin-bottom: 10px; } +.user-admin .form-change-pw input { + margin-top: 12px; + margin-bottom: 12px; +} + #image-history-container .node circle { cursor: pointer; fill: #fff; diff --git a/static/js/app.js b/static/js/app.js index 310d3dd74..4edfa3549 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -5,7 +5,8 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', verified: false, anonymous: true, username: null, - email: null + email: null, + askForPassword: false, } var userService = {} diff --git a/static/js/controllers.js b/static/js/controllers.js index 4f25a52e2..100805a5c 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -456,9 +456,13 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { }); } -function UserAdminCtrl($scope, Restangular, PlanService, KeyService, $routeParams) { +function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService, KeyService, $routeParams) { $scope.plans = PlanService.planList(); + $scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) { + $scope.askForPassword = currentUser.askForPassword; + }, true); + var subscribedToPlan = function(sub) { $scope.subscription = sub; $scope.subscribedPlan = PlanService.getPlan(sub.plan); @@ -545,4 +549,28 @@ function UserAdminCtrl($scope, Restangular, PlanService, KeyService, $routeParam $scope.subscribe(requested); } } + + $scope.updatingUser = false; + $scope.changePasswordSuccess = false; + $('.form-change-pw').popover(); + + $scope.changePassword = function() { + $('.form-change-pw').popover('hide'); + $scope.updatingUser = true; + $scope.changePasswordSuccess = false; + var changePasswordPost = Restangular.one('user/'); + changePasswordPost.customPUT($scope.user).then(function() { + $scope.updatingUser = false; + $scope.changePasswordSuccess = true; + + UserService.load(); + }, function(result) { + $scope.updatingUser = false; + + $scope.changePasswordError = result.data.message; + $timeout(function() { + $('.form-change-pw').popover('show'); + }); + }); + }; } \ No newline at end of file diff --git a/static/partials/user-admin.html b/static/partials/user-admin.html index 17c98a070..39201569d 100644 --- a/static/partials/user-admin.html +++ b/static/partials/user-admin.html @@ -7,6 +7,11 @@