mirror of https://github.com/portainer/portainer
				
				
				
			feat(user):logout after change password EE-1590 (#6267)
* fix(user) logout after password changepull/6453/head
							parent
							
								
									58de8e175f
								
							
						
					
					
						commit
						661f0aad49
					
				| 
						 | 
				
			
			@ -3,6 +3,7 @@ package users
 | 
			
		|||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	httperror "github.com/portainer/libhttp/error"
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +100,7 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
 | 
			
		|||
		if err != nil {
 | 
			
		||||
			return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
 | 
			
		||||
		}
 | 
			
		||||
		user.TokenIssueAt = time.Now().Unix()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if payload.Role != 0 {
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +118,5 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
 | 
			
		|||
 | 
			
		||||
	// remove all of the users persisted API keys
 | 
			
		||||
	handler.apiKeyService.InvalidateUserKeyCache(user.ID)
 | 
			
		||||
 | 
			
		||||
	return response.JSON(w, user)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ package users
 | 
			
		|||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	httperror "github.com/portainer/libhttp/error"
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +86,8 @@ func (handler *Handler) userUpdatePassword(w http.ResponseWriter, r *http.Reques
 | 
			
		|||
		return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user.TokenIssueAt = time.Now().Unix()
 | 
			
		||||
 | 
			
		||||
	err = handler.DataStore.User().UpdateUser(user.ID, user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user changes inside the database", err}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,6 +121,14 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData,
 | 
			
		|||
 | 
			
		||||
	if err == nil && parsedToken != nil {
 | 
			
		||||
		if cl, ok := parsedToken.Claims.(*claims); ok && parsedToken.Valid {
 | 
			
		||||
 | 
			
		||||
			user, err := service.dataStore.User().User(portainer.UserID(cl.UserID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errInvalidJWTToken
 | 
			
		||||
			}
 | 
			
		||||
			if user.TokenIssueAt > cl.StandardClaims.IssuedAt {
 | 
			
		||||
				return nil, errInvalidJWTToken
 | 
			
		||||
			}
 | 
			
		||||
			return &portainer.TokenData{
 | 
			
		||||
				ID:       portainer.UserID(cl.UserID),
 | 
			
		||||
				Username: cl.Username,
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +170,7 @@ func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt
 | 
			
		|||
		Scope:    scope,
 | 
			
		||||
		StandardClaims: jwt.StandardClaims{
 | 
			
		||||
			ExpiresAt: expiresAt,
 | 
			
		||||
			IssuedAt:  time.Now().Unix(),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1103,7 +1103,8 @@ type (
 | 
			
		|||
		// User Theme
 | 
			
		||||
		UserTheme string `example:"dark"`
 | 
			
		||||
		// User role (1 for administrator account and 2 for regular account)
 | 
			
		||||
		Role UserRole `json:"Role" example:"1"`
 | 
			
		||||
		Role         UserRole `json:"Role" example:"1"`
 | 
			
		||||
		TokenIssueAt int64    `json:"TokenIssueAt" example:"1"`
 | 
			
		||||
 | 
			
		||||
		// Deprecated fields
 | 
			
		||||
		// Deprecated in DBVersion == 25
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -218,3 +218,17 @@ export function confirmImageExport(callback: ConfirmCallback) {
 | 
			
		|||
    callback,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function confirmChangePassword() {
 | 
			
		||||
  return confirmAsync({
 | 
			
		||||
    title: 'Are you sure?',
 | 
			
		||||
    message:
 | 
			
		||||
      'You will be logged out after the password change. Do you want to change your password?',
 | 
			
		||||
    buttons: {
 | 
			
		||||
      confirm: {
 | 
			
		||||
        label: 'Change',
 | 
			
		||||
        className: 'btn-primary',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import {
 | 
			
		|||
  confirmDetachment,
 | 
			
		||||
  confirmDeletionAsync,
 | 
			
		||||
  confirmEndpointSnapshot,
 | 
			
		||||
  confirmChangePassword,
 | 
			
		||||
  confirmImageExport,
 | 
			
		||||
  confirmImageForceRemoval,
 | 
			
		||||
  confirmRedeploy,
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +54,7 @@ export function ModalServiceAngular() {
 | 
			
		|||
    confirmDeletionAsync,
 | 
			
		||||
    confirmContainerRecreation,
 | 
			
		||||
    confirmEndpointSnapshot,
 | 
			
		||||
    confirmChangePassword,
 | 
			
		||||
    confirmImageExport,
 | 
			
		||||
    confirmServiceForceUpdate,
 | 
			
		||||
    selectRegistry,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,15 +16,17 @@ angular.module('portainer.app').controller('AccountController', [
 | 
			
		|||
      userTheme: '',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    $scope.updatePassword = function () {
 | 
			
		||||
      UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword)
 | 
			
		||||
        .then(function success() {
 | 
			
		||||
    $scope.updatePassword = async function () {
 | 
			
		||||
      const confirmed = await ModalService.confirmChangePassword();
 | 
			
		||||
      if (confirmed) {
 | 
			
		||||
        try {
 | 
			
		||||
          await UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword);
 | 
			
		||||
          Notifications.success('Success', 'Password successfully updated');
 | 
			
		||||
          $state.reload();
 | 
			
		||||
        })
 | 
			
		||||
        .catch(function error(err) {
 | 
			
		||||
          $state.go('portainer.logout');
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
          Notifications.error('Failure', err, err.msg);
 | 
			
		||||
        });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    $scope.removeAction = (selectedTokens) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,11 +63,21 @@ angular.module('portainer.app').controller('UserController', [
 | 
			
		|||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    $scope.updatePassword = function () {
 | 
			
		||||
    $scope.updatePassword = async function () {
 | 
			
		||||
      const isCurrentUser = Authentication.getUserDetails().ID === $scope.user.Id;
 | 
			
		||||
      const confirmed = !isCurrentUser || (await ModalService.confirmChangePassword());
 | 
			
		||||
      if (!confirmed) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      UserService.updateUser($scope.user.Id, { password: $scope.formValues.newPassword })
 | 
			
		||||
        .then(function success() {
 | 
			
		||||
          Notifications.success('Password successfully updated');
 | 
			
		||||
          $state.reload();
 | 
			
		||||
 | 
			
		||||
          if (isCurrentUser) {
 | 
			
		||||
            $state.go('portainer.logout');
 | 
			
		||||
          } else {
 | 
			
		||||
            $state.reload();
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch(function error(err) {
 | 
			
		||||
          Notifications.error('Failure', err, 'Unable to update user password');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue