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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
@ -99,6 +100,7 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
|
||||||
}
|
}
|
||||||
|
user.TokenIssueAt = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Role != 0 {
|
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
|
// remove all of the users persisted API keys
|
||||||
handler.apiKeyService.InvalidateUserKeyCache(user.ID)
|
handler.apiKeyService.InvalidateUserKeyCache(user.ID)
|
||||||
|
|
||||||
return response.JSON(w, user)
|
return response.JSON(w, user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package users
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
httperror "github.com/portainer/libhttp/error"
|
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}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.TokenIssueAt = time.Now().Unix()
|
||||||
|
|
||||||
err = handler.DataStore.User().UpdateUser(user.ID, user)
|
err = handler.DataStore.User().UpdateUser(user.ID, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user changes inside the database", err}
|
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 err == nil && parsedToken != nil {
|
||||||
if cl, ok := parsedToken.Claims.(*claims); ok && parsedToken.Valid {
|
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{
|
return &portainer.TokenData{
|
||||||
ID: portainer.UserID(cl.UserID),
|
ID: portainer.UserID(cl.UserID),
|
||||||
Username: cl.Username,
|
Username: cl.Username,
|
||||||
|
@ -162,6 +170,7 @@ func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
StandardClaims: jwt.StandardClaims{
|
StandardClaims: jwt.StandardClaims{
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
|
IssuedAt: time.Now().Unix(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1104,6 +1104,7 @@ type (
|
||||||
UserTheme string `example:"dark"`
|
UserTheme string `example:"dark"`
|
||||||
// User role (1 for administrator account and 2 for regular account)
|
// 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 fields
|
||||||
// Deprecated in DBVersion == 25
|
// Deprecated in DBVersion == 25
|
||||||
|
|
|
@ -218,3 +218,17 @@ export function confirmImageExport(callback: ConfirmCallback) {
|
||||||
callback,
|
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,
|
confirmDetachment,
|
||||||
confirmDeletionAsync,
|
confirmDeletionAsync,
|
||||||
confirmEndpointSnapshot,
|
confirmEndpointSnapshot,
|
||||||
|
confirmChangePassword,
|
||||||
confirmImageExport,
|
confirmImageExport,
|
||||||
confirmImageForceRemoval,
|
confirmImageForceRemoval,
|
||||||
confirmRedeploy,
|
confirmRedeploy,
|
||||||
|
@ -53,6 +54,7 @@ export function ModalServiceAngular() {
|
||||||
confirmDeletionAsync,
|
confirmDeletionAsync,
|
||||||
confirmContainerRecreation,
|
confirmContainerRecreation,
|
||||||
confirmEndpointSnapshot,
|
confirmEndpointSnapshot,
|
||||||
|
confirmChangePassword,
|
||||||
confirmImageExport,
|
confirmImageExport,
|
||||||
confirmServiceForceUpdate,
|
confirmServiceForceUpdate,
|
||||||
selectRegistry,
|
selectRegistry,
|
||||||
|
|
|
@ -16,15 +16,17 @@ angular.module('portainer.app').controller('AccountController', [
|
||||||
userTheme: '',
|
userTheme: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updatePassword = function () {
|
$scope.updatePassword = async function () {
|
||||||
UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword)
|
const confirmed = await ModalService.confirmChangePassword();
|
||||||
.then(function success() {
|
if (confirmed) {
|
||||||
|
try {
|
||||||
|
await UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword);
|
||||||
Notifications.success('Success', 'Password successfully updated');
|
Notifications.success('Success', 'Password successfully updated');
|
||||||
$state.reload();
|
$state.go('portainer.logout');
|
||||||
})
|
} catch (err) {
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, err.msg);
|
Notifications.error('Failure', err, err.msg);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeAction = (selectedTokens) => {
|
$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 })
|
UserService.updateUser($scope.user.Id, { password: $scope.formValues.newPassword })
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Password successfully updated');
|
Notifications.success('Password successfully updated');
|
||||||
|
|
||||||
|
if (isCurrentUser) {
|
||||||
|
$state.go('portainer.logout');
|
||||||
|
} else {
|
||||||
$state.reload();
|
$state.reload();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to update user password');
|
Notifications.error('Failure', err, 'Unable to update user password');
|
||||||
|
|
Loading…
Reference in New Issue